From 968927d472ffd680c875f24b04cd67ca39d3d4e5 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Sat, 13 Dec 2014 22:00:32 +0100 Subject: [PATCH 001/101] Enabled sorting for custom columns on list view --- .../Repositories/ContentRepository.cs | 7 +- .../Interfaces/IContentRepository.cs | 3 +- .../Interfaces/IMediaRepository.cs | 3 +- .../Interfaces/IMemberRepository.cs | 17 +++-- .../Repositories/MediaRepository.cs | 7 +- .../Repositories/MemberRepository.cs | 19 ++--- .../Repositories/VersionableRepositoryBase.cs | 73 ++++++++++++------- src/Umbraco.Core/Services/ContentService.cs | 10 ++- src/Umbraco.Core/Services/IContentService.cs | 6 +- src/Umbraco.Core/Services/IMediaService.cs | 6 +- src/Umbraco.Core/Services/IMemberService.cs | 9 ++- src/Umbraco.Core/Services/MediaService.cs | 10 ++- src/Umbraco.Core/Services/MemberService.cs | 14 ++-- .../Repositories/ContentRepositoryTest.cs | 12 +-- .../Repositories/MediaRepositoryTest.cs | 14 ++-- .../UmbracoExamine/IndexInitializer.cs | 2 +- .../src/common/resources/content.resource.js | 4 +- .../src/common/resources/media.resource.js | 4 +- .../src/common/resources/member.resource.js | 4 +- .../listview/listview.controller.js | 22 +++--- .../propertyeditors/listview/listview.html | 4 +- src/Umbraco.Web/Editors/ContentController.cs | 3 +- src/Umbraco.Web/Editors/MediaController.cs | 3 +- src/Umbraco.Web/Editors/MemberController.cs | 3 +- src/UmbracoExamine/UmbracoMemberIndexer.cs | 2 +- 25 files changed, 153 insertions(+), 108 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 203393391a..ce4f54b2b1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -233,7 +233,7 @@ namespace Umbraco.Core.Persistence.Repositories var processed = 0; do { - var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending); + var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending, true); var xmlItems = (from descendant in descendants let xml = serializer(descendant) @@ -713,10 +713,11 @@ namespace Umbraco.Core.Persistence.Repositories /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects public IEnumerable GetPagedResultsByQuery(IQuery query, int pageIndex, int pageSize, out int totalRecords, - string orderBy, Direction orderDirection, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") { //NOTE: This uses the GetBaseQuery method but that does not take into account the required 'newest' field which is @@ -735,7 +736,7 @@ namespace Umbraco.Core.Persistence.Repositories return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsDocument", "nodeId"), - ProcessQuery, orderBy, orderDirection, + ProcessQuery, orderBy, orderDirection, orderBySystemField, filterCallback); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index 5cb470b7cd..50adaadd73 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -92,9 +92,10 @@ namespace Umbraco.Core.Persistence.Repositories /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedResultsByQuery(IQuery query, int pageIndex, int pageSize, out int totalRecords, - string orderBy, Direction orderDirection, string filter = ""); + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs index f84844c177..94ac1ed7cb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs @@ -33,9 +33,10 @@ namespace Umbraco.Core.Persistence.Repositories /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedResultsByQuery(IQuery query, int pageIndex, int pageSize, out int totalRecords, - string orderBy, Direction orderDirection, string filter = ""); + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs index 0538fe18fa..ece5e02ba0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs @@ -44,16 +44,17 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Gets paged member results /// - /// - /// - /// - /// - /// - /// - /// + /// The query. + /// Index of the page. + /// Size of the page. + /// The total records. + /// The order by column + /// The order direction. + /// Flag to indicate when ordering by system field + /// Search query /// IEnumerable GetPagedResultsByQuery(IQuery query, int pageIndex, int pageSize, out int totalRecords, - string orderBy, Direction orderDirection, string filter = ""); + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); //IEnumerable GetPagedResultsByQuery( // Sql sql, int pageIndex, int pageSize, out int totalRecords, diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index a6db5387f4..5c0be2c2e6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -242,7 +242,7 @@ namespace Umbraco.Core.Persistence.Repositories var processed = 0; do { - var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending); + var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending, true); var xmlItems = (from descendant in descendants let xml = serializer(descendant) @@ -451,10 +451,11 @@ namespace Umbraco.Core.Persistence.Repositories /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects public IEnumerable GetPagedResultsByQuery(IQuery query, int pageIndex, int pageSize, out int totalRecords, - string orderBy, Direction orderDirection, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") { var args = new List(); var sbWhere = new StringBuilder(); @@ -468,7 +469,7 @@ namespace Umbraco.Core.Persistence.Repositories return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsContentVersion", "contentId"), - ProcessQuery, orderBy, orderDirection, + ProcessQuery, orderBy, orderDirection, orderBySystemField, filterCallback); } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 9e3a47efe0..f9ff109879 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -487,7 +487,7 @@ namespace Umbraco.Core.Persistence.Repositories var processed = 0; do { - var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending); + var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending, true); var xmlItems = (from descendant in descendants let xml = serializer(descendant) @@ -643,18 +643,19 @@ namespace Umbraco.Core.Persistence.Repositories /// /// The where clause, if this is null all records are queried /// - /// - /// - /// - /// - /// - /// + /// Index of the page. + /// Size of the page. + /// The total records. + /// The order by column + /// The order direction. + /// Flag to indicate when ordering by system field + /// Search query /// /// /// The query supplied will ONLY work with data specifically on the cmsMember table because we are using PetaPoco paging (SQL paging) /// public IEnumerable GetPagedResultsByQuery(IQuery query, int pageIndex, int pageSize, out int totalRecords, - string orderBy, Direction orderDirection, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") { var args = new List(); var sbWhere = new StringBuilder(); @@ -669,7 +670,7 @@ namespace Umbraco.Core.Persistence.Repositories return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsMember", "nodeId"), - ProcessQuery, orderBy, orderDirection, + ProcessQuery, orderBy, orderDirection, orderBySystemField, filterCallback); } diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 3acc06b132..c53937473d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -238,24 +238,54 @@ namespace Umbraco.Core.Persistence.Repositories return filteredSql; } - private Sql GetSortedSqlForPagedResults(Sql sql, Direction orderDirection, string orderBy) + private Sql GetSortedSqlForPagedResults(Sql sql, Direction orderDirection, string orderBy, bool orderBySystemField) { //copy to var so that the original isn't changed var sortedSql = new Sql(sql.SQL, sql.Arguments); - // Apply order according to parameters - if (string.IsNullOrEmpty(orderBy) == false) + + if (orderBySystemField) { - var orderByParams = new[] { GetDatabaseFieldNameForOrderBy(orderBy) }; - if (orderDirection == Direction.Ascending) + // Apply order according to parameters + if (string.IsNullOrEmpty(orderBy) == false) { - sortedSql.OrderBy(orderByParams); + var orderByParams = new[] { GetDatabaseFieldNameForOrderBy(orderBy) }; + if (orderDirection == Direction.Ascending) + { + sortedSql.OrderBy(orderByParams); + } + else + { + sortedSql.OrderByDescending(orderByParams); + } } - else - { - sortedSql.OrderByDescending(orderByParams); - } - return sortedSql; } + else + { + // Sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through valie + // from most recent content version for the given order by field + sortedSql.Append(@"ORDER BY ( + SELECT CASE + WHEN dataInt Is Not Null THEN RIGHT('00000000' + CAST(dataInt AS varchar(8)),8) + WHEN dataDate Is Not Null THEN CONVERT(varchar, dataDate, 102) + ELSE IsNull(dataNvarchar,'') + END + FROM cmsContent c + INNER JOIN cmsContentVersion cv ON cv.ContentId = c.nodeId AND VersionDate = ( + SELECT Max(VersionDate) + FROM cmsContentVersion + WHERE ContentId = c.nodeId + ) + INNER JOIN cmsPropertyType cpt ON cpt.contentTypeId = c.contentType + INNER JOIN cmsPropertyData cpd ON cpd.contentNodeId = c.nodeId + AND cpd.versionId = cv.VersionId + AND cpd.propertytypeId = cpt.id + WHERE c.nodeId = umbracoNode.Id and cpt.Alias = @0)", orderBy); + if (orderDirection == Direction.Descending) + { + sortedSql.Append(" DESC"); + } + } + return sortedSql; } @@ -273,6 +303,7 @@ namespace Umbraco.Core.Persistence.Repositories /// A callback to process the query result /// The order by column /// The order direction. + /// Flag to indicate when ordering by system field /// /// orderBy protected IEnumerable GetPagedResultsByQuery(IQuery query, int pageIndex, int pageSize, out int totalRecords, @@ -280,6 +311,7 @@ namespace Umbraco.Core.Persistence.Repositories Func> processQuery, string orderBy, Direction orderDirection, + bool orderBySystemField, Func> defaultFilter = null) where TContentBase : class, IAggregateRoot, TEntity { @@ -302,7 +334,7 @@ namespace Umbraco.Core.Persistence.Repositories //get sorted and filtered sql var sqlNodeIdsWithSort = GetSortedSqlForPagedResults( GetFilteredSqlForPagedResults(sqlNodeIds, defaultFilter), - orderDirection, orderBy); + orderDirection, orderBy, orderBySystemField); // Get page of results and total count IEnumerable result; @@ -340,22 +372,9 @@ namespace Umbraco.Core.Persistence.Repositories //get sorted and filtered sql var fullQuery = GetSortedSqlForPagedResults( GetFilteredSqlForPagedResults(withInnerJoinSql, defaultFilter), - orderDirection, orderBy); - - var content = processQuery(fullQuery) - .Cast() - .AsQueryable(); + orderDirection, orderBy, orderBySystemField); - // Now we need to ensure this result is also ordered by the same order by clause - var orderByProperty = GetEntityPropertyNameForOrderBy(orderBy); - if (orderDirection == Direction.Ascending) - { - result = content.OrderBy(orderByProperty); - } - else - { - result = content.OrderByDescending(orderByProperty); - } + return processQuery(fullQuery); } else { diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 057ca928bd..5bebdefec9 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -495,10 +495,11 @@ namespace Umbraco.Core.Services /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, - string orderBy, Direction orderDirection, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); @@ -511,7 +512,7 @@ namespace Umbraco.Core.Services { query.Where(x => x.ParentId == id); } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, filter); + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); return contents; } @@ -526,9 +527,10 @@ namespace Umbraco.Core.Services /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); @@ -541,7 +543,7 @@ namespace Umbraco.Core.Services { query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, filter); + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); return contents; } diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index f5579896f4..ce2828e208 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -128,10 +128,11 @@ namespace Umbraco.Core.Services /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); /// /// Gets a collection of objects by Parent Id @@ -142,10 +143,11 @@ namespace Umbraco.Core.Services /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); /// /// Gets a collection of an objects versions by its Id diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index b77046ddc1..05f861a331 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -80,10 +80,11 @@ namespace Umbraco.Core.Services /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); /// /// Gets a collection of objects by Parent Id @@ -94,10 +95,11 @@ namespace Umbraco.Core.Services /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); /// /// Gets descendants of a object by its Id diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 249a744081..b8bb2e69f1 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -28,13 +28,14 @@ namespace Umbraco.Core.Services /// Current page index /// Size of the page /// Total number of records found (out) - /// - /// + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field /// - /// + /// Search text filter /// IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords, - string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = ""); + string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = ""); /// /// Creates an object without persisting it diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 4a42439024..e1ca32d14c 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -403,10 +403,11 @@ namespace Umbraco.Core.Services /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, - string orderBy, Direction orderDirection, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); @@ -418,7 +419,7 @@ namespace Umbraco.Core.Services { query.Where(x => x.ParentId == id); } - var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, filter); + var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); return medias; } @@ -433,9 +434,10 @@ namespace Umbraco.Core.Services /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); @@ -448,7 +450,7 @@ namespace Umbraco.Core.Services { query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, filter); + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); return contents; } diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 4bb5e406f9..ea566eca80 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -308,7 +308,7 @@ namespace Umbraco.Core.Services throw new ArgumentOutOfRangeException("matchType"); } - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Name", Direction.Ascending); + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Name", Direction.Ascending, true); } } @@ -349,7 +349,7 @@ namespace Umbraco.Core.Services throw new ArgumentOutOfRangeException("matchType"); } - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Email", Direction.Ascending); + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Email", Direction.Ascending, true); } } @@ -390,7 +390,7 @@ namespace Umbraco.Core.Services throw new ArgumentOutOfRangeException("matchType"); } - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending); + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, true); } } @@ -677,22 +677,22 @@ namespace Umbraco.Core.Services var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateMemberRepository(uow)) { - return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending); + return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, true); } } public IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords, - string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = "") { var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateMemberRepository(uow)) { if (memberTypeAlias == null) { - return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filter); + return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, filter); } var query = new Query().Where(x => x.ContentTypeAlias == memberTypeAlias); - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filter); + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, filter); } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index f55b185d8b..cacc17bddf 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -483,7 +483,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); int totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -504,7 +504,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); int totalRecords; - var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "Name", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "Name", Direction.Ascending, true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -525,7 +525,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); int totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "Name", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "Name", Direction.Ascending, true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -546,7 +546,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); int totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Descending); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Descending, true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -567,7 +567,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); int totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, "Page 2"); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, "Page 2"); // Assert Assert.That(totalRecords, Is.EqualTo(1)); @@ -588,7 +588,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); int totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, "Page"); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, "Page"); // Assert Assert.That(totalRecords, Is.EqualTo(2)); diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index b76bdd37bc..dcf7435c3b 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -332,7 +332,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); int totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -353,7 +353,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); int totalRecords; - var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "SortOrder", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "SortOrder", Direction.Ascending, true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -374,7 +374,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); int totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "SortOrder", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "SortOrder", Direction.Ascending, true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -395,7 +395,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); int totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Descending); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Descending, true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -416,7 +416,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); int totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -437,7 +437,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); int totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, "File"); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true, "File"); // Assert Assert.That(totalRecords, Is.EqualTo(1)); @@ -458,7 +458,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); int totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, "Test"); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true, "Test"); // Assert Assert.That(totalRecords, Is.EqualTo(2)); diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index 7196d59c0c..670fccd07f 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -74,7 +74,7 @@ namespace Umbraco.Tests.UmbracoExamine mediaService = Mock.Of( x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny()) + It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()) == allRecs); } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 4de66fa240..2823200832 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -435,7 +435,8 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { pageNumber: 0, filter: '', orderDirection: "Ascending", - orderBy: "SortOrder" + orderBy: "SortOrder", + orderBySystemField: true }; if (options === undefined) { options = {}; @@ -463,6 +464,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { { pageSize: options.pageSize }, { orderBy: options.orderBy }, { orderDirection: options.orderDirection }, + { orderBySystemField: options.orderBySystemField }, { filter: options.filter } ])), 'Failed to retrieve children for content item ' + parentId); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index 12963a1ea7..82376ef478 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -296,7 +296,8 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { pageNumber: 0, filter: '', orderDirection: "Ascending", - orderBy: "SortOrder" + orderBy: "SortOrder", + orderBySystemField: true }; if (options === undefined) { options = {}; @@ -324,6 +325,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { { pageSize: options.pageSize }, { orderBy: options.orderBy }, { orderDirection: options.orderDirection }, + { orderBySystemField: options.orderBySystemField }, { filter: options.filter } ])), 'Failed to retrieve children for media item ' + parentId); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js index 381019fb5b..b0eb8c7bd9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js @@ -34,7 +34,8 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { pageNumber: 1, filter: '', orderDirection: "Ascending", - orderBy: "LoginName" + orderBy: "LoginName", + orderBySystemField: true }; if (options === undefined) { options = {}; @@ -56,6 +57,7 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { { pageSize: options.pageSize }, { orderBy: options.orderBy }, { orderDirection: options.orderDirection }, + { orderBySystemField: options.orderBySystemField }, { filter: options.filter } ]; if (memberTypeAlias != null) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index a0c0c8b517..f83a218a2e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -66,6 +66,7 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific { alias: 'updateDate', header: 'Last edited', isSystem: 1 }, { alias: 'updater', header: 'Last edited by', isSystem: 1 } ], + orderBySystemField: true, allowBulkPublish: true, allowBulkUnpublish: true, allowBulkDelete: true, @@ -73,16 +74,15 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific //update all of the system includeProperties to enable sorting _.each($scope.options.includeProperties, function(e, i) { - - if (e.isSystem) { - //NOTE: special case for contentTypeAlias, it's a system property that cannot be sorted - // to do that, we'd need to update the base query for content to include the content type alias column - // which requires another join and would be slower. BUT We are doing this for members so not sure it makes a diff? - if (e.alias != "contentTypeAlias") { - e.allowSorting = true; - } - + //NOTE: special case for contentTypeAlias, it's a system property that cannot be sorted + // to do that, we'd need to update the base query for content to include the content type alias column + // which requires another join and would be slower. BUT We are doing this for members so not sure it makes a diff? + if (e.alias != "contentTypeAlias") { + e.allowSorting = true; + } + + if (e.isSystem) { //localize the header var key = getLocalizedKey(e.alias); localizationService.localize(key).then(function (v) { @@ -111,7 +111,7 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific //$location.search("page", $scope.options.pageNumber); }; - $scope.sort = function(field, allow) { + $scope.sort = function(field, allow, isSystem) { if (allow) { $scope.options.orderBy = field; @@ -122,6 +122,8 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific $scope.options.orderDirection = "desc"; } + $scope.options.orderBySystemField = isSystem; + $scope.reloadView($scope.contentId); } }; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html index 599d8e5569..9029a2fd65 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html @@ -48,14 +48,14 @@ - + Name - + diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index d52a65fcb3..4d3c211113 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -155,13 +155,14 @@ namespace Umbraco.Web.Editors int pageSize = 0, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, string filter = "") { int totalChildren; IContent[] children; if (pageNumber > 0 && pageSize > 0) { - children = Services.ContentService.GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren, orderBy, orderDirection, filter).ToArray(); + children = Services.ContentService.GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter).ToArray(); } else { diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 7f9d703763..b659676c1d 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -137,13 +137,14 @@ namespace Umbraco.Web.Editors int pageSize = 0, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, string filter = "") { int totalChildren; IMedia[] children; if (pageNumber > 0 && pageSize > 0) { - children = Services.MediaService.GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren, orderBy, orderDirection, filter).ToArray(); + children = Services.MediaService.GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter).ToArray(); } else { diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 30320b278e..cad94b2d1d 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -77,6 +77,7 @@ namespace Umbraco.Web.Editors int pageSize = 100, string orderBy = "Name", Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, string filter = "", string memberTypeAlias = null) { @@ -88,7 +89,7 @@ namespace Umbraco.Web.Editors if (MembershipScenario == MembershipScenario.NativeUmbraco) { - var members = Services.MemberService.GetAll((pageNumber - 1), pageSize, out totalRecords, orderBy, orderDirection, memberTypeAlias, filter).ToArray(); + var members = Services.MemberService.GetAll((pageNumber - 1), pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, memberTypeAlias, filter).ToArray(); if (totalRecords == 0) { return new PagedResult(0, 0, 0); diff --git a/src/UmbracoExamine/UmbracoMemberIndexer.cs b/src/UmbracoExamine/UmbracoMemberIndexer.cs index 1fdfdb97b3..d60cda2840 100644 --- a/src/UmbracoExamine/UmbracoMemberIndexer.cs +++ b/src/UmbracoExamine/UmbracoMemberIndexer.cs @@ -128,7 +128,7 @@ namespace UmbracoExamine do { int total; - var members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName", Direction.Ascending, nodeType); + var members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName", Direction.Ascending, true, nodeType).ToArray(); memberCount = 0; foreach (var member in members) { From 5954c370ed0144e522b7a45f8cad6b8e63c6ae97 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Sat, 13 Dec 2014 22:24:06 +0100 Subject: [PATCH 002/101] Fixed issue with sorting on default member list view column of username --- .../Persistence/Repositories/VersionableRepositoryBase.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index c53937473d..a614f70f58 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -532,6 +532,10 @@ WHERE EXISTS( case "OWNER": //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter return "umbracoNode.nodeUser"; + + // Members only + case "USERNAME": + return "cmsMember.LoginName"; default: return orderBy; } From b9ff327536cf56ecc586aae2c330c4502d0e0f10 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Sat, 13 Dec 2014 22:29:32 +0100 Subject: [PATCH 003/101] Removed ability to sort member list view system fields other than those on the base table (others aren't handled so error) --- .../views/propertyeditors/listview/listview.controller.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index f83a218a2e..63da7b54bf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -82,6 +82,11 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific e.allowSorting = true; } + // Another special case for members, only fields on the base table (cmsMember) can be used for sorting + if (e.isSystem && $scope.entityType == "member") { + e.allowSorting = e.alias == 'username' || e.alias == 'email'; + } + if (e.isSystem) { //localize the header var key = getLocalizedKey(e.alias); From 58f2694a9014af7b057272a3bdbc9404aab827c4 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Sat, 20 Dec 2014 22:05:43 +0100 Subject: [PATCH 004/101] Removed option to sort by media last edited date as currently only base table fields can be sorted and this isn't one. Trying to do so currently triggers an error. --- .../views/propertyeditors/listview/listview.controller.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 63da7b54bf..01e20eb6af 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -82,9 +82,14 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific e.allowSorting = true; } + // Another special case for lasted edited data/update date for media, again this field isn't available on the base table so we can't sort by it + if (e.isSystem && $scope.entityType == "media") { + e.allowSorting = e.alias != 'updateDate'; + } + // Another special case for members, only fields on the base table (cmsMember) can be used for sorting if (e.isSystem && $scope.entityType == "member") { - e.allowSorting = e.alias == 'username' || e.alias == 'email'; + e.allowSorting = e.alias == 'username' || e.alias == 'email'; } if (e.isSystem) { From a1ae0f70bb6673305bf289e221aa95abd133d9f9 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Thu, 11 Jun 2015 23:46:27 +0200 Subject: [PATCH 005/101] Moved custom column list view order by sql variations into syntax providers --- .../Repositories/VersionableRepositoryBase.cs | 18 +++++++++++++----- .../SqlSyntax/ISqlSyntaxProvider.cs | 4 ++++ .../SqlSyntax/MySqlSyntaxProvider.cs | 4 ++++ .../SqlSyntax/SqlSyntaxProviderBase.cs | 4 ++++ 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index a614f70f58..ff6b0d81b3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -19,6 +19,8 @@ using Umbraco.Core.Dynamics; namespace Umbraco.Core.Persistence.Repositories { + using SqlSyntax; + internal abstract class VersionableRepositoryBase : PetaPocoRepositoryBase where TEntity : class, IAggregateRoot { @@ -263,11 +265,15 @@ namespace Umbraco.Core.Persistence.Repositories { // Sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through valie // from most recent content version for the given order by field - sortedSql.Append(@"ORDER BY ( + var sortedInt = string.Format(SqlSyntaxContext.SqlSyntaxProvider.ConvertIntegerToOrderableString, "dataInt"); + var sortedDate = string.Format(SqlSyntaxContext.SqlSyntaxProvider.ConvertIntegerToOrderableString, "dataDate"); + var sortedString = string.Format(SqlSyntaxContext.SqlSyntaxProvider.IsNull, "dataNvarchar", "''"); + + var orderBySql = string.Format(@"ORDER BY ( SELECT CASE - WHEN dataInt Is Not Null THEN RIGHT('00000000' + CAST(dataInt AS varchar(8)),8) - WHEN dataDate Is Not Null THEN CONVERT(varchar, dataDate, 102) - ELSE IsNull(dataNvarchar,'') + WHEN dataInt Is Not Null THEN {0} + WHEN dataDate Is Not Null THEN {1} + ELSE {2} END FROM cmsContent c INNER JOIN cmsContentVersion cv ON cv.ContentId = c.nodeId AND VersionDate = ( @@ -279,7 +285,9 @@ namespace Umbraco.Core.Persistence.Repositories INNER JOIN cmsPropertyData cpd ON cpd.contentNodeId = c.nodeId AND cpd.versionId = cv.VersionId AND cpd.propertytypeId = cpt.id - WHERE c.nodeId = umbracoNode.Id and cpt.Alias = @0)", orderBy); + WHERE c.nodeId = umbracoNode.Id and cpt.Alias = @0)", sortedInt, sortedDate, sortedString); + + sortedSql.Append(orderBySql, orderBy); if (orderDirection == Direction.Descending) { sortedSql.Append(" DESC"); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index 899a462172..8ad8eb0d7a 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -69,6 +69,10 @@ namespace Umbraco.Core.Persistence.SqlSyntax bool SupportsIdentityInsert(); bool? SupportsCaseInsensitiveQueries(Database db); + string IsNull { get; } + string ConvertIntegerToOrderableString { get; } + string ConvertDateToOrderableString { get; } + IEnumerable GetTablesInSchema(Database db); IEnumerable GetColumnsInSchema(Database db); IEnumerable> GetConstraintsPerTable(Database db); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index ba6f4dcc7e..bdd2a2ef23 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -342,6 +342,10 @@ ORDER BY TABLE_NAME, INDEX_NAME", public override string RenameColumn { get { return "ALTER TABLE {0} CHANGE {1} {2}"; } } + public override string IsNull { get { return "IFNULL({0},{1})"; } } + public override string ConvertIntegerToOrderableString { get { return "LPAD({0}, 8, '0')"; } } + public override string ConvertDateToOrderableString { get { return "DATE_FORMAT({0}, '%Y%m%d')"; } } + public override bool? SupportsCaseInsensitiveQueries(Database db) { bool? supportsCaseInsensitiveQueries = null; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index 08d85e0573..81a7750942 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -501,5 +501,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax public virtual string CreateConstraint { get { return "ALTER TABLE {0} ADD CONSTRAINT {1} {2} ({3})"; } } public virtual string DeleteConstraint { get { return "ALTER TABLE {0} DROP CONSTRAINT {1}"; } } public virtual string CreateForeignKeyConstraint { get { return "ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4}){5}{6}"; } } + + public virtual string IsNull { get { return "ISNULL({0},{1})"; } } + public virtual string ConvertIntegerToOrderableString { get { return "RIGHT('00000000' + CAST({0} AS varchar(8)),8)"; } } + public virtual string ConvertDateToOrderableString { get { return "CONVERT(varchar, {0}, 102)"; } } } } \ No newline at end of file From 7008d6b2fb6ea2c12f1e7100af303affbcc21f3b Mon Sep 17 00:00:00 2001 From: AndyButland Date: Sat, 13 Jun 2015 00:15:51 +0200 Subject: [PATCH 006/101] Fix to issue with ordering by custom date field --- .../Persistence/Repositories/VersionableRepositoryBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index ff6b0d81b3..9047df31f7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -266,7 +266,7 @@ namespace Umbraco.Core.Persistence.Repositories // Sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through valie // from most recent content version for the given order by field var sortedInt = string.Format(SqlSyntaxContext.SqlSyntaxProvider.ConvertIntegerToOrderableString, "dataInt"); - var sortedDate = string.Format(SqlSyntaxContext.SqlSyntaxProvider.ConvertIntegerToOrderableString, "dataDate"); + var sortedDate = string.Format(SqlSyntaxContext.SqlSyntaxProvider.ConvertDateToOrderableString, "dataDate"); var sortedString = string.Format(SqlSyntaxContext.SqlSyntaxProvider.IsNull, "dataNvarchar", "''"); var orderBySql = string.Format(@"ORDER BY ( From af244917f6b77c57f19de08c1e7ce25f708b323d Mon Sep 17 00:00:00 2001 From: AndyButland Date: Sun, 14 Jun 2015 11:24:03 +0200 Subject: [PATCH 007/101] Fixed issue with ordering by custom property from inherited or composed doc type --- .../Persistence/Repositories/VersionableRepositoryBase.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 9047df31f7..2b36853966 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -281,10 +281,9 @@ namespace Umbraco.Core.Persistence.Repositories FROM cmsContentVersion WHERE ContentId = c.nodeId ) - INNER JOIN cmsPropertyType cpt ON cpt.contentTypeId = c.contentType INNER JOIN cmsPropertyData cpd ON cpd.contentNodeId = c.nodeId AND cpd.versionId = cv.VersionId - AND cpd.propertytypeId = cpt.id + INNER JOIN cmsPropertyType cpt ON cpt.Id = cpd.propertytypeId WHERE c.nodeId = umbracoNode.Id and cpt.Alias = @0)", sortedInt, sortedDate, sortedString); sortedSql.Append(orderBySql, orderBy); From 893ca14859586425429271117a6f34279bd62406 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Thu, 28 Jan 2016 01:01:06 +0100 Subject: [PATCH 008/101] Config for max items --- .../relatedlinks/relatedlinks.controller.js | 28 +++++++++- .../relatedlinks/relatedlinks.html | 52 +++++++++---------- .../RelatedLinksPropertyEditor.cs | 10 ++++ 3 files changed, 60 insertions(+), 30 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js index 26d9768c29..0504378434 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js @@ -5,6 +5,8 @@ if (!$scope.model.value) { $scope.model.value = []; } + + $scope.maxNumber = isNumeric($scope.model.config.maxNumber) && $scope.model.config.maxNumber !== 0 ? $scope.model.config.maxNumber : Number.MAX_VALUE; $scope.newCaption = ''; $scope.newLink = 'http://'; @@ -141,9 +143,22 @@ $scope.model.value[index + direction] = temp; }; + //helper for determining if a user can add items + $scope.canAdd = function () { + return $scope.model.config.maxNumber > countVisible(); + } + + //helper that returns if an item can be sorted + $scope.canSort = function () { + return countVisible() > 1; + } + $scope.sortableOptions = { - containment: 'parent', + axis: 'y', + handle: '.handle', cursor: 'move', + cancel: '.no-drag', + containment: 'parent', helper: function (e, ui) { // When sorting , the cells collapse. This helper fixes that: http://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/ ui.children().each(function () { @@ -151,7 +166,7 @@ }); return ui; }, - items: '> tr', + items: '> tr:not(.unsortable)', tolerance: 'pointer', update: function (e, ui) { // Get the new and old index for the moved element (using the URL as the identifier) @@ -166,6 +181,15 @@ } }; + //helper to count what is visible + function countVisible() { + return $scope.model.value.length; + } + + function isNumeric(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + } + function getElementIndexByUrl(url) { for (var i = 0; i < $scope.model.value.length; i++) { if ($scope.model.value[i].link == url) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html index a5eae94491..126982267f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html @@ -11,8 +11,8 @@ - - + + {{link.caption}} @@ -20,9 +20,7 @@
@@ -41,40 +39,38 @@ -
-
+
- - - - - + + + + - - - - -
- -
- + + + + +
+ +
+ @@ -86,4 +82,4 @@ position="right"> -
+
\ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs index 3009b39518..6001d9c54e 100644 --- a/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs @@ -11,5 +11,15 @@ namespace Umbraco.Web.PropertyEditors [PropertyEditor(Constants.PropertyEditors.RelatedLinksAlias, "Related links", "relatedlinks", ValueType ="JSON", Icon="icon-thumbnail-list", Group="pickers")] public class RelatedLinksPropertyEditor : PropertyEditor { + //protected override PreValueEditor CreatePreValueEditor() + //{ + // return new RelatedLinksPreValueEditor(); + //} + + //internal class RelatedLinksPreValueEditor : PreValueEditor + //{ + // [PreValueField("maxNumber", "Maximum number of items", "number")] + // public int MaxNumber { get; set; } + //} } } From 7cf4c33d1541e1ad8536c14de30f78d9d4621120 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Mon, 1 Feb 2016 01:52:51 +0100 Subject: [PATCH 009/101] Fix max number in config and other adjustments --- .../src/less/listview.less | 3 ++- .../src/less/property-editors.less | 16 +++++++++++++++- .../relatedlinks/relatedlinks.controller.js | 4 ++-- .../relatedlinks/relatedlinks.html | 10 +++++----- .../RelatedLinksPropertyEditor.cs | 18 +++++++++--------- 5 files changed, 33 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/listview.less b/src/Umbraco.Web.UI.Client/src/less/listview.less index b6efa9567f..87d5f9a5e2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/listview.less +++ b/src/Umbraco.Web.UI.Client/src/less/listview.less @@ -243,7 +243,8 @@ background-color: @grayLighter } -.table-striped tbody i:hover { +/* don't hide all icons, e.g. for a sortable handle */ +.table-striped tbody i:not(.handle):hover { display: none !important } diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index b3cb034596..5033dacb2d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -620,7 +620,21 @@ ul.color-picker li a { } // -// tags +// Related links +// -------------------------------------------------- +.umb-relatedlinks table > tr > td { word-wrap:break-word; word-break: break-all; } +.umb-relatedlinks .handle { cursor:move; } +.umb-relatedlinks table > tbody > tr.unsortable .handle { cursor:default; } + +/* sortable placeholder */ +.umb-relatedlinks .sortable-placeholder { + //background-color: #fff; + margin-bottom: 47px; +} + + +// +// Tags // -------------------------------------------------- .umb-tags{border: @grayLighter solid 1px; padding: 10px; font-size: 13px; text-shadow: none;} .umb-tags .tag{cursor: pointer; margin: 7px; padding: 7px; background: @blue} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js index 0504378434..af7ea0cb24 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js @@ -6,7 +6,7 @@ $scope.model.value = []; } - $scope.maxNumber = isNumeric($scope.model.config.maxNumber) && $scope.model.config.maxNumber !== 0 ? $scope.model.config.maxNumber : Number.MAX_VALUE; + $scope.model.config.maxNumber = isNumeric($scope.model.config.maxNumber) && $scope.model.config.maxNumber !== 0 ? $scope.model.config.maxNumber : Number.MAX_VALUE; $scope.newCaption = ''; $scope.newLink = 'http://'; @@ -160,7 +160,7 @@ cancel: '.no-drag', containment: 'parent', helper: function (e, ui) { - // When sorting , the cells collapse. This helper fixes that: http://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/ + // When sorting table rows, the cells collapse. This helper fixes that: http://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/ ui.children().each(function () { $(this).width($(this).width()); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html index 126982267f..f5f49881d0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html @@ -13,11 +13,11 @@ - + {{link.caption}} - +
{{link.link}} @@ -49,10 +49,10 @@ - + - +
@@ -68,7 +68,7 @@
- +
diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs index 6001d9c54e..751774eeba 100644 --- a/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs @@ -11,15 +11,15 @@ namespace Umbraco.Web.PropertyEditors [PropertyEditor(Constants.PropertyEditors.RelatedLinksAlias, "Related links", "relatedlinks", ValueType ="JSON", Icon="icon-thumbnail-list", Group="pickers")] public class RelatedLinksPropertyEditor : PropertyEditor { - //protected override PreValueEditor CreatePreValueEditor() - //{ - // return new RelatedLinksPreValueEditor(); - //} + protected override PreValueEditor CreatePreValueEditor() + { + return new RelatedLinksPreValueEditor(); + } - //internal class RelatedLinksPreValueEditor : PreValueEditor - //{ - // [PreValueField("maxNumber", "Maximum number of items", "number")] - // public int MaxNumber { get; set; } - //} + internal class RelatedLinksPreValueEditor : PreValueEditor + { + [PreValueField("maxNumber", "Maximum number of items", "number")] + public int MaxNumber { get; set; } + } } } From 6f078989797516f59b25b08a014ddf2c69f5d221 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Mon, 1 Feb 2016 02:13:05 +0100 Subject: [PATCH 010/101] Max config adjustments --- .../propertyeditors/relatedlinks/relatedlinks.controller.js | 4 ++-- src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js index af7ea0cb24..463c635570 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js @@ -6,7 +6,7 @@ $scope.model.value = []; } - $scope.model.config.maxNumber = isNumeric($scope.model.config.maxNumber) && $scope.model.config.maxNumber !== 0 ? $scope.model.config.maxNumber : Number.MAX_VALUE; + $scope.model.config.max = isNumeric($scope.model.config.max) && $scope.model.config.max !== 0 ? $scope.model.config.max : Number.MAX_VALUE; $scope.newCaption = ''; $scope.newLink = 'http://'; @@ -145,7 +145,7 @@ //helper for determining if a user can add items $scope.canAdd = function () { - return $scope.model.config.maxNumber > countVisible(); + return $scope.model.config.max <= 0 || $scope.model.config.max > countVisible(); } //helper that returns if an item can be sorted diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs index 751774eeba..732f478a80 100644 --- a/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.PropertyEditors internal class RelatedLinksPreValueEditor : PreValueEditor { - [PreValueField("maxNumber", "Maximum number of items", "number")] + [PreValueField("max", "Maximum number of links", "number", Description = "Enter the maximum amount of links to be added, enter 0 for unlimited")] public int MaxNumber { get; set; } } } From 6d4364a8b9b7fe38f5c349b7fc9226dfa46f0ca6 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Tue, 2 Feb 2016 03:47:10 +0100 Subject: [PATCH 011/101] Modify sortable table row --- .../src/less/property-editors.less | 12 ++++++++-- .../relatedlinks/relatedlinks.controller.js | 23 +++++++++++++++++++ .../relatedlinks/relatedlinks.html | 1 + 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 5033dacb2d..f67b1d804c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -628,8 +628,16 @@ ul.color-picker li a { /* sortable placeholder */ .umb-relatedlinks .sortable-placeholder { - //background-color: #fff; - margin-bottom: 47px; + background-color: #efefef; + display: table-row; +} +.umb-relatedlinks .sortable-placeholder > td { + display: table-cell; + padding: 8px; +} +.umb-relatedlinks .ui-sortable-helper { + background-color: #fff; + opacity: 0.5; } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js index 463c635570..cc13dd33c9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js @@ -75,6 +75,9 @@ $scope.model.value[idx].edit = true; }; + $scope.cancelEdit = function (idx) { + $scope.model.value[idx].edit = false; + }; $scope.saveEdit = function (idx) { $scope.model.value[idx].title = $scope.model.value[idx].caption; @@ -159,6 +162,8 @@ cursor: 'move', cancel: '.no-drag', containment: 'parent', + placeholder: 'sortable-placeholder', + forcePlaceholderSize: true, helper: function (e, ui) { // When sorting table rows, the cells collapse. This helper fixes that: http://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/ ui.children().each(function () { @@ -178,6 +183,24 @@ var movedElement = $scope.model.value[originalIndex]; $scope.model.value.splice(originalIndex, 1); $scope.model.value.splice(newIndex, 0, movedElement); + }, + start: function (e, ui) { + //ui.placeholder.html(""); + + // Build a placeholder cell that spans all the cells in the row: http://stackoverflow.com/questions/25845310/jquery-ui-sortable-and-table-cell-size + var cellCount = 0; + $('td, th', ui.helper).each(function () { + // For each td or th try and get it's colspan attribute, and add that or 1 to the total + var colspan = 1; + var colspanAttr = $(this).attr('colspan'); + if (colspanAttr > 1) { + colspan = colspanAttr; + } + cellCount += colspan; + }); + + // Add the placeholder UI - note that this is the item's content, so td rather than tr + ui.placeholder.html(' '); } }; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html index f5f49881d0..578d6a576a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html @@ -45,6 +45,7 @@
+
From b8c7b09d25222b5746e5f6e293c5d1ecee8b33c0 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Wed, 3 Feb 2016 02:29:21 +0100 Subject: [PATCH 012/101] Update styles + adjust sort of table row --- .../src/less/property-editors.less | 24 +++++++++-- .../relatedlinks/relatedlinks.controller.js | 8 +--- .../relatedlinks/relatedlinks.html | 43 +++++++++++-------- 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index f67b1d804c..d99c35f485 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -622,13 +622,24 @@ ul.color-picker li a { // // Related links // -------------------------------------------------- -.umb-relatedlinks table > tr > td { word-wrap:break-word; word-break: break-all; } +.umb-relatedlinks table > tr > td { word-wrap:break-word; word-break: break-all; border-bottom: 1px solid transparent; } .umb-relatedlinks .handle { cursor:move; } .umb-relatedlinks table > tbody > tr.unsortable .handle { cursor:default; } +.umb-relatedlinks table td.col-sort { width: 20px; } +.umb-relatedlinks table td.col-caption { min-width: 200px; } +.umb-relatedlinks table td.col-link { min-width: 200px; } +.umb-relatedlinks table td.col-actions { min-width: 120px; } + +.umb-relatedlinks table td.col-caption .control-wrapper, +.umb-relatedlinks table td.col-link .control-wrapper { display: flex; } + +.umb-relatedlinks table td.col-caption .control-wrapper input[type="text"], +.umb-relatedlinks table td.col-link .control-wrapper input[type="text"] { width: auto; flex: 1; } + /* sortable placeholder */ .umb-relatedlinks .sortable-placeholder { - background-color: #efefef; + background-color: @tableBackgroundAccent; display: table-row; } .umb-relatedlinks .sortable-placeholder > td { @@ -636,8 +647,13 @@ ul.color-picker li a { padding: 8px; } .umb-relatedlinks .ui-sortable-helper { - background-color: #fff; - opacity: 0.5; + display: table-row; + background-color: @white; + opacity: 0.7; +} +.umb-relatedlinks .ui-sortable-helper > td { + display: table-cell; + border-bottom: 1px solid @tableBorder; } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js index cc13dd33c9..01b465daba 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js @@ -74,10 +74,6 @@ } $scope.model.value[idx].edit = true; }; - - $scope.cancelEdit = function (idx) { - $scope.model.value[idx].edit = false; - }; $scope.saveEdit = function (idx) { $scope.model.value[idx].title = $scope.model.value[idx].caption; @@ -199,8 +195,8 @@ cellCount += colspan; }); - // Add the placeholder UI - note that this is the item's content, so td rather than tr - ui.placeholder.html(' '); + // Add the placeholder UI - note that this is the item's content, so td rather than tr - and set height of tr + ui.placeholder.html('').height(ui.item.height()); } }; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html index 578d6a576a..17358e0628 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html @@ -12,21 +12,25 @@ - - + + {{link.caption}} - +
+ +
- +
Choose
@@ -34,30 +38,35 @@
- + {{link.newWindow}} - +
-
- - - + + +
+ +
+ +
- -
or +
+ +
+ or choose internal page
@@ -66,8 +75,8 @@ or enter external link
- - + +
From 4bcee1df711bad2208d9840cd17f0ffa691c8c35 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Wed, 3 Feb 2016 02:42:47 +0100 Subject: [PATCH 013/101] Change property to maximum --- src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs index 732f478a80..1fc4d7f471 100644 --- a/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.PropertyEditors internal class RelatedLinksPreValueEditor : PreValueEditor { [PreValueField("max", "Maximum number of links", "number", Description = "Enter the maximum amount of links to be added, enter 0 for unlimited")] - public int MaxNumber { get; set; } + public int Maximum { get; set; } } } } From 87667ec2d1ce61cd78c6b06f0f6a1861104c13d6 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 8 Mar 2016 20:56:24 +0100 Subject: [PATCH 014/101] Fixes: U4-8016 Pick only images from media --- .../components/umbmediagrid.directive.js | 10 +++++++++- .../common/overlays/mediaPicker/mediapicker.html | 3 ++- .../mediapicker/mediapicker.controller.js | 15 ++++++++------- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index eed0cffadb..70cbb6d9c3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -34,6 +34,13 @@ var item = scope.items[i]; setItemData(item); setOriginalSize(item, itemMaxHeight); + + // remove non images when onlyImages is set to true + if(scope.onlyImages === "true" && !item.isFolder && !item.thumbnail){ + scope.items.splice(i, 1); + i--; + } + } if (scope.items.length > 0) { @@ -181,7 +188,8 @@ itemMaxWidth: "@", itemMaxHeight: "@", itemMinWidth: "@", - itemMinHeight: "@" + itemMinHeight: "@", + onlyImages: "@" }, link: link }; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html index 5bcbfbb3d2..51f05fea00 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html @@ -80,7 +80,8 @@ item-max-width="150" item-max-height="150" item-min-width="100" - item-min-height="100"> + item-min-height="100" + only-images={{onlyImages}}> diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js index bf82efae24..9969af937d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js @@ -12,10 +12,10 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl $scope.model.config.startNodeId = userData.startMediaId; }); } - + function setupViewModel() { $scope.images = []; - $scope.ids = []; + $scope.ids = []; if ($scope.model.value) { var ids = $scope.model.value.split(','); @@ -30,16 +30,16 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl entityResource.getByIds(ids, "Media").then(function (medias) { _.each(medias, function (media, i) { - + //only show non-trashed items if (media.parentId >= -1) { - if (!media.thumbnail) { + if (!media.thumbnail) { media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); } $scope.images.push(media); - $scope.ids.push(media.id); + $scope.ids.push(media.id); } }); @@ -67,6 +67,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl title: "Select media", startNodeId: $scope.model.config.startNodeId, multiPicker: multiPicker, + onlyImages: onlyImages, show: true, submit: function(model) { @@ -93,8 +94,8 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl $scope.sortableOptions = { update: function(e, ui) { var r = []; - //TODO: Instead of doing this with a half second delay would be better to use a watch like we do in the - // content picker. THen we don't have to worry about setting ids, render models, models, we just set one and let the + //TODO: Instead of doing this with a half second delay would be better to use a watch like we do in the + // content picker. THen we don't have to worry about setting ids, render models, models, we just set one and let the // watch do all the rest. $timeout(function(){ angular.forEach($scope.images, function(value, key){ From effe7c1669b664e5021504853e3981f7d9c00eac Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 10 Mar 2016 11:20:44 +0100 Subject: [PATCH 015/101] Fixes unit test --- src/Umbraco.Tests/Plugins/TypeFinderTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Tests/Plugins/TypeFinderTests.cs b/src/Umbraco.Tests/Plugins/TypeFinderTests.cs index 906027086d..50ab053668 100644 --- a/src/Umbraco.Tests/Plugins/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Plugins/TypeFinderTests.cs @@ -79,8 +79,8 @@ namespace Umbraco.Tests.Plugins var originalTypesFound = TypeFinderOriginal.FindClassesOfType(_assemblies); Assert.AreEqual(originalTypesFound.Count(), typesFound.Count()); - Assert.AreEqual(8, typesFound.Count()); - Assert.AreEqual(8, originalTypesFound.Count()); + Assert.AreEqual(9, typesFound.Count()); + Assert.AreEqual(9, originalTypesFound.Count()); } [Test] From bfe9bd5a202d7078db08f0df9bc7b7a86361f4fc Mon Sep 17 00:00:00 2001 From: sna Date: Mon, 21 Mar 2016 21:15:31 +0000 Subject: [PATCH 016/101] U4-8199 - Remove the obsolete EncodedStringWriter.cs No references, clean build - looks safe to remove http://issues.umbraco.org/issue/U4-8199 --- src/Umbraco.Web/Umbraco.Web.csproj | 1 - .../_Legacy/Utils/EncodedStringWriter.cs | 42 ------------------- 2 files changed, 43 deletions(-) delete mode 100644 src/Umbraco.Web/_Legacy/Utils/EncodedStringWriter.cs diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c16363d3a6..ec73662bec 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1704,7 +1704,6 @@ - Mvc\web.config diff --git a/src/Umbraco.Web/_Legacy/Utils/EncodedStringWriter.cs b/src/Umbraco.Web/_Legacy/Utils/EncodedStringWriter.cs deleted file mode 100644 index 94699f5687..0000000000 --- a/src/Umbraco.Web/_Legacy/Utils/EncodedStringWriter.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.IO; -using System.Text; - -namespace Umbraco.Web._Legacy.Utils -{ - - /// - /// A TextWriter class based on the StringWriter that can support any encoding, not just UTF-16 - /// as is the default of the normal StringWriter class - /// - [Obsolete("Remove this for v8")] - internal class EncodedStringWriter : StringWriter - { - - /// - /// Initializes a new instance of the class. - /// - /// The sb. - /// The enc. - public EncodedStringWriter(StringBuilder sb, Encoding enc) - : base(sb) - { - m_encoding = enc; - } - - private Encoding m_encoding; - - /// - /// Gets the in which the output is written. - /// - /// - /// The Encoding in which the output is written. - public override Encoding Encoding - { - get - { - return m_encoding; - } - } - } -} From afc312612513b0bca6b38aec434d767f16d2f5bf Mon Sep 17 00:00:00 2001 From: sna Date: Mon, 21 Mar 2016 21:37:03 +0000 Subject: [PATCH 017/101] U4-8199 - Remove the JSONSerializer, where used for serialisation it's been replaced with JavaScriptSerializer Also removed references to JSONSerializer.ToJSONObject in \Umbraco-CMS\src\Umbraco.Web\umbraco.presentation\umbraco\controls\Tree\JTreeContextMenuItem.cs as it looks like this is just used by the jsTree which will be removed in a future commit. http://issues.umbraco.org/issue/U4-8199 --- src/Umbraco.Web/Umbraco.Web.csproj | 1 - .../_Legacy/Utils/JSONSerializer.cs | 66 ------------------- .../umbraco/Trees/XmlTree.cs | 7 +- .../umbraco/controls/Tree/JTreeContextMenu.cs | 3 +- .../controls/Tree/JTreeContextMenuItem.cs | 7 -- 5 files changed, 4 insertions(+), 80 deletions(-) delete mode 100644 src/Umbraco.Web/_Legacy/Utils/JSONSerializer.cs diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index ec73662bec..383eee54fb 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1704,7 +1704,6 @@ - Mvc\web.config diff --git a/src/Umbraco.Web/_Legacy/Utils/JSONSerializer.cs b/src/Umbraco.Web/_Legacy/Utils/JSONSerializer.cs deleted file mode 100644 index f5eaf708a0..0000000000 --- a/src/Umbraco.Web/_Legacy/Utils/JSONSerializer.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Text.RegularExpressions; -using System.Web.Script.Serialization; - -namespace Umbraco.Web._Legacy.Utils -{ - /// - /// The built in JavaScriptSerializer does not allow you to export real JavaScript - /// objects, functions, etc... only string values which isn't always what you want. - /// See - /// - /// Override the JavaScriptSerializer serialization process and look for any - /// custom "tags" strings such as a @ symbol which depicts that the string value - /// should really be a JSON value, therefore the output removes the double quotes. - /// - /// - /// - /// If you want to output: - /// {"myFunction": function() {alert('hello');}} - /// The JavaScriptSerializer will not let you do this, it will render: - /// {"myFunction": "function() {alert('hello');}"} - /// which means that JavaScript will interpret it as a string. - /// This class allows you to output JavaScript objects, amongst other things. - /// - [Obsolete("Remove this for v8")] - internal class JSONSerializer : JavaScriptSerializer - { - - public new string Serialize(object obj) - { - string output = base.Serialize(obj); - - //replaces all strings beginning with this prefix to have no double quotes - Regex regex1 = new Regex(string.Format("(\"{0}(.*?)\")+", PrefixJavaScriptObject), - RegexOptions.Multiline - | RegexOptions.CultureInvariant - | RegexOptions.Compiled - ); - string result = regex1.Replace(output, "$2"); - - return result; - } - - private const string PrefixJavaScriptObject = "@@@@"; - - /// - /// method for a string to be converted to a json object. - /// - /// - /// A string formatted with a special prefix - /// - /// This essentially just prefixes the string with a special key that we will use - /// to parse with later during serialization. - /// - public static string ToJSONObject(string s) - { - return PrefixJavaScriptObject + s; - } - - - } - - - -} - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/XmlTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/XmlTree.cs index def2bbb9b0..c6dfd4c758 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/XmlTree.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/XmlTree.cs @@ -7,7 +7,6 @@ using System.Web.Script.Serialization; using Umbraco.Core.IO; using Umbraco.Web.UI.Pages; using Umbraco.Web._Legacy.Actions; -using Umbraco.Web._Legacy.Utils; using Action = Umbraco.Web._Legacy.Actions.Action; namespace umbraco.cms.presentation.Trees @@ -48,7 +47,7 @@ namespace umbraco.cms.presentation.Trees private void Init() { - m_JSSerializer = new JSONSerializer { MaxJsonLength = int.MaxValue }; + m_JSSerializer = new JavaScriptSerializer { MaxJsonLength = int.MaxValue }; switch (m_TreeType) { @@ -70,7 +69,7 @@ namespace umbraco.cms.presentation.Trees } - private JSONSerializer m_JSSerializer; + private JavaScriptSerializer m_JSSerializer; private SerializedTreeType m_TreeType; /// @@ -717,7 +716,7 @@ namespace umbraco.cms.presentation.Trees metadata.Add("source", node.Source); //the metadata/jsTree requires this property to be in a quoted JSON syntax - JSONSerializer jsSerializer = new JSONSerializer(); + JavaScriptSerializer jsSerializer = new JavaScriptSerializer(); string strMetaData = jsSerializer.Serialize(metadata).Replace("\"", "'"); dataAttributes.Add("umb:nodedata", strMetaData); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenu.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenu.cs index 8935aa9bdf..cfaf6bb2cd 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenu.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenu.cs @@ -4,7 +4,6 @@ using System.Web.Script.Serialization; using Umbraco.Core.Logging; using Umbraco.Core; using Umbraco.Web._Legacy.Actions; -using Umbraco.Web._Legacy.Utils; using Action = Umbraco.Web._Legacy.Actions.Action; namespace umbraco.controls.Tree @@ -14,7 +13,7 @@ namespace umbraco.controls.Tree public string RenderJSONMenu() { - JSONSerializer jSSerializer = new JSONSerializer(); + JavaScriptSerializer jSSerializer = new JavaScriptSerializer(); jSSerializer.RegisterConverters(new List() { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenuItem.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenuItem.cs index f49bc75a50..c624c429c2 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenuItem.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/Tree/JTreeContextMenuItem.cs @@ -5,7 +5,6 @@ using System.Text; using Umbraco.Core; using Umbraco.Core.Services; using Umbraco.Web._Legacy.Actions; -using Umbraco.Web._Legacy.Utils; namespace umbraco.controls.Tree { @@ -56,12 +55,6 @@ namespace umbraco.controls.Tree data.Add("icon", a.Icon); } - //required by jsTree - data.Add("visible", JSONSerializer.ToJSONObject("function() {return true;}")); - - //The action handler is what is assigned to the IAction, but for flexibility, we'll call our onContextMenuSelect method which will need to return true if the function is to execute. - data.Add("action", JSONSerializer.ToJSONObject("function(N,T){" + a.JsFunctionName + ";}")); - return data; } From a04506ba603e12848da686d96f02b37756b8cce5 Mon Sep 17 00:00:00 2001 From: James Coxhead Date: Mon, 21 Mar 2016 23:31:22 +0000 Subject: [PATCH 018/101] Created new dictionary tree controller --- src/Umbraco.Web.UI/config/trees.config | 2 +- .../umbraco/config/create/UI.xml | 9 +- .../Trees/DictionaryTreeController.cs | 124 ++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + .../settings/EditDictionaryItem.aspx.cs | 5 +- 5 files changed, 130 insertions(+), 11 deletions(-) create mode 100644 src/Umbraco.Web/Trees/DictionaryTreeController.cs diff --git a/src/Umbraco.Web.UI/config/trees.config b/src/Umbraco.Web.UI/config/trees.config index 42bc28239b..4093d87479 100644 --- a/src/Umbraco.Web.UI/config/trees.config +++ b/src/Umbraco.Web.UI/config/trees.config @@ -14,7 +14,7 @@ - + diff --git a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml index f6859c6423..70df900af2 100644 --- a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml +++ b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml @@ -101,14 +101,7 @@ - -
Dictionary editor egenskab
- /create/simple.ascx - - - -
- +
Dictionary editor egenskab
/create/simple.ascx diff --git a/src/Umbraco.Web/Trees/DictionaryTreeController.cs b/src/Umbraco.Web/Trees/DictionaryTreeController.cs new file mode 100644 index 0000000000..dec233a61e --- /dev/null +++ b/src/Umbraco.Web/Trees/DictionaryTreeController.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Formatting; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.WebApi.Filters; +using Umbraco.Web._Legacy.Actions; + +namespace Umbraco.Web.Trees +{ + [UmbracoTreeAuthorize(Constants.Trees.Dictionary)] + [Tree(Constants.Applications.Settings, Constants.Trees.Dictionary, null, sortOrder: 3)] + [Mvc.PluginController("UmbracoTrees")] + [CoreTree] + public class DictionaryTreeController : TreeController + { + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var node = base.CreateRootNode(queryStrings); + + // For now, this is using the legacy webforms view but will need refactoring + // when the dictionary has been converted to Angular. + node.RoutePath = String.Format("{0}/framed/{1}", queryStrings.GetValue("application"), + Uri.EscapeDataString("settings/DictionaryItemList.aspx")); + + return node; + } + + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var intId = ValidateId(id); + + var nodes = new TreeNodeCollection(); + nodes.AddRange(GetDictionaryItems(intId) + .OrderBy(dictionaryItem => dictionaryItem.ItemKey) + .Select(dictionaryItem => CreateTreeNode(id, queryStrings, dictionaryItem))); + + return nodes; + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var intId = ValidateId(id); + + var menu = new MenuItemCollection(); + + if (intId == Constants.System.Root) + { + // Again, menu actions will need to use legacy views as this section hasn't been converted to Angular (yet!) + menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)) + .ConvertLegacyMenuItem(null, "dictionary", queryStrings.GetValue("application")); + + menu.Items.Add( + Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); + } + else + { + + var dictionaryItem = Services.LocalizationService.GetDictionaryItemById(intId); + var entity = new UmbracoEntity + { + Id = dictionaryItem.Id, + Level = 1, + ParentId = -1, + Name = dictionaryItem.ItemKey + }; + + menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)) + .ConvertLegacyMenuItem(entity, "dictionary", queryStrings.GetValue("application")); + + menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias)) + .ConvertLegacyMenuItem(null, "dictionary", queryStrings.GetValue("application")); + + menu.Items.Add( + Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); + } + + return menu; + } + + private IEnumerable GetDictionaryItems(int id) + { + if (id > Constants.System.Root) + { + var dictionaryItem = Services.LocalizationService.GetDictionaryItemById(id); + + if (dictionaryItem != null) + { + return Services.LocalizationService.GetDictionaryItemChildren(dictionaryItem.Key); + } + } + + return Services.LocalizationService.GetRootDictionaryItems(); + } + + private TreeNode CreateTreeNode(string id, FormDataCollection queryStrings, IDictionaryItem dictionaryItem) + { + var hasChildren = Services.LocalizationService.GetDictionaryItemChildren(dictionaryItem.Key).Any(); + + // Again, menu actions will need to use legacy views as this section hasn't been converted to Angular (yet!) + var node = CreateTreeNode(dictionaryItem.Id.ToInvariantString(), id, queryStrings, dictionaryItem.ItemKey, + "icon-book-alt", hasChildren, + String.Format("{0}/framed/{1}", queryStrings.GetValue("application"), + Uri.EscapeDataString("settings/editDictionaryItem.aspx?id=" + + dictionaryItem.Id))); + + return node; + } + + private int ValidateId(string id) + { + var intId = id.TryConvertTo(); + if (intId == false) + { + throw new InvalidOperationException("Id must be an integer"); + } + + return intId.Result; + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 8a9160611d..404862c9d5 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -329,6 +329,7 @@ + diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditDictionaryItem.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditDictionaryItem.aspx.cs index b5bed0d759..2d63a6593b 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditDictionaryItem.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/EditDictionaryItem.aspx.cs @@ -4,6 +4,7 @@ using System.Web.UI.WebControls; using umbraco.cms.presentation.Trees; using Umbraco.Core; using Umbraco.Core.Services; +using Umbraco.Web; using Umbraco.Web.UI; namespace umbraco.settings @@ -61,8 +62,8 @@ namespace umbraco.settings { var path = BuildPath(currentItem); ClientTools - .SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) - .SyncTree(path, false); + .SetActiveTreeType(Constants.Trees.Dictionary) + .SyncTree(path, false); } From 88ad2cdc37b281212789eb0d69d5a63df164cce8 Mon Sep 17 00:00:00 2001 From: James Coxhead Date: Mon, 21 Mar 2016 23:32:56 +0000 Subject: [PATCH 019/101] Removed legacy loadDictionary class --- src/Umbraco.Web/Umbraco.Web.csproj | 1 - .../umbraco/Trees/loadDictionary.cs | 82 ------------------- 2 files changed, 83 deletions(-) delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadDictionary.cs diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 404862c9d5..7e52248d3c 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1577,7 +1577,6 @@ - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadDictionary.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadDictionary.cs deleted file mode 100644 index d6c82d0fbc..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadDictionary.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -using umbraco.cms.businesslogic; -using umbraco.cms.presentation.Trees; -using Umbraco.Core; -using Umbraco.Web.Trees; -using Umbraco.Web._Legacy.Actions; - - -namespace umbraco -{ - - [Tree(Constants.Applications.Settings, Constants.Trees.Dictionary, "Dictionary", sortOrder: 3)] - public class loadDictionary : BaseTree - { - public loadDictionary(string application) : base(application) { } - - protected override void CreateRootNode(ref XmlTreeNode rootNode) - { - rootNode.NodeType = "init" + TreeAlias; - rootNode.NodeID = "init"; - rootNode.Action = "javascript:openDictionary()"; - } - - protected override void CreateAllowedActions(ref List actions) - { - actions.Clear(); - actions.Add(ActionNew.Instance); - actions.Add(ActionDelete.Instance); - actions.Add(ContextMenuSeperator.Instance); - actions.Add(ActionRefresh.Instance); - } - - public override void RenderJS(ref StringBuilder Javascript) - { - Javascript.Append( - @" - function openDictionary() { - UmbClientMgr.contentFrame('settings/DictionaryItemList.aspx'); - } - function openDictionaryItem(id) { - UmbClientMgr.contentFrame('settings/editDictionaryItem.aspx?id=' + id); - }"); - } - - public override void Render(ref XmlTree tree) - { - - Dictionary.DictionaryItem[] tmp; - if (this.id == this.StartNodeID) - tmp = Dictionary.getTopMostItems; - else - tmp = new Dictionary.DictionaryItem(this.id).Children; - - foreach (Dictionary.DictionaryItem di in tmp.OrderBy(a => a.key)) - { - XmlTreeNode xNode = XmlTreeNode.Create(this); - xNode.NodeID = di.id.ToString(); //dictionary_ + id.. - xNode.Text = di.key; - xNode.Action = string.Format("javascript:openDictionaryItem({0});", di.id); - xNode.Icon = "icon-book-alt"; - xNode.NodeType = "DictionaryItem"; //this shouldn't be like this, it should be this.TreeAlias but the ui.config file points to this name. - xNode.Source = this.GetTreeServiceUrl(di.id); - xNode.HasChildren = di.hasChildren; - - OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty); - if (xNode != null) - { - tree.Add(xNode); - OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty); - } - - } - } - - - } - -} From b9af6f631a38696010a03a3efb8e211fd2abc5fa Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Mar 2016 11:23:09 +0100 Subject: [PATCH 020/101] U4-8220 ModelState and ViewData is not carried through when using the obsolete Content.GetGridHtml method --- ...tialViewMacroViewContextFilterAttribute.cs | 34 ++++++++++++++++--- .../Mvc/UmbracoViewPageOfTModel.cs | 10 ++++-- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web/Mvc/EnsurePartialViewMacroViewContextFilterAttribute.cs b/src/Umbraco.Web/Mvc/EnsurePartialViewMacroViewContextFilterAttribute.cs index afd40330ea..5be98f338b 100644 --- a/src/Umbraco.Web/Mvc/EnsurePartialViewMacroViewContextFilterAttribute.cs +++ b/src/Umbraco.Web/Mvc/EnsurePartialViewMacroViewContextFilterAttribute.cs @@ -21,20 +21,46 @@ namespace Umbraco.Web.Mvc /// internal class EnsurePartialViewMacroViewContextFilterAttribute : ActionFilterAttribute { + /// + /// Ensures the custom ViewContext datatoken is set before the RenderController action is invoked, + /// this ensures that any calls to GetPropertyValue with regards to RTE or Grid editors can still + /// render any PartialViewMacro with a form and maintain ModelState + /// + /// public override void OnActionExecuting(ActionExecutingContext filterContext) { //ignore anything that is not IRenderController if ((filterContext.Controller is IRenderController) == false) return; + SetViewContext(filterContext); + } + + /// + /// Ensures that the custom ViewContext datatoken is set after the RenderController action is invoked, + /// this ensures that any custom ModelState that may have been added in the RenderController itself is + /// passed onwards in case it is required when rendering a PartialViewMacro with a form + /// + /// The filter context. + public override void OnResultExecuting(ResultExecutingContext filterContext) + { + //ignore anything that is not IRenderController + if ((filterContext.Controller is IRenderController) == false) + return; + + SetViewContext(filterContext); + } + + private void SetViewContext(ControllerContext controllerContext) + { var viewCtx = new ViewContext( - filterContext.Controller.ControllerContext, - new DummyView(), - filterContext.Controller.ViewData, filterContext.Controller.TempData, + controllerContext, + new DummyView(), + controllerContext.Controller.ViewData, controllerContext.Controller.TempData, new StringWriter()); //set the special data token - filterContext.RequestContext.RouteData.DataTokens[Constants.DataTokenCurrentViewContext] = viewCtx; + controllerContext.RequestContext.RouteData.DataTokens[Constants.DataTokenCurrentViewContext] = viewCtx; } private class DummyView : IView diff --git a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs index 0c45c24c4a..3d1a41d289 100644 --- a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs +++ b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs @@ -120,9 +120,13 @@ namespace Umbraco.Web.Mvc base.InitializePage(); if (ViewContext.IsChildAction == false) { - //always ensure the special data token is set - this is used purely for partial view macros that contain forms - // and mostly just when rendered within the RTE - ViewContext.RouteData.DataTokens[Constants.DataTokenCurrentViewContext] = ViewContext; + //this is used purely for partial view macros that contain forms + // and mostly just when rendered within the RTE - This should already be set with the + // EnsurePartialViewMacroViewContextFilterAttribute + if (ViewContext.RouteData.DataTokens.ContainsKey(Constants.DataTokenCurrentViewContext) == false) + { + ViewContext.RouteData.DataTokens.Add(Constants.DataTokenCurrentViewContext, ViewContext); + } } } From 43e23829ae3affbccbb95e1ee4ca330949145336 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Mar 2016 10:06:13 +0100 Subject: [PATCH 021/101] updates EnsurePartialViewMacroViewContextFilterAttribute to check for child actions --- .../Mvc/EnsurePartialViewMacroViewContextFilterAttribute.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Mvc/EnsurePartialViewMacroViewContextFilterAttribute.cs b/src/Umbraco.Web/Mvc/EnsurePartialViewMacroViewContextFilterAttribute.cs index 5be98f338b..625b67b2b2 100644 --- a/src/Umbraco.Web/Mvc/EnsurePartialViewMacroViewContextFilterAttribute.cs +++ b/src/Umbraco.Web/Mvc/EnsurePartialViewMacroViewContextFilterAttribute.cs @@ -30,7 +30,7 @@ namespace Umbraco.Web.Mvc public override void OnActionExecuting(ActionExecutingContext filterContext) { //ignore anything that is not IRenderController - if ((filterContext.Controller is IRenderController) == false) + if ((filterContext.Controller is IRenderController) == false && filterContext.IsChildAction == false) return; SetViewContext(filterContext); @@ -45,7 +45,7 @@ namespace Umbraco.Web.Mvc public override void OnResultExecuting(ResultExecutingContext filterContext) { //ignore anything that is not IRenderController - if ((filterContext.Controller is IRenderController) == false) + if ((filterContext.Controller is IRenderController) == false && filterContext.IsChildAction == false) return; SetViewContext(filterContext); From bb7d5b4bd2c684bf74e52567d27eb02c486f7825 Mon Sep 17 00:00:00 2001 From: Yannis Guedel Date: Wed, 23 Mar 2016 22:56:42 +0100 Subject: [PATCH 022/101] U4-8193 replaced loadMacros with MacrosTreeController --- src/Umbraco.Core/Constants-Applications.cs | 2 + .../config/trees.Release.config | 2 +- src/Umbraco.Web.UI/config/trees.config | 2 +- src/Umbraco.Web/Trees/MacrosTreeController.cs | 88 +++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 2 +- .../umbraco/Trees/loadMacros.cs | 66 -------------- .../developer/Macros/editMacro.aspx.cs | 4 +- 7 files changed, 95 insertions(+), 71 deletions(-) create mode 100644 src/Umbraco.Web/Trees/MacrosTreeController.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMacros.cs diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index 38dc22c473..1c15083a81 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -106,6 +106,8 @@ public const string Xslt = "xslt"; public const string Languages = "languages"; + + public const string Macros = "macros"; /// /// alias for the user types tree. diff --git a/src/Umbraco.Web.UI/config/trees.Release.config b/src/Umbraco.Web.UI/config/trees.Release.config index 2825b5205e..bdf30ed8f3 100644 --- a/src/Umbraco.Web.UI/config/trees.Release.config +++ b/src/Umbraco.Web.UI/config/trees.Release.config @@ -20,7 +20,7 @@ - + diff --git a/src/Umbraco.Web.UI/config/trees.config b/src/Umbraco.Web.UI/config/trees.config index dece42a362..364ecfd4fd 100644 --- a/src/Umbraco.Web.UI/config/trees.config +++ b/src/Umbraco.Web.UI/config/trees.config @@ -17,7 +17,7 @@ - + diff --git a/src/Umbraco.Web/Trees/MacrosTreeController.cs b/src/Umbraco.Web/Trees/MacrosTreeController.cs new file mode 100644 index 0000000000..ce9d71fb75 --- /dev/null +++ b/src/Umbraco.Web/Trees/MacrosTreeController.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.Http.Formatting; +using System.Web.Http; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi.Filters; +using umbraco; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Services; +using Umbraco.Web._Legacy.Actions; +using Constants = Umbraco.Core.Constants; + +namespace Umbraco.Web.Trees +{ + [UmbracoTreeAuthorize(Constants.Trees.Macros)] + [Tree(Constants.Applications.Developer, Constants.Trees.Macros, "Macros", sortOrder: 2)] + [PluginController("UmbracoTrees")] + [CoreTree] + public class MacrosTreeController : TreeController + { + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var nodes = new TreeNodeCollection(); + + if (id == Constants.System.Root.ToInvariantString()) + { + foreach (var macro in Services.MacroService.GetAll()) + { + nodes.Add(CreateTreeNode( + macro.Id.ToString(), + id, + queryStrings, + macro.Name, + "icon-settings-alt", + false, + //TODO: Rebuild the macro editor in angular, then we dont need to have this at all (which is just a path to the legacy editor) + "/" + queryStrings.GetValue("application") + "/framed/" + + Uri.EscapeDataString("/umbraco/developer/macros/editMacro.aspx?macroID=" + macro.Id))); + } + } + + return nodes; + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + + if (id == Constants.System.Root.ToInvariantString()) + { + //Create the normal create action + menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)) + //Since we haven't implemented anything for macros in angular, this needs to be converted to + //use the legacy format + .ConvertLegacyMenuItem(null, "initmacros", queryStrings.GetValue("application")); + + //refresh action + menu.Items.Add(Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); + + return menu; + } + + + var macro = Services.MacroService.GetById(int.Parse(id)); + if (macro == null) return new MenuItemCollection(); + + //add delete option for all languages + menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias)) + //Since we haven't implemented anything for languages in angular, this needs to be converted to + //use the legacy format + .ConvertLegacyMenuItem(new UmbracoEntity + { + Id = macro.Id, + Level = 1, + ParentId = -1, + Name = macro.Name + }, "macros", queryStrings.GetValue("application")); + + return menu; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7dd0f6b41d..2f1dcef7ce 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -326,6 +326,7 @@ + @@ -1547,7 +1548,6 @@ - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMacros.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMacros.cs deleted file mode 100644 index 6a689878fa..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadMacros.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Text; - -using umbraco.DataLayer; -using umbraco.cms.presentation.Trees; -using Umbraco.Core; -using Umbraco.Web.Trees; - - -namespace umbraco -{ - /// - /// Handles loading of the cache application into the developer application tree - /// - [Tree(Constants.Applications.Developer, "macros", "Macros", sortOrder: 2)] - public class loadMacros : BaseTree - { - - public loadMacros(string application) : base(application) { } - - protected override void CreateRootNode(ref XmlTreeNode rootNode) - { - rootNode.NodeType = "init" + TreeAlias; - rootNode.NodeID = "init"; - } - - /// - /// Renders the JS. - /// - /// The javascript. - public override void RenderJS(ref StringBuilder Javascript) - { - Javascript.Append( - @" -function openMacro(id) { - UmbClientMgr.contentFrame('developer/macros/editMacro.aspx?macroID=' + id); -} -"); - } - - /// - /// This will call the normal Render method by passing the converted XmlTree to an XmlDocument. - /// TODO: need to update this render method to do everything that the obsolete render method does and remove the obsolete method - /// - /// - public override void Render(ref XmlTree tree) - { - foreach(var macros in ApplicationContext.Current.DatabaseContext.Database.Query("select id, macroName from cmsMacro order by macroName")) - { - XmlTreeNode xNode = XmlTreeNode.Create(this); - xNode.NodeID = macros.id.ToString(); - xNode.Text = macros.macroName; - xNode.Action = "javascript:openMacro(" + macros.id + ");"; - xNode.Icon = " icon-settings-alt"; - xNode.OpenIcon = "icon-settings-alt"; - OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty); - if (xNode != null) - { - tree.Add(xNode); - } - OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty); - } - } - - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs index 9e9d3b1522..08d28617e2 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs @@ -44,7 +44,7 @@ namespace umbraco.cms.presentation.developer if (IsPostBack == false) { ClientTools - .SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) + .SetActiveTreeType(Constants.Trees.Macros) .SyncTree("-1,init," + _macro.Id, false); string tempMacroAssembly = _macro.ControlAssembly ?? ""; @@ -292,7 +292,7 @@ namespace umbraco.cms.presentation.developer Page.Validate(); ClientTools - .SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) + .SetActiveTreeType(Constants.Trees.Macros) .SyncTree("-1,init," + _macro.Id.ToInvariantString(), true); //true forces the reload var tempMacroAssembly = macroAssembly.Text; From 5ec0a58d4dd7b44b315e3ac7fe961b71d1af6680 Mon Sep 17 00:00:00 2001 From: Yannis Guedel Date: Wed, 23 Mar 2016 22:58:52 +0100 Subject: [PATCH 023/101] fixed copy paste error --- src/Umbraco.Web/Trees/MacrosTreeController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Trees/MacrosTreeController.cs b/src/Umbraco.Web/Trees/MacrosTreeController.cs index ce9d71fb75..3ee054dee5 100644 --- a/src/Umbraco.Web/Trees/MacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/MacrosTreeController.cs @@ -70,9 +70,9 @@ namespace Umbraco.Web.Trees var macro = Services.MacroService.GetById(int.Parse(id)); if (macro == null) return new MenuItemCollection(); - //add delete option for all languages + //add delete option for all macros menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias)) - //Since we haven't implemented anything for languages in angular, this needs to be converted to + //Since we haven't implemented anything for macros in angular, this needs to be converted to //use the legacy format .ConvertLegacyMenuItem(new UmbracoEntity { From e2fbbc3df0f6de7e5581ca70ffaade250fdf48cb Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Thu, 24 Mar 2016 12:02:29 +0100 Subject: [PATCH 024/101] First commit - Could not resist the urge to get rid of a lot of unused images as well as some of the unused .ascx files. Primarily images removed in this commit and two user controls no longer used in the dashboard.config. --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 339 ------------------ .../Umbraco/Images/editor/renderbody.gif | Bin 1336 -> 0 bytes .../Images/pinnedIcons/task_content.ico | Bin 1150 -> 0 bytes .../Images/pinnedIcons/task_default.ico | Bin 1150 -> 0 bytes .../Images/pinnedIcons/task_developer.ico | Bin 1150 -> 0 bytes .../Umbraco/Images/pinnedIcons/task_media.ico | Bin 1150 -> 0 bytes .../Images/pinnedIcons/task_member.ico | Bin 1150 -> 0 bytes .../Images/pinnedIcons/task_settings.ico | Bin 1150 -> 0 bytes .../Umbraco/Images/pinnedIcons/task_users.ico | Bin 1150 -> 0 bytes .../Umbraco/Images/pinnedIcons/umb.ico | Bin 17542 -> 0 bytes .../Umbraco/Images/umbraco/icon_folder.gif | Bin 1030 -> 0 bytes .../umbraco/dashboard/ExamineManagement.ascx | 216 ----------- .../dashboard/ExamineManagement.ascx.cs | 14 - .../ExamineManagement.ascx.designer.cs | 60 ---- .../umbraco/dashboard/LatestEdits.ascx | 11 - src/Umbraco.Web.UI/umbraco/images/Lminus.png | Bin 219 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/Lplus.png | Bin 224 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/T.png | Bin 152 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/Tminus.png | Bin 207 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/Tplus.png | Bin 222 -> 0 bytes .../umbraco/images/aboutNew.png | Bin 1178 -> 0 bytes .../umbraco/images/arrawBack.gif | Bin 834 -> 0 bytes .../umbraco/images/arrowDown.gif | Bin 832 -> 0 bytes .../umbraco/images/arrowForward.gif | Bin 834 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/audit.png | Bin 897 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/back.png | Bin 422 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/blank.png | Bin 144 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/c_b.gif | Bin 92 -> 0 bytes .../umbraco/images/c_b_label.gif | Bin 96 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/c_bl.gif | Bin 211 -> 0 bytes .../umbraco/images/c_bl_label.gif | Bin 948 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/c_br.gif | Bin 240 -> 0 bytes .../umbraco/images/c_br_label.gif | Bin 1031 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/c_r.gif | Bin 93 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/c_t.gif | Bin 96 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/c_tl.gif | Bin 242 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/c_tr.gif | Bin 434 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/close.png | Bin 389 -> 0 bytes .../umbraco/images/collapse.png | Bin 388 -> 0 bytes .../umbraco/images/copy.small.png | Bin 489 -> 0 bytes .../umbraco/images/cut.small.png | Bin 472 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/date.gif | Bin 253 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/delete.gif | Bin 512 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/delete.png | Bin 715 -> 0 bytes .../umbraco/images/delete.small.png | Bin 374 -> 0 bytes .../umbraco/images/delete_button.png | Bin 582 -> 0 bytes .../images/developer/customControlIcon.png | Bin 842 -> 0 bytes .../images/developer/usercontrolIcon.png | Bin 775 -> 0 bytes .../umbraco/images/developer/xsltIcon.png | Bin 776 -> 0 bytes .../umbraco/images/dialogBg.png | Bin 255 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/domain.gif | Bin 362 -> 0 bytes .../umbraco/images/domain_on.png | Bin 472 -> 0 bytes .../umbraco/images/download.png | Bin 1420 -> 0 bytes .../umbraco/images/editor/Bold.GIF | Bin 76 -> 0 bytes .../umbraco/images/editor/Center.GIF | Bin 73 -> 0 bytes .../umbraco/images/editor/Copy.GIF | Bin 263 -> 0 bytes .../umbraco/images/editor/Cut.GIF | Bin 187 -> 0 bytes .../umbraco/images/editor/DeIndent.GIF | Bin 199 -> 0 bytes .../umbraco/images/editor/Italic.GIF | Bin 79 -> 0 bytes .../umbraco/images/editor/Link.GIF | Bin 175 -> 0 bytes .../umbraco/images/editor/Lock.GIF | Bin 87 -> 0 bytes .../umbraco/images/editor/Open.GIF | Bin 132 -> 0 bytes .../umbraco/images/editor/Paste.GIF | Bin 286 -> 0 bytes .../umbraco/images/editor/Redo.GIF | Bin 169 -> 0 bytes .../umbraco/images/editor/Save.GIF | Bin 633 -> 0 bytes .../umbraco/images/editor/SaveAndPublish.gif | Bin 1094 -> 0 bytes .../umbraco/images/editor/SaveAndPublish.png | Bin 1196 -> 0 bytes .../umbraco/images/editor/SaveToPublish.gif | Bin 610 -> 0 bytes .../umbraco/images/editor/TaskList.GIF | Bin 137 -> 0 bytes .../umbraco/images/editor/Undo.GIF | Bin 175 -> 0 bytes .../umbraco/images/editor/anchor.gif | Bin 171 -> 0 bytes .../umbraco/images/editor/anchor.png | Bin 1152 -> 0 bytes .../umbraco/images/editor/anchor_symbol.gif | Bin 70 -> 0 bytes .../umbraco/images/editor/backcolor.gif | Bin 174 -> 0 bytes .../umbraco/images/editor/bold_de_se.gif | Bin 73 -> 0 bytes .../umbraco/images/editor/bold_es.gif | Bin 80 -> 0 bytes .../umbraco/images/editor/bold_fr.gif | Bin 78 -> 0 bytes .../umbraco/images/editor/bold_ru.gif | Bin 77 -> 0 bytes .../umbraco/images/editor/bold_tw.gif | Bin 207 -> 0 bytes .../umbraco/images/editor/browse.gif | Bin 113 -> 0 bytes .../umbraco/images/editor/bullist.GIF | Bin 108 -> 0 bytes .../umbraco/images/editor/button_menu.gif | Bin 57 -> 0 bytes .../umbraco/images/editor/buttons.gif | Bin 8399 -> 0 bytes .../umbraco/images/editor/calendar.gif | Bin 394 -> 0 bytes .../umbraco/images/editor/calendarButton.gif | Bin 108 -> 0 bytes .../images/editor/cancel_button_bg.gif | Bin 677 -> 0 bytes .../umbraco/images/editor/charmap.gif | Bin 245 -> 0 bytes .../umbraco/images/editor/cleanup.gif | Bin 256 -> 0 bytes .../umbraco/images/editor/close.gif | Bin 102 -> 0 bytes .../umbraco/images/editor/code.gif | Bin 110 -> 0 bytes .../umbraco/images/editor/color.gif | Bin 125 -> 0 bytes .../umbraco/images/editor/custom_1.gif | Bin 76 -> 0 bytes .../umbraco/images/editor/delcell.GIF | Bin 123 -> 0 bytes .../umbraco/images/editor/delcol.GIF | Bin 121 -> 0 bytes .../umbraco/images/editor/delrow.GIF | Bin 124 -> 0 bytes .../umbraco/images/editor/dezoom.gif | Bin 294 -> 0 bytes .../umbraco/images/editor/dictionaryItem.gif | Bin 1040 -> 0 bytes .../umbraco/images/editor/doc.gif | Bin 616 -> 0 bytes .../umbraco/images/editor/documentType.gif | Bin 607 -> 0 bytes .../umbraco/images/editor/forecolor.gif | Bin 272 -> 0 bytes .../umbraco/images/editor/form.gif | Bin 1085 -> 0 bytes .../umbraco/images/editor/formButton.gif | Bin 109 -> 0 bytes .../umbraco/images/editor/formCheckbox.gif | Bin 118 -> 0 bytes .../umbraco/images/editor/formHidden.gif | Bin 131 -> 0 bytes .../umbraco/images/editor/formRadio.gif | Bin 111 -> 0 bytes .../umbraco/images/editor/formSelect.gif | Bin 126 -> 0 bytes .../umbraco/images/editor/formText.gif | Bin 132 -> 0 bytes .../umbraco/images/editor/formTextarea.gif | Bin 118 -> 0 bytes .../umbraco/images/editor/fullscrn.GIF | Bin 134 -> 0 bytes .../umbraco/images/editor/help.gif | Bin 295 -> 0 bytes .../umbraco/images/editor/help.png | Bin 377 -> 0 bytes .../umbraco/images/editor/hr.gif | Bin 63 -> 0 bytes .../umbraco/images/editor/html.gif | Bin 321 -> 0 bytes .../umbraco/images/editor/image.GIF | Bin 194 -> 0 bytes .../umbraco/images/editor/indent.gif | Bin 112 -> 0 bytes .../umbraco/images/editor/inindent.GIF | Bin 199 -> 0 bytes .../umbraco/images/editor/insBreadcrum.gif | Bin 382 -> 0 bytes .../images/editor/insChildTemplate.gif | Bin 914 -> 0 bytes .../images/editor/insChildTemplateNew.gif | Bin 914 -> 0 bytes .../umbraco/images/editor/insField.gif | Bin 648 -> 0 bytes .../umbraco/images/editor/insFieldByLevel.gif | Bin 626 -> 0 bytes .../umbraco/images/editor/insFieldByTree.gif | Bin 621 -> 0 bytes .../umbraco/images/editor/insMacro.gif | Bin 610 -> 0 bytes .../umbraco/images/editor/insMacroSB.png | Bin 474 -> 0 bytes .../umbraco/images/editor/insMemberItem.gif | Bin 627 -> 0 bytes .../umbraco/images/editor/insRazorMacro.png | Bin 529 -> 0 bytes .../umbraco/images/editor/inscell.GIF | Bin 123 -> 0 bytes .../umbraco/images/editor/inscol.GIF | Bin 120 -> 0 bytes .../images/editor/insert_button_bg.gif | Bin 703 -> 0 bytes .../umbraco/images/editor/insform.gif | Bin 119 -> 0 bytes .../umbraco/images/editor/inshtml.GIF | Bin 191 -> 0 bytes .../umbraco/images/editor/insrow.GIF | Bin 124 -> 0 bytes .../umbraco/images/editor/instable.GIF | Bin 612 -> 0 bytes .../umbraco/images/editor/italic_de_se.gif | Bin 75 -> 0 bytes .../umbraco/images/editor/italic_es.gif | Bin 74 -> 0 bytes .../umbraco/images/editor/italic_ru.gif | Bin 78 -> 0 bytes .../umbraco/images/editor/italic_tw.gif | Bin 274 -> 0 bytes .../umbraco/images/editor/justifycenter.gif | Bin 70 -> 0 bytes .../umbraco/images/editor/justifyfull.gif | Bin 71 -> 0 bytes .../umbraco/images/editor/justifyleft.gif | Bin 71 -> 0 bytes .../umbraco/images/editor/justifyright.gif | Bin 70 -> 0 bytes .../umbraco/images/editor/left.GIF | Bin 73 -> 0 bytes .../images/editor/masterpageContent.gif | Bin 132 -> 0 bytes .../images/editor/masterpagePlaceHolder.gif | Bin 286 -> 0 bytes .../umbraco/images/editor/media.gif | Bin 260 -> 0 bytes .../umbraco/images/editor/menu_check.gif | Bin 51 -> 0 bytes .../umbraco/images/editor/mrgcell.GIF | Bin 142 -> 0 bytes .../umbraco/images/editor/newdoc.GIF | Bin 102 -> 0 bytes .../umbraco/images/editor/newdocument.gif | Bin 170 -> 0 bytes .../umbraco/images/editor/numlist.GIF | Bin 111 -> 0 bytes .../umbraco/images/editor/opacity.png | Bin 147 -> 0 bytes .../umbraco/images/editor/outdent.gif | Bin 110 -> 0 bytes .../umbraco/images/editor/project.GIF | Bin 149 -> 0 bytes .../umbraco/images/editor/properties.gif | Bin 139 -> 0 bytes .../umbraco/images/editor/propertiesNew.gif | Bin 983 -> 0 bytes .../umbraco/images/editor/props.GIF | Bin 122 -> 0 bytes .../umbraco/images/editor/rel.gif | Bin 327 -> 0 bytes .../umbraco/images/editor/removeformat.gif | Bin 168 -> 0 bytes .../umbraco/images/editor/right.GIF | Bin 73 -> 0 bytes .../umbraco/images/editor/saveToPublish.png | Bin 861 -> 0 bytes .../umbraco/images/editor/separator.gif | Bin 57 -> 0 bytes .../umbraco/images/editor/showStyles.gif | Bin 197 -> 0 bytes .../umbraco/images/editor/showStyles.png | Bin 628 -> 0 bytes .../umbraco/images/editor/skin.gif | Bin 1684 -> 0 bytes .../umbraco/images/editor/spacer.gif | Bin 43 -> 0 bytes .../umbraco/images/editor/spellchecker.gif | Bin 591 -> 0 bytes .../umbraco/images/editor/split.gif | Bin 73 -> 0 bytes .../umbraco/images/editor/spltcell.GIF | Bin 137 -> 0 bytes .../images/editor/statusbar_resize.gif | Bin 79 -> 0 bytes .../umbraco/images/editor/strikethrough.gif | Bin 83 -> 0 bytes .../umbraco/images/editor/styleMarkEnd.gif | Bin 968 -> 0 bytes .../umbraco/images/editor/styleMarkStart.gif | Bin 157 -> 0 bytes .../umbraco/images/editor/sub.gif | Bin 148 -> 0 bytes .../umbraco/images/editor/sup.gif | Bin 147 -> 0 bytes .../umbraco/images/editor/table.gif | Bin 287 -> 0 bytes .../umbraco/images/editor/umbracoField.gif | Bin 120 -> 0 bytes .../images/editor/umbracoScriptlet.gif | Bin 146 -> 0 bytes .../umbraco/images/editor/umbracoTextGen.gif | Bin 560 -> 0 bytes .../umbraco/images/editor/under.GIF | Bin 91 -> 0 bytes .../umbraco/images/editor/underline.gif | Bin 88 -> 0 bytes .../umbraco/images/editor/underline_es.gif | Bin 79 -> 0 bytes .../umbraco/images/editor/underline_fr.gif | Bin 79 -> 0 bytes .../umbraco/images/editor/underline_ru.gif | Bin 77 -> 0 bytes .../umbraco/images/editor/underline_tw.gif | Bin 245 -> 0 bytes .../umbraco/images/editor/unlink.gif | Bin 190 -> 0 bytes .../umbraco/images/editor/upload.png | Bin 3113 -> 0 bytes .../umbraco/images/editor/vis.gif | Bin 589 -> 0 bytes .../umbraco/images/editor/visualaid.gif | Bin 206 -> 0 bytes .../umbraco/images/editor/xslVisualize.gif | Bin 663 -> 0 bytes .../umbraco/images/editor/zoom.gif | Bin 302 -> 0 bytes .../umbraco/images/errorLayerBackground.gif | Bin 1487 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/expand.png | Bin 400 -> 0 bytes .../umbraco/images/exportDocumenttype.png | Bin 617 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/false.png | Bin 894 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/file.png | Bin 251 -> 0 bytes .../umbraco/images/find.small.png | Bin 390 -> 0 bytes .../umbraco/images/findDocument.gif | Bin 967 -> 0 bytes .../umbraco/images/findDocument.png | Bin 617 -> 0 bytes .../umbraco/images/folder.small.png | Bin 413 -> 0 bytes .../umbraco/images/foldericon.png | Bin 229 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/forward.png | Bin 418 -> 0 bytes .../umbraco/images/gradientBackground.png | Bin 1321 -> 0 bytes .../umbraco/images/gradientLine.gif | Bin 1482 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/help.gif | Bin 1013 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/help.png | Bin 1018 -> 0 bytes .../umbraco/images/htmldoc.small.png | Bin 507 -> 0 bytes .../umbraco/images/importDocumenttype.png | Bin 601 -> 0 bytes .../umbraco/images/information.png | Bin 725 -> 0 bytes .../umbraco/images/listItemOrange.gif | Bin 114 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/loginBg.png | Bin 17566 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/logout.png | Bin 1011 -> 0 bytes .../umbraco/images/logout_small.gif | Bin 996 -> 0 bytes .../umbraco/images/logout_small.png | Bin 592 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/macro.gif | Bin 115 -> 0 bytes .../umbraco/images/mediaThumbnails/pdf.png | Bin 6074 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/nada.gif | Bin 45 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/new.gif | Bin 246 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/new.png | Bin 1035 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/newStar.gif | Bin 989 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/notepad.png | Bin 489 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/notify.gif | Bin 1003 -> 0 bytes .../umbraco/images/notifyOld.gif | Bin 1003 -> 0 bytes .../umbraco/images/okLayerBackground.gif | Bin 1469 -> 0 bytes .../umbraco/images/openfoldericon.png | Bin 232 -> 0 bytes .../umbraco/images/options.small.png | Bin 396 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/package.png | Bin 1012 -> 0 bytes .../umbraco/images/package2.png | Bin 966 -> 0 bytes .../umbraco/images/paste.small.png | Bin 632 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/pencil.png | Bin 450 -> 0 bytes .../umbraco/images/permission.gif | Bin 1009 -> 0 bytes .../umbraco/images/permission.png | Bin 581 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/protect.gif | Bin 1023 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/protect.png | Bin 628 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/publish.gif | Bin 554 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/publish.png | Bin 717 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/refresh.png | Bin 502 -> 0 bytes .../umbraco/images/rollback.gif | Bin 588 -> 0 bytes .../umbraco/images/rollback.png | Bin 626 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/save.png | Bin 549 -> 0 bytes .../umbraco/images/sendToTranslate.png | Bin 1047 -> 0 bytes .../umbraco/images/small_minus.png | Bin 232 -> 0 bytes .../umbraco/images/small_plus.png | Bin 257 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/sort.gif | Bin 388 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/sort.png | Bin 400 -> 0 bytes .../umbraco/images/sort.small.png | Bin 400 -> 0 bytes .../umbraco/images/speechBubble/error.gif | Bin 582 -> 0 bytes .../umbraco/images/speechBubble/error.png | Bin 1915 -> 0 bytes .../umbraco/images/speechBubble/info.gif | Bin 630 -> 0 bytes .../umbraco/images/speechBubble/info.png | Bin 1542 -> 0 bytes .../umbraco/images/speechBubble/save.gif | Bin 562 -> 0 bytes .../umbraco/images/speechBubble/save.png | Bin 1036 -> 0 bytes .../images/speechBubble/speechbubble.gif | Bin 9439 -> 0 bytes .../images/speechBubble/speechbubble.png | Bin 12444 -> 0 bytes .../speechBubble/speechbubbleShadow.png | Bin 916 -> 0 bytes .../speechBubble/speechbubbleShadowNew.gif | Bin 1774 -> 0 bytes .../images/speechBubble/speechbubble_body.png | Bin 10694 -> 0 bytes .../speechBubble/speechbubble_bottom.png | Bin 2891 -> 0 bytes .../speechBubble/speechbubble_close.gif | Bin 169 -> 0 bytes .../speechBubble/speechbubble_close_over.gif | Bin 696 -> 0 bytes .../speechBubble/speechbubble_shadow.gif | Bin 660 -> 0 bytes .../images/speechBubble/speechbubble_top.png | Bin 1045 -> 0 bytes .../umbraco/images/speechBubble/success.png | Bin 1036 -> 0 bytes .../umbraco/images/speechBubble/warning.png | Bin 1188 -> 0 bytes .../umbraco/images/throbber.gif | Bin 1844 -> 0 bytes .../umbraco/images/thumbnails/developer.png | Bin 19431 -> 0 bytes .../umbraco/images/thumbnails/doc.png | Bin 8311 -> 0 bytes .../images/thumbnails/docWithImage.png | Bin 21725 -> 0 bytes .../umbraco/images/thumbnails/folder.png | Bin 21034 -> 0 bytes .../images/thumbnails/folder_media.png | Bin 28459 -> 0 bytes .../umbraco/images/thumbnails/mediaFile.png | Bin 8311 -> 0 bytes .../umbraco/images/thumbnails/mediaPhoto.png | Bin 21725 -> 0 bytes .../umbraco/images/thumbnails/member.png | Bin 15119 -> 0 bytes .../umbraco/images/thumbnails/memberGroup.png | Bin 19497 -> 0 bytes .../umbraco/images/thumbnails/members.png | Bin 19684 -> 0 bytes .../umbraco/images/thumbnails/template.png | Bin 11883 -> 0 bytes .../umbraco/images/thumbnails/xml.png | Bin 12153 -> 0 bytes .../umbraco/images/thumbs_lrg.png | Bin 959 -> 0 bytes .../umbraco/images/thumbs_med.png | Bin 967 -> 0 bytes .../umbraco/images/thumbs_smll.png | Bin 957 -> 0 bytes .../umbraco/images/toggleTreeOff.png | Bin 266 -> 0 bytes .../umbraco/images/toggleTreeOn.png | Bin 271 -> 0 bytes .../umbraco/images/topGradient.gif | Bin 304 -> 0 bytes .../umbraco/images/tray/traySprites.png | Bin 11965 -> 0 bytes src/Umbraco.Web.UI/umbraco/images/true.png | Bin 612 -> 0 bytes .../umbraco/images/umbraco/bin.png | Bin 3435 -> 0 bytes .../umbraco/images/umbraco/bin_closed.png | Bin 363 -> 0 bytes .../umbraco/images/umbraco/bin_empty.png | Bin 659 -> 0 bytes .../images/umbraco/developerCacheItem.gif | Bin 577 -> 0 bytes .../images/umbraco/developerCacheTypes.gif | Bin 578 -> 0 bytes .../images/umbraco/developerDatatype.gif | Bin 125 -> 0 bytes .../umbraco/images/umbraco/developerMacro.gif | Bin 604 -> 0 bytes .../images/umbraco/developerRegistry.gif | Bin 118 -> 0 bytes .../images/umbraco/developerRegistryItem.gif | Bin 123 -> 0 bytes .../images/umbraco/developerScript.gif | Bin 399 -> 0 bytes .../umbraco/images/umbraco/developerXslt.gif | Bin 410 -> 0 bytes .../umbraco/images/umbraco/doc.gif | Bin 616 -> 0 bytes .../umbraco/images/umbraco/doc2.gif | Bin 604 -> 0 bytes .../umbraco/images/umbraco/doc3.gif | Bin 616 -> 0 bytes .../umbraco/images/umbraco/doc4.gif | Bin 597 -> 0 bytes .../umbraco/images/umbraco/doc5.gif | Bin 609 -> 0 bytes .../umbraco/images/umbraco/docPic.gif | Bin 607 -> 0 bytes .../umbraco/images/umbraco/folder.gif | Bin 1030 -> 0 bytes .../umbraco/images/umbraco/folder_o.gif | Bin 1030 -> 0 bytes .../umbraco/images/umbraco/mediaFile.gif | Bin 621 -> 0 bytes .../umbraco/images/umbraco/mediaMovie.gif | Bin 619 -> 0 bytes .../umbraco/images/umbraco/mediaMulti.gif | Bin 618 -> 0 bytes .../umbraco/images/umbraco/mediaPhoto.gif | Bin 561 -> 0 bytes .../umbraco/images/umbraco/member.gif | Bin 998 -> 0 bytes .../umbraco/images/umbraco/memberGroup.gif | Bin 1000 -> 0 bytes .../umbraco/images/umbraco/memberType.gif | Bin 1012 -> 0 bytes .../umbraco/images/umbraco/newsletter.gif | Bin 1013 -> 0 bytes .../umbraco/images/umbraco/nitros.gif | Bin 525 -> 0 bytes .../umbraco/images/umbraco/package.gif | Bin 1021 -> 0 bytes .../umbraco/images/umbraco/package.png | Bin 810 -> 0 bytes .../umbraco/images/umbraco/repository.gif | Bin 1062 -> 0 bytes .../umbraco/images/umbraco/settingAgent.gif | Bin 145 -> 0 bytes .../umbraco/images/umbraco/settingCss.gif | Bin 607 -> 0 bytes .../umbraco/images/umbraco/settingCssItem.gif | Bin 299 -> 0 bytes .../images/umbraco/settingDataTypeChild.gif | Bin 144 -> 0 bytes .../images/umbraco/settingDatatype.gif | Bin 607 -> 0 bytes .../umbraco/images/umbraco/settingDomain.gif | Bin 1034 -> 0 bytes .../images/umbraco/settingLanguage.gif | Bin 387 -> 0 bytes .../images/umbraco/settingMasterDatatype.gif | Bin 587 -> 0 bytes .../images/umbraco/settingMasterTemplate.gif | Bin 235 -> 0 bytes .../umbraco/images/umbraco/settingSkin.gif | Bin 1655 -> 0 bytes .../images/umbraco/settingTemplate.gif | Bin 252 -> 0 bytes .../umbraco/images/umbraco/settingView.gif | Bin 1644 -> 0 bytes .../umbraco/images/umbraco/settingXML.gif | Bin 268 -> 0 bytes .../umbraco/images/umbraco/settingsScript.gif | Bin 553 -> 0 bytes .../umbraco/images/umbraco/sprites.png | Bin 14538 -> 0 bytes .../umbraco/images/umbraco/sprites_ie6.gif | Bin 7307 -> 0 bytes .../umbraco/images/umbraco/statistik.gif | Bin 305 -> 0 bytes .../umbraco/images/umbraco/uploadpackage.gif | Bin 1035 -> 0 bytes .../umbraco/images/umbraco/user.gif | Bin 998 -> 0 bytes .../umbraco/images/umbraco/userGroup.gif | Bin 1000 -> 0 bytes .../umbraco/images/umbraco/userType.gif | Bin 1012 -> 0 bytes .../umbraco/images/umbracoSplash.png | Bin 3134 -> 0 bytes 337 files changed, 640 deletions(-) delete mode 100644 src/Umbraco.Web.UI/Umbraco/Images/editor/renderbody.gif delete mode 100644 src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_content.ico delete mode 100644 src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_default.ico delete mode 100644 src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_developer.ico delete mode 100644 src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_media.ico delete mode 100644 src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_member.ico delete mode 100644 src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_settings.ico delete mode 100644 src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_users.ico delete mode 100644 src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/umb.ico delete mode 100644 src/Umbraco.Web.UI/Umbraco/Images/umbraco/icon_folder.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/dashboard/ExamineManagement.ascx delete mode 100644 src/Umbraco.Web.UI/umbraco/dashboard/ExamineManagement.ascx.cs delete mode 100644 src/Umbraco.Web.UI/umbraco/dashboard/ExamineManagement.ascx.designer.cs delete mode 100644 src/Umbraco.Web.UI/umbraco/dashboard/LatestEdits.ascx delete mode 100644 src/Umbraco.Web.UI/umbraco/images/Lminus.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/Lplus.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/T.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/Tminus.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/Tplus.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/aboutNew.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/arrawBack.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/arrowDown.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/arrowForward.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/audit.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/back.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/blank.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_b.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_b_label.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_bl.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_bl_label.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_br.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_br_label.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_r.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_t.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_tl.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/c_tr.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/close.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/collapse.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/copy.small.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/cut.small.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/date.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/delete.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/delete.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/delete.small.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/delete_button.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/developer/customControlIcon.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/developer/usercontrolIcon.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/developer/xsltIcon.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/dialogBg.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/domain.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/domain_on.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/download.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/Bold.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/Center.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/Copy.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/Cut.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/DeIndent.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/Italic.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/Link.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/Lock.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/Open.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/Paste.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/Redo.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/Save.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/SaveAndPublish.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/SaveAndPublish.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/SaveToPublish.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/TaskList.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/Undo.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/anchor.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/anchor.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/anchor_symbol.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/backcolor.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/bold_de_se.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/bold_es.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/bold_fr.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/bold_ru.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/bold_tw.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/browse.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/bullist.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/button_menu.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/buttons.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/calendar.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/calendarButton.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/cancel_button_bg.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/charmap.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/cleanup.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/close.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/code.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/color.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/custom_1.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/delcell.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/delcol.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/delrow.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/dezoom.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/dictionaryItem.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/doc.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/documentType.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/forecolor.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/form.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/formButton.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/formCheckbox.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/formHidden.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/formRadio.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/formSelect.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/formText.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/formTextarea.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/fullscrn.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/help.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/help.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/hr.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/html.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/image.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/indent.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/inindent.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/insBreadcrum.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/insChildTemplate.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/insChildTemplateNew.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/insField.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/insFieldByLevel.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/insFieldByTree.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/insMacro.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/insMacroSB.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/insMemberItem.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/insRazorMacro.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/inscell.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/inscol.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/insert_button_bg.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/insform.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/inshtml.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/insrow.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/instable.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/italic_de_se.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/italic_es.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/italic_ru.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/italic_tw.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/justifycenter.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/justifyfull.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/justifyleft.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/justifyright.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/left.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/masterpageContent.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/masterpagePlaceHolder.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/media.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/menu_check.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/mrgcell.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/newdoc.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/newdocument.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/numlist.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/opacity.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/outdent.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/project.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/properties.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/propertiesNew.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/props.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/rel.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/removeformat.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/right.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/saveToPublish.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/separator.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/showStyles.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/showStyles.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/skin.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/spacer.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/spellchecker.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/split.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/spltcell.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/statusbar_resize.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/strikethrough.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/styleMarkEnd.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/styleMarkStart.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/sub.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/sup.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/table.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/umbracoField.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/umbracoScriptlet.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/umbracoTextGen.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/under.GIF delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/underline.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/underline_es.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/underline_fr.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/underline_ru.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/underline_tw.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/unlink.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/upload.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/vis.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/visualaid.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/xslVisualize.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/editor/zoom.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/errorLayerBackground.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/expand.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/exportDocumenttype.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/false.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/file.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/find.small.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/findDocument.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/findDocument.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/folder.small.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/foldericon.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/forward.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/gradientBackground.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/gradientLine.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/help.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/help.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/htmldoc.small.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/importDocumenttype.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/information.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/listItemOrange.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/loginBg.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/logout.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/logout_small.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/logout_small.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/macro.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/mediaThumbnails/pdf.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/nada.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/new.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/new.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/newStar.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/notepad.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/notify.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/notifyOld.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/okLayerBackground.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/openfoldericon.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/options.small.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/package.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/package2.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/paste.small.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/pencil.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/permission.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/permission.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/protect.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/protect.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/publish.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/publish.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/refresh.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/rollback.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/rollback.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/save.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/sendToTranslate.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/small_minus.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/small_plus.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/sort.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/sort.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/sort.small.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/error.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/error.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/info.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/info.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/save.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/save.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubbleShadow.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubbleShadowNew.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_body.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_bottom.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_close.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_close_over.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_shadow.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_top.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/success.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/speechBubble/warning.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/throbber.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbnails/developer.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbnails/doc.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbnails/docWithImage.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbnails/folder.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbnails/folder_media.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbnails/mediaFile.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbnails/mediaPhoto.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbnails/member.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbnails/memberGroup.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbnails/members.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbnails/template.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbnails/xml.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbs_lrg.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbs_med.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/thumbs_smll.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/toggleTreeOff.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/toggleTreeOn.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/topGradient.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/tray/traySprites.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/true.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/bin.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/bin_closed.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/bin_empty.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/developerCacheItem.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/developerCacheTypes.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/developerDatatype.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/developerMacro.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/developerRegistry.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/developerRegistryItem.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/developerScript.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/developerXslt.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/doc.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/doc2.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/doc3.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/doc4.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/doc5.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/docPic.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/folder.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/folder_o.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/mediaFile.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/mediaMovie.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/mediaMulti.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/mediaPhoto.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/member.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/memberGroup.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/memberType.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/newsletter.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/nitros.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/package.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/package.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/repository.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingAgent.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingCss.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingCssItem.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingDataTypeChild.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingDatatype.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingDomain.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingLanguage.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingMasterDatatype.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingMasterTemplate.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingSkin.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingTemplate.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingView.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingXML.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/settingsScript.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/sprites.png delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/sprites_ie6.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/statistik.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/uploadpackage.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/user.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/userGroup.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbraco/userType.gif delete mode 100644 src/Umbraco.Web.UI/umbraco/images/umbracoSplash.png diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 0b89ed19a5..705282fd97 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -394,13 +394,6 @@ User.ascx - - ExamineManagement.ascx - ASPXCodeBehind - - - ExamineManagement.ascx - UserControlProxy.aspx ASPXCodeBehind @@ -551,14 +544,6 @@ - - - - - - - - @@ -636,7 +621,6 @@ - @@ -649,21 +633,6 @@ ASPXCodeBehind - - - - - - - - - - - - - - - @@ -691,8 +660,6 @@ - - @@ -701,14 +668,6 @@ - - - - - - - - @@ -733,12 +692,7 @@ - - - - - @@ -755,7 +709,6 @@ - @@ -768,14 +721,6 @@ - - - - - - - - @@ -786,35 +731,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -837,65 +753,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Designer - - - - - @@ -975,10 +836,6 @@ - - - - @@ -994,7 +851,6 @@ - @@ -1002,201 +858,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Umbraco.Web.UI/Umbraco/Images/editor/renderbody.gif b/src/Umbraco.Web.UI/Umbraco/Images/editor/renderbody.gif deleted file mode 100644 index 1a84493fe9d7668b782aee1e352f12970cc4bff2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1336 zcmZ?wbhEHb6k`x)I3mVy?Ha?)n+&gCGd_F9@c1#qrArL&-Z8&?$?*9z@7=o$4<0al z{K)?2&)+})|Ni)4@axat&tDCG{0jT^E9}possI1~Wnf_V|NlRb#03=pb4U0FD7Yk+ zBm!w0`-+0ZIIg#cFVINM%8)eo$(0erZv1Dp0vH$f^P>=c3falKi5O{QMkPCP( zUIa|kjQ{`r{qy_R&mZ5vef{$J)5j0*-@SeF`qj%9&!0Vg^7zri2lwyYy>t84%^TOR zUA=Po(!~qs&z(JU`qar2$B!L7a`@1}1N-;w-Lrew&K=vgZQZhY)5ZeMTG_VdAT{+S(zE>X{jm6Nr?&Zaj`McQIQehVWA9RT!WL(IBz?v24h5IWKHm@;LfF zz%}gUxcwZ*9VBRqW-{{>{`r`09=|=HFR++u3&R7o1uTo6#`>eIU!R{rM^Tr*)mL+P zu&*XxrzJEu99s|~>ruAhmHE&D_;rPOl~gPQmFPMo7K0?j<#bx!?yEX9tNBCsP9_o| zZ(1Izhh(XPIH-p-sLNoffoQn_(OgaMRglazkVQf6*ByyzzV&`FW8nbg;X8om9pHWo zxZY5FP4N}+5}54*W?rbdMHYvtu8?WIt?3M7+d&9D*T8zNgY{meaU}#pC7AI_2E%1A zLxqZzOkn8liL7sbRKkSFD$>idke9!!NEB6;zCrR&65|jTM%E6+rH=WM<;eKr(pQLqaiqK>gaawM z3kUVA?cdQ>#**3`GrcH#pxUC3+{F4$kA$3-QWzQ z;B>~Nx{AmDw1D@R$lrqF$+;cBy`21QT2J-JlUM%K4NOp7r|KqLCd6iX5t%kZptZJ^ zzx+f5QeXJ-1HfYQF7hnT>ke5aOHk_XtCX?Sgt%`i=(O@EgeO(kG1dMOjzLVQoHJ z?d-T@c0Q}vBzS_UJg;D*G#=@wVD|R%8Czpfr?I|xpH^3ik8`?9{);{2<#w0+2K;90 A{r~^~ diff --git a/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_default.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_default.ico deleted file mode 100644 index 8ccffe07944d8e8567fb8f3acc09b15dff902982..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1150 zcmbV}OH30{6o!ZB!mwgtT)Q$Z-4hnX#F&W4Bc+y-b|`HMeFd1(MoOWHh$TQFwY=08 z%0r-3C9;y`v~0CPwe%pF4BreE&K348uf`D<*~^ z_p?m)NrpMYFwA)(5+acwakBmMibNt&jYVo@vpFr?S0X}>H5tvulrJumoco(uokbPb zRG7ZqXA|Rv&yJ-57XnoZJT8;KXG*|bqiEk*z+O7;qpi}P);jd3{KX>vRltLlfEzD| z?qR9_4hHQR7^%v{P$i2#n+P4o1fKG=PhGVki*+f(3U3bB=4|wmUN`55usYsG^Z?5v z_c2r<$6&c!;HeMI(L3_u)V11eRf`tR@dmhOT9MmrCTC5;*5WAECViy78#66Mn5Z}4 ziCrpq2z*H@6dn!VIp2f!xo&u8JJB%Zfu@tgLrWsogOBjcVYn96=P_(7h|F^7j31QV1`GD z;gvCzjW~pJr(9o$DrX<{wPDoMe1g3^gml?9WSP6jV0VxtCO)aFe66+MVta)tI;%;; z-y&?ov=^M;g2c3U80h_qI>OW16_p`KRNrS?I%%djNgj0VL87f(PZ3#J_o~rzASxWsKE23{riE30IOg;Pr-)qdgGL zUFVE|Doec!rD{*`I9i|faCD_g-tZR7$6rh`nLicfsSo{@AbQP{{?8rd4xjVQ3qif% zK$t!4)oc!d<3hsxvNijl=gBk81rPI&?jH^v_yBC-)Ne^L~Q|5S+VA;M4r{`l{^7A1ZF diff --git a/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_developer.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_developer.ico deleted file mode 100644 index 07bbf289e98427e71132ea77f9e1e8542402c8d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1150 zcmbV|-)mA~7{}jH^aC0#(t|{}1rZ}6Bt#2RcSI%pRd=%e0p?)PMby>UL@&LZ)MY_k zcp7&7FOtb5>~=f!yZjF$5C|ZZO2Of9!0mP)W)wR9Bc6iAVlkxC>BEc-`7T?R zOT&DjbBYx@_l#JPNCfeCe8}*4Jfk-)%4=*x?vw5ir^yQPqETM=_Zd#76QU?091bJ6 z&0#6;VRCa1#IPFWEfvLjv8=p}Mx(FMlBAf0 zic$)NLTEG^P*oMJRtv$G6+9j>6k^;q%Im&W;wulDJw#tup=lZhg8}B>>|pV`3brYK zBTbLX->7lR^)3GCs>BibtRnSGL*!Kz3+qihtn6a0(ZiXKGT3|h`MCT+Z+45X%zsg_ z^h3k)u7==F4;RWxZ}Nk3?qHu$eY3{D(k!KG5-;ATS?Eqr=zY7Uva(>5+4pbNxRtb| z|I?j9#A)jFjKr^8mXx?&|Ld~V_*t4|Y-SpH@jU4?DNU;W&WnH6@?!TyUerk42|7uA Ql3owU)77UQyvU1x0q}M$Jpcdz diff --git a/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_media.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_media.ico deleted file mode 100644 index 50e357ca8be25c82e6d4bc4cb3af5118d83a835c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1150 zcmcJN%WD%+6vl7CKcFkuF8vcMt}06DMnsf=T}c&0BBiN{)@svqQ7HJ>1QlDVP2xDW zF;!|8@c}{w-MbJ=GPT;&*rfBgGo3q#&F8sOg6YE+EY9$o!(7fc-{H*1SQFjN&5WM= z*ttE7?PZMZC+LVy*3PN?w@dFiKFqt1&gjv@wtoK5&UCfgq`6ZcNQ<`3o7E*qVIy!A z7NkNb%yk6x?wCv9UF}v~pXqF|eY=X)@+Z`GK=sIK32M$i`UAEkef)GQCI%wgqI&!d zdhKir=_{eGl)Z@UP~Z2L$)9#r_Yd_>br<@BwiL_A+&BqCkA)PU#8A{?C_L_7N2>1x z`7dtpC%tRpLGl6?-;dzyyStd1N#eunAuQwvh(yT8xA%~~-@CQmvoM`bV5K+--kPhXqLC>#5ei%lf=DGrs=8&GM4q+is&~5|zKY^%FmMfIsGW3T0>b<9K k^2?F3MsuhvY=ZKwHA30ddXC+$=Vvz+$hO9t{tc!54e5t#mjD0& diff --git a/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_member.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_member.ico deleted file mode 100644 index 7d68fa3c81aa70304ec4667475553019f963a839..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1150 zcmd6m%WD%s9LGoS;4vr9UIcIc4ZZ70JShl1pq@Nb+n#)!RIsO_(9(lK3sO_lnB9>lq@67&|u`}YbSeR%pu;sIi zoo9?)5{Q(@{K|{&R7Nx!O+_LRgu`Kw!C)|mKp+60&j*jk1Gn1^m&*mG(+NdUh(J2kjUY1AfL}mePVwd zJ#7Q<-NSVE8nAZ*m5(b@PhM#Mze6HbRTul6g_G7$LT(AX#_^|mB`q7%y4+aDD zdOdVHorM?D)49^QQJ8!KpAHr5gmk=(7s8=2eg&v&BpXxtM0oEnLa?ZNM K*uOE?Q2!I-`gEcI diff --git a/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_settings.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/task_settings.ico deleted file mode 100644 index 619bdf265d0251a668e34a5bd09ece6c0d51150a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1150 zcmb7DTT5b56h1iv^CmqF_%@HzuNVaXM-LU*OBZ?w6+gec5w6?Y;oK9!czkmk@1{!=mpLJwp#4gwt$ zVOqMoyRFU5%?5HQ?sw>C61WSf|n(DL~_9UmW)%jMEKJ3DRd?d@7iON-Ii z*f{+wMGU(Ub0yu2gJvSVRk!GZN1=H>jmySwD^ zcy!c4g$E^@%^EK+FTxks>iqm%)SPkE9E*#Kj`j6*Iy^k2bUICnqKN%HJv|0$Wazq1 zcXxMmc6LUrf$KCmIa$Mp!(j*f5!Z8XZ%=4^e0(Io-*3RDg18EOda+m(`B~@q`1sE7 z@UUZabX4f)-0Xq%&CSi}Gcz+P`lM7fLVMum=0?<-b#R`gr6pQjT@||78|!BMv$M0c z`Y|5+z0hE?Chp<={XMCwDm?M`!^4B{%e~{i1cO0yaB#5Jzst)@5AT0_dlNY5ETgU_ z*Mv1ClSy-HYpcxo*Vk8}0Um1Shc)25Ov9T2EgH1i;8)}G`1JI|{m{T+1BZt5V4&x} zonOZD07nBjtjJ>lM+t*?0$0M>v4F=KkH_Ere<}<=@8D8!5_~(U~5_Ba7L76MJM5rch zgsF1&2q~YUE1#O7%S(r)zY6Hc0Btx1y7>s`MlsNG7|^A`)~P^iW)dzA{6Og0G1|%R zE}L9c^vt72dmHG@0v_jp?k0D3oB-W+6u-4%(3(`Bc0UHLS3=ddfRK_AyOY<7!w#ws z#bI@{9hj;EJt7A^m``TT1>KcNW+6SA4w2qU(8dZ}P7~m2a+t))8xz@H#snY9%VaLB zb2GqPD==LQyp{sbbI3d=fuRamKfJyF+TK_ZRnm!k zet2g?JZFoG@N}UWH;3w!i@0jyW^gOd&13ZSorvT8JgimTlbBB?>4&=?$vgF4aziqQ1{NEsBsjWm;&sKD+VwAG?;tx>?w+7 zZ+F7IVu&)qJ6fh<32T>pMK{0@s=*YLTR4#!)t)E+JpoLDp$#U%1usuI@=x9- S!A}N}V|RK|+X74i2m1#JCXm$t diff --git a/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/umb.ico b/src/Umbraco.Web.UI/Umbraco/Images/pinnedIcons/umb.ico deleted file mode 100644 index b1baa5b622a303eeab112a0b8638a44fac119b77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17542 zcmc&*d5{&w8J{iIqEcm*$6x-1yTC42q6nz*NEFl#x5)*0LI`Mr-0k3we|EK#OJl$g-@aCinYL%lZaoM;xKPd%t5@w;g9$hoYSI zXEa*Yh*K?VEZTsC^(!Qn@2{t|%RZ>3(`gyq;aoJb-C2(O?<3po9nEcas%d7r%+%bL zPK|7{cks7n)EU+8T!iyTCt$V&A1=cW)@w1)7 zS~@em;AJP)a}C^4G)Q|?N2a&6D|1+w|4_b$a~{vWlkOn zX0{#Qof~EU6|FN5TAW!&(%bRD~!v-S<+Bd?^7b7p4bPwb^c{jIZ7LV-6 z!>cBLVC%zRFSK>~zx{4AaXi4EW4qkkp6P{8PTUXUXJ;-$yHXt8162;&5i$V_=8Mac z^uUDK&Pl-T94L&n__+FKA22zv=aXv5Fs|D<9Akg$faW64zA(P&pOU16zQEJJ*ALEC z$Z&X^46e;)=Tcpi({|Oim}~U#x6+EVPtZ|n68YNJTEIQye%i5daEeMg4P4^ z7fKcJlN&*&4jvVI+rVyf+#(jQjlcJaTtN5ab=OM;fmkuf%kv@b+{158+k8hE;$?C3 zlX+1FEz(m-fNtJzquW;JIp}>f(sYl0bsa?qI(_+HMXe^8(^kgMit!l|r)$F=W`2k5 zIxP^Ri8nt|V@k{L?#CtHx5iRXNf z8>cL$%#G1aKptlqd{19J{3rHggbg3BA9S^tetZ8Q@I(I*vidtLnO%f$E*k4D*ebD%s}N8{J}QTP+- zAK2^L&zZeJ`=S03?-_b9bfR@`=33`b{Su*n+RyojQGGod^gZ3P%#r(I_3BMqm=%n% z^h5hUcHRK+YgtS=j56#giCn>_&%XZI9}?l;k;H$Va7{*zFO8q{89ztLEpc(43fhQg zQvv<4o{hLu`5%s7KGfB;>yzNO;rr{)UyM6(l z)(@4n{tY8er*CxW>NZJpw!7zLMVs`Q_^s*1=iIwi`|o=F633G?W~@>=B6iyUHmpNv zSAlVfzKEj{SC05ayuLjce_r`OjBUC)QZlLa3)e8fA=i&_JXrsDjHg$&mBqtYr}nNd zVEqx|K|4adI2v)~Y;)eyezEw8qWJ5p1@Y?kvh;1NuW&~C{=oXtm0?vJ5ANwGTJ)KQ zU50F*J~NgE@YMWG@*r*Hmuun=t>1}X@1I2R@bsYZl9#J*&wkwBqS_C(%;W3nPwFvp z|AaDN%=bp^y;Wfi(hJBkSgcX)qVY%XU%7n{xqleeajowt9D^;Z#$S8?kTOh|ovGTt zRdhI>bkgHN=X$KwvGICiAoP0(f8G6C;^(9H&pkbd?WM-|V09hU&cL5||6Jks&o4aN zuYuX%Q`^Uc^c#iQEOY4&`22!0U|#Xgk4${^-c9~PeFrafe$-hQ(D@PZ^O?|{_RjC> z;nu$o6|-&+dP6$DQxd4Z=J~0{8JBDRgUuz~eRO_m%3z*f$6;srU~#h@+V7Y1Yb^)X zj8*ps!m)F*i?`bt5or*Nnds-M1 z!$)=4lSX&g7h_$$8gt-g#7uiICUA!$Rm5Jy9#v+q#@g>x)$n>~$P|+f-lfdTRmVsJ0IyX5WPMV3j?9_fo78 zU0bW>0lf(B7oK%PMxGJUFv{o=LlC6U7ZA@^AIeh~L;_~>u`md=*N<{c%`d2hcuE7N>~w2RQe zWMaS=9S-}vHHvpjr!5|Otq|DbRUCbuns+$o-HFGy6vWsr$f9B(#zAHRPiHvB*(8*2 z5B-L7N?Y8t-ap&ry@q$O*SYrfg1Gg$yjb*jUi7R}iSNsw&C9zA@9ZjxjC-Czt z;T$EvS7RX0Nu%1+7bCAaXFh4ZgL?z&Y?gHHe6b*=F3bq@m1?i`%G{NUe0cu0411XR z?3ib+r!Fw|c6=R>CwZ^d?aln6+p>b^oqXQ^v?Oq+*5jjW8F3}`Dscqg(Vt?=4z>42 zd-%{$XBzV!ia8T@=dl27 zm;ZH-0b))ROTSHw$@gQ&dAIw*XtAg{^IX7p- zuCGdx#*6P2jd8BmqQUy%_K$B-{C9s_66Y?7Jd>0DhyEAy9%8%T{QZM#9r5-~`Vb*@ zd{I*PU4P@xufaB&26gbyUCRHw|7j6tc9H&1`CoVb3FD_Poq2ao%(}l{{QMU9FGJ@( z|LS>O(>dqntmwKgCpzxU;cgQ8#P4(cF6ST4ft-Iqld-fLM@6EsD&qu}|GS(&CD7*48B#~e1LFWels`!KagYj3&vt!@4__poyucrh5cY}Xq z{6!2p{*-#J?l0YMk@20DGcI@e$Ht#7{|3azGX6I3dVF=+AmvB=-Cr~QCWZ;d`UCvf zS2g&@+rO`eGx#GKwS8!gt5VBYCfUYkcB5ggtR6zey zQ&*6y74u%CPQX{Lf6m37xAT{)#P?~q@541-SXOuLbg<6!`U7*^I`mP%pPjxaBc8;a zX!D+)xyEGtY4?}KGjA8gk8z*Io0EMy>nH>Gw175j&ZmDG;*M|KH56kp^4R%R8F%|j zGJW)A8TYfxGR7rOzMn-N1`S0M_6-{jm5WC<6(m&Nf&JyJ{fIMkoWoqbvyArycY!Z4 zP>wJie7j@E$BywN{+m+kzu&Jh)^A?yi@IK%kd|va(x<$w)$3i1OMAD;SKsYqo8zz1 zxJlDYejLJknQ}dVcsk+^6KDt5G2aJG_8sHz0G~Kt5}>ek$xtp=tr!_Njsn)%3HwtmogtUCrsbhi_%vbeQ(RPF6&RJzQae_ z>;n%cF>`$n72#eGS^(Oq}lfT#hf)rC;~P`W-Li<^NVpzuk||&EL&;-mBY$eW^7Y^2-1C_@R3>H$VpZ zdXIk47FBgNsip*8HhC>&U4)t>IJqtYI?sSPfH|z$a1C z^>usFB2Cg}9Cj#D6VgE9FszfOU>;uyxwb)fc{go?K3CQ{f592Y!SF$AP#N9>RxK-J}vZYQ@4^P=l_3yQ5M9Zd$m6s%z5#I>)%cO+<)ht*I;`eKmVu9`(5%UeqWXi!1px%m&<6s zZDd<|rS5m?p0N+xsJ=37AT2)nvLyH4jrJ?qf7<4F*L@oH)Z^N7o<7c>OJ)Dj29*5- z_b96Q`&eB5(tg-p*{{9Pz!ibtY#+2=Ny8mKpdoEBnt$~8VSD}fiFjsqyRczFYgF@0_jp z@@nRV?Y`%>cph2lab-v78y=j@KeEE* z=a+3a_k>FbbWr>bz*(+!*kh>PsKkxQ~KxEi?{dN zADzj&d(i*L`s91ZGM-;4xqmY2=RcR>veZCb1~U+n?JrG5GO>!_M3mDi}X+E*|w_Qe0NEu;H;IcVDrM6vv@s$L6vd>HK+E zacDv#W5tpW4#|n`thP>avnsk6`nU{gwJdfjEIfJGkXb-PV1sMB!X{o3iOLG+wG0d~ zFA_2n*qfQm`RA`;6-YR|z*ORqPe8!4h4Xha3Y~c|DcNbEQ{prm5l0J07Pd@<7(JI0 x2M=3KRNj-&;PU0EZHio00~5;;K{l^F9fbjlJVg51Z44w2E$1!hV`OBo1^}mihams} diff --git a/src/Umbraco.Web.UI/umbraco/dashboard/ExamineManagement.ascx b/src/Umbraco.Web.UI/umbraco/dashboard/ExamineManagement.ascx deleted file mode 100644 index f713f35bee..0000000000 --- a/src/Umbraco.Web.UI/umbraco/dashboard/ExamineManagement.ascx +++ /dev/null @@ -1,216 +0,0 @@ -<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ExamineManagement.ascx.cs" Inherits="Umbraco.Web.UI.Umbraco.Dashboard.ExamineManagement" %> -<%@ Import Namespace="Umbraco.Core" %> -<%@ Import Namespace="Umbraco.Web" %> -<%@ Import Namespace="umbraco" %> -<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web.UI.Controls" Assembly="umbraco" %> -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> - - - - - - - - -
- -
-
- -

Examine Management

- -
- -
- -

Indexers

- -
-
- - -
- -
- Index info & tools -
-
-
- - -
-
- -
-
- The process is taking longer than expected, check the umbraco log to see if there have been any errors during this operation -
-
- - - - - - - - - - - - - -
Documents in index
Fields in index
Has deletions? / Optimized? - - ()/ - -
-
-
- -
- Node types - - - - - - - - - - - - - -
Include node types
Exclude node types
Parent node id
-
- -
- System fields - - - - - - - - - - - - - - - -
NameEnable sortingType
-
- -
- User fields - - - - - - - - - - - - - - - -
NameEnable sortingType
-
- -
- Provider properties - - - - - -
-
-
-
-
- -

Searchers

- -
-
- - -
- -
- Search tools -
- Hide search results - - - - - - -
-
- -
- - - - - - - - - - - - - - - -
ScoreIdValues
- - -
-
-
-
- -
- Provider properties - - - - - -
-
- -
- -
-
- -
-
- -
diff --git a/src/Umbraco.Web.UI/umbraco/dashboard/ExamineManagement.ascx.cs b/src/Umbraco.Web.UI/umbraco/dashboard/ExamineManagement.ascx.cs deleted file mode 100644 index 77a04423c0..0000000000 --- a/src/Umbraco.Web.UI/umbraco/dashboard/ExamineManagement.ascx.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.UI; -using System.Web.UI.WebControls; - -namespace Umbraco.Web.UI.Umbraco.Dashboard -{ - public partial class ExamineManagement : UI.Controls.UmbracoUserControl - { - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/dashboard/ExamineManagement.ascx.designer.cs b/src/Umbraco.Web.UI/umbraco/dashboard/ExamineManagement.ascx.designer.cs deleted file mode 100644 index 71251692aa..0000000000 --- a/src/Umbraco.Web.UI/umbraco/dashboard/ExamineManagement.ascx.designer.cs +++ /dev/null @@ -1,60 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Umbraco.Web.UI.Umbraco.Dashboard { - - - public partial class ExamineManagement { - - /// - /// JsInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.JsInclude JsInclude1; - - /// - /// CssInclude1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::ClientDependency.Core.Controls.CssInclude CssInclude1; - - /// - /// ProgBar1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web.UI.Controls.ProgressBar ProgBar1; - - /// - /// ProgressBar1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web.UI.Controls.ProgressBar ProgressBar1; - - /// - /// ProgressBar2 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web.UI.Controls.ProgressBar ProgressBar2; - } -} diff --git a/src/Umbraco.Web.UI/umbraco/dashboard/LatestEdits.ascx b/src/Umbraco.Web.UI/umbraco/dashboard/LatestEdits.ascx deleted file mode 100644 index ed85a604f0..0000000000 --- a/src/Umbraco.Web.UI/umbraco/dashboard/LatestEdits.ascx +++ /dev/null @@ -1,11 +0,0 @@ -<%@ Control Language="c#" AutoEventWireup="True" Codebehind="LatestEdits.ascx.cs" Inherits="dashboardUtilities.LatestEdits" %> -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> - -

<%=Services.TextService.Localize("defaultdialogs/lastEdited")%>

- \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/images/Lminus.png b/src/Umbraco.Web.UI/umbraco/images/Lminus.png deleted file mode 100644 index f7c43c0aa3bebb499e86eb744b1e47b9a9445ba7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 219 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv*!VDzYUPT51DfSXiUsv`EJiLsmQh${X`~(Vd z7I;J!Gca&{0AWU_H6}BFf(8LTA+A9B|NsBf`>KHqVn8Lk#c`lIrjj7P;QtIyw;Ol? zc?O;?jv*Ddk_A|pTm=*lC~zFVdQ&MBb@0OtiQr2qf` diff --git a/src/Umbraco.Web.UI/umbraco/images/Lplus.png b/src/Umbraco.Web.UI/umbraco/images/Lplus.png deleted file mode 100644 index 848ec2fc3bbaab6345864c303684ff8a86559cfb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 224 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv*!VDzYUPT51DfSXiUsv`EJiLtR`ZJfMMF533 z3p^r=85p=efH0%e8j~47L4yFF5LY1m|NsB#ebqn)F`$y&;y6$pQ%R6t@PCG<+YP*c zJX22>$B>F!$pS1)t^$e&6gZd#)XV}^6#`leL>0XheB5$;*x1}$U0qr4a0#d>%H> zJR*x37`Q%wFr(8NlNmrkwg8_H*Xe!L|Ns9N51QZs6k#d}@(cdY@N~O@7mz3J>Eakt raVz;p{QriB|LYZwxwcz9n9R-~pRXx?mgVYJpb`d8S3j3^P6#nkpi;(?AirP+hi5m^fE-m% z7srr_TgeFwOpI(f46|1#2yiGY`Di?KiU>FVdQ I&MBb@06e2A@c;k- diff --git a/src/Umbraco.Web.UI/umbraco/images/Tplus.png b/src/Umbraco.Web.UI/umbraco/images/Tplus.png deleted file mode 100644 index 2c8d8f4fd38259b2ef70fc63fad505fb0a0f55a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^!ayv*!VDzYUPT51DfSXiUsv`EJiLtRLI=-jc>;wv z3p^r=85p=efH0%e8j~47L4yFF5LY1m|NsB#ebqn)F`$y&;y6$pQ%R6t@PCG<+YP*c zJY!E6$B>F!$pS1)t^$e&6gZd#)XV}^6#`leL>0XheB5$;*x1}$U0qr4a0#d>%86hy4NT6+w`tp00i_>zopr0L}p{>Hq)$ diff --git a/src/Umbraco.Web.UI/umbraco/images/aboutNew.png b/src/Umbraco.Web.UI/umbraco/images/aboutNew.png deleted file mode 100644 index 94e13801670a834363ede866d1936b4c58b2680d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1178 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m=!WZB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxKsVXI%s|1+P|wiV z#N6CmN5ROz&_Lh7NZ-&%*U;R`*vQJjKmiJrfVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8EkR}&8R-I5=oVMzl_XZ^<`pZ$OmImpPAEg{v+u2}(t{7puX=A(aKG z`a!A1`K3k4z=%sz23b{L$o& z6x?nBx*mr=^ns4i2SqDVG{b~|X$QoFCt4r}p6pZefN8x5n5e^Uf6!xKU=;9laSW-r zwIuj1Z)!)<~0a62ur9ls7(;O;ow%l$m{$gZheLOdREUX-d?9; z_wRmR$8}8NYrl@uQRDdh!qXxSi)x$>Lw^>&8cJ9cb8qaeJ@SjxIfZRzV=ont9Rtu6s;F0S}}qRi5L*LF+K zIn33aFOnK_N_3Sr3j3P9oN`Oz*-3?K-qi)>vt~5&=52qtsJl%0_7Md}6OIQ~DHZpE z`=Y+dU)Pk-NlNfh)mV1r(o~P+&h*vZt<9%qyGZrbT&e!|{i~|jmlszXM4o0OsQifS pXSAu}zL)&y=8tEFf0gtZ*%%H*HF3EY&Z!5L7M`wtF6*2UngF(!jj#X! diff --git a/src/Umbraco.Web.UI/umbraco/images/arrawBack.gif b/src/Umbraco.Web.UI/umbraco/images/arrawBack.gif deleted file mode 100644 index 9d3f0ca6869d1af01a28961dd171ec57bc826569..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 834 zcmZ?wbhEHbWM>dz_|C)t1poj4hm#=wC>RZa5fcK6KUo+V7?>DzKt2ZL2?h>%238Ik ij|~eBHggCo{R#gdf4GrP-f0R);KHNb68dZ`4AubXbQK2x diff --git a/src/Umbraco.Web.UI/umbraco/images/arrowDown.gif b/src/Umbraco.Web.UI/umbraco/images/arrowDown.gif deleted file mode 100644 index a02ccbf6f88113a7b463dae1a297c8dd96b2eb2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 832 zcmZ?wbhEHb6k%Xz_|5dz_|C)t1poj4hm#=wC>RZa5fcK6KUo+V7?>DzKt2ZL2?h>%238Ik ij|~eBHggC|{nlQ^ zQ9mL`$s(v?QK8*dcWdp=?96;WX1u$V2Jt1&H#6US@B2L8^S+<*swjtqltxRyE#`W0 z?zsl{Jd-nK1XAjr2;!-Za6Z@Ha|iz$&u8Q=oB6@w@R#8GGT=f4UrTmy=H9n^560gf zq96DdssfOJ;CDKui6BtGeH9EL2(I99edW-xF`OHF^y>J(0#uTz5$9ivjW3@j=>u9g zjGfUU_Gg#j?%4ZNrP565&%K&Y38VzRRzuBz1_T9jdb$pSE`DNrpMkX<5;rC-v+}c+ zN+wZUN)Y&xf>eB5035!^-Z+t3FQE+!;KD)Ca=^Vh`Y2KU+4CbUdStk>v-5NF2vUfM zPqYyoh!PBfu*&FaDIr9b#8z}6XO^)h6GL><5bQudJbGp1LZMK|EqWjXu}HWA@JRMT zjUla-V3V5OECpv;v1-E(tn44a{P!tL%Q)&v%jV0ME?%irDw-;}Fc>KaspLN~2K6#j zpQA06MY|bBl~`s&QQUm`6t~ZhVEw=*By*H({zrE{pFcthI5;`xk&%vQxay#0+gMS1 z30gvlDs=1GRh&I>5*@4ikcdUFZf*imqZNh1r`;NPU~p2<>;RgmDs&u%89f1$)+|EB zmDsTB021jmm?h9=#t_nDuq-R3`JC&F6u6C!A`hw_5UN-4Il2W2hGrx6@7#xM^Bq{X z3V3Gs!{gu3N`0ovMjw#ObSu!60L)|xm8y;Mi*a}^m6Q^)ibTi`u#xuIl`^K^Oym3P z6nc7lu4{sE!*v40h}|a-Qf+a(Tb_egxQ`E(h3wz}GUL}_rc)?sYmk39iK*9b5jTAd z4sIRR6jj6M#V3fx!p$yfRzlHHuSLKnZ(#1-1maN>YPksa@f?a@K7h+v96Ne+TYG!E zt2A!Z$kox)wQ4P+s)|^;B&sGjs~fY$xdGc|NydcfI8|hOGWk7w_8#o+&Q3J;{SsgR X9tgLBzw#qC00000NkvXXu0mjfzlxe* diff --git a/src/Umbraco.Web.UI/umbraco/images/back.png b/src/Umbraco.Web.UI/umbraco/images/back.png deleted file mode 100644 index d0ab2e28b5c3d1a1ca487b3cda111a8b488903bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 422 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLlMCm9|n$4Ua_ z9(cMqhE&{2dce+T=I7^UX3#CK&>(RjhL2B8uS_m)0)qnsBdf(siJ3bp8XM-aYA^^m z@chZ~@$rd~sgY4oTp-oJkn|v(&8zssN5dwT1}_Gtj>cZ@GwH0a6Q4h0=TKntixZgN zFUVmrW2WQ}Mxf;E>D~NnGb$s191abJYX=#bY;1V`FfblDaEwnZF(vKU!-t894?sHW z#1*O@$y09nXht;x5{fj&wF_>sv$e5zyBE-hn#Wc}l t%8V0iiUJWi)AxQ5)n;av>vwZjPqg5g$l+i<$%BubuSQw8RX~x!8UR(mSYiMG diff --git a/src/Umbraco.Web.UI/umbraco/images/c_bl_label.gif b/src/Umbraco.Web.UI/umbraco/images/c_bl_label.gif deleted file mode 100644 index 1f55e5b558aec2344b7a49fa423dad066c8c2d09..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 948 zcmZ?wbhEHb(4S}H<0y-dP zgYp6c$4myz2nml33670y!h9|(E+`ysW07~-q9B-bv`t);t0tjw@$o(d`4k_EOAn7U zt1B;y*)b{U^dwf+5R(Z3O)j%c^R-$YFf3!`U{X3TM`0lY!+dt74javbj16oei@c_K zZCDY+#LN^B(4gSh)Z{zWOhG{50Mj})RTj~1)9kYw802g+E-YBc!p6tt(!r3hh=r4# O&%(lTp+QRngEatn=WZ1M diff --git a/src/Umbraco.Web.UI/umbraco/images/c_br.gif b/src/Umbraco.Web.UI/umbraco/images/c_br.gif deleted file mode 100644 index 2674d46df554b311b4b2242a072d52f90e28555d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 240 zcmZ?wbhEHb6krfwIKsei@Zj!$|NhOGId%X3owsk_nlfeLgr1&-b7pVYxaslZTZKfd+p)63hpFTZ{J zbpQUH2M_LEv10L*DHHqqdloHPaQpVH&6_uMb#}D1wk(`8dqPjo|NsC0{rkr-3PwX< zsD*&yPZmZ721y1Tki$WFf`Q`+gFc6hh5!>QJFf(f!GZ<_$7T*uqY?{&q(iN|nsFO6 z43%B`Buw*cDhe05O;EF+^rphm;q-K-fT|LW;FPnIwVfaJtO!;;KgFb&saB&z?c#hc zgN&L4#zRbuoLmAs7KFS!z{#)RAt0dW%(jY8-p64_!huF6$u)d64vkI?-RygUa&DJo zGcgxk-!}Kdw@j8T@%lb9E_^!}y}M#B-@V@poSN6BF{VrWIpD~^!7HZm=k?XqPq}8O z8Z?~RS=`3PppqeGpl~qqa-3vOTJ80Ql*?;{cWmRm{e_WvTmJWR3mO_eKHR{}#9$2o Di-wo} diff --git a/src/Umbraco.Web.UI/umbraco/images/c_r.gif b/src/Umbraco.Web.UI/umbraco/images/c_r.gif deleted file mode 100644 index c3976c6282cd5d4f4ceb63126db58a44f8ed8b49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93 zcmZ?wbhEHb6lGv!*vtR||NsBLeC6!pCwJbyeR}ZV?iDK*Pnj}t(V_)ytt~()uo48J g_>+Z^fq{!b2P6bCgMo!hU}U>Z0gLM-ng9R* diff --git a/src/Umbraco.Web.UI/umbraco/images/c_tl.gif b/src/Umbraco.Web.UI/umbraco/images/c_tl.gif deleted file mode 100644 index 836446e39b130faaf123cebd4f3ac99d56cfe586..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 242 zcmZ?wbhEHbJ`*! z!*}XYlh8-0Ez=5bdW(6U_`u$pH23yXf%v0#^$rS3BK1uIb)rfFqFr5WRoy*ZO8q@N Z?PC2?`9*o`c;?L$<%d;Iv;+qdsNfBtyl`0B znKE(a%$fW5?|l07?$4h;Z{NQ4?c2A{pFjWm_wV-Y%ZnB*c>m$~%U6&3`+L5A{d)S$ zvG3o%fBN+D|Ns9C*l@{j7$Wm0)=*dx16w?%9;+y2TaM6-w;+pD0!ZMxl8jBW9U$jMvf1a|Z#<62d zj%`s_VbEmVbzDO$M?vGX3g3Bmg==>;Jn!B(SFft|{JDasmcor&7oNR%p7Z83(-Zek tpLL!;zWq$@$=c5e{PGU`9JU)cU+~LuGOEbA&GC42q_bM{6e}ZxH2}a8%Dw;q diff --git a/src/Umbraco.Web.UI/umbraco/images/close.png b/src/Umbraco.Web.UI/umbraco/images/close.png deleted file mode 100644 index 443c804940c6900f7a0bcc1cf9c992a8243a6d45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 389 zcmV;00eb$4P)p00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz7fD1xRCwBy zlFv#5F%ZT#o88v+&|ah`!9$ON*sJNoqr7egeRPZQ@kWJampCXKw-=G$pA*VYnG!)!sd zHl>&uvt(lDp~2uKt?fdEph=nqt4*yaIA_e$E;^ld?{0*o*{Jt> zCSJcZ6s)6y&jR4KG&l*N#x6Dt9#gCJPj4Yh2&+uSudZttsnI-$tdUAVp2iZ;^V546 zptVOrL2Lg|l{w@m!DJ}SXg<8ZhMhj%;>>8|?C_!cYi`aymo2|O{(93L|MFas)rXfG jsWh#-g#7Ct{1#vU>_N$l<-!@g00000NkvXXu0mjfZ^W%V diff --git a/src/Umbraco.Web.UI/umbraco/images/collapse.png b/src/Umbraco.Web.UI/umbraco/images/collapse.png deleted file mode 100644 index 9cb8909df2715dfafb15ee92d8d3d2eb93c5aa51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 388 zcmV-~0ek+5P)p00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz7D+@wRCwBy zlCf^WFc5~5KnP+e8!Sw8X;rE13_KFg(#Po5H(;b4SU{D!AThM6S}8?Hj)QZyW7BhN z1%`;Gr<3)c{hjSQ+kc#`US2m|4IABFdeeueLp?*XS!Y*7t!NfOex6pj)Y^A624_$y zRR*b2h6!wZ>WjrXY;_nkPzxb4NEoA(5J#Wda=95#?oo6vBB@$?hUi`Zx2r)V`aECR zgs9yInwCINRH@KQlq7sKzAF=YgBvX+x~md+hU3+BKaM|C4lI3VsW%*UzQ5Kv)A8pU z7vk7KECNS+Ijyuiv=N}@U^Wt2a-{KQ+!UWwc96iK)P|;HYi4&pf4ynjy*w3u(Rf}- ig<)ic|F1vzE5HEz7TL$?rb^NP0000LG#kBaSCzVZLZkDpc?`hW27 zi66iJ6;3+);QjxjH~!Dx`~U5?|M3|!YnIzZ?0|O(g%gmWGmr6)V zDl#!J9#PO&V|}vO=!7FH15=0NyxCKyPMyx2G@)aU4M^#an>UX-+;UkeVd?UO5hUN< z*w`4&bwr$9l10FQ=aUc5n>TmXDA?_9QCJMtaO1{}iXVy$8Vn%WO*wDg*f@kLFo=L% zktrr7Ho+3c(^E)DV%C1Z!o|QMzF8r>mdKI;Vst0D*AM)c^nh diff --git a/src/Umbraco.Web.UI/umbraco/images/cut.small.png b/src/Umbraco.Web.UI/umbraco/images/cut.small.png deleted file mode 100644 index 9e936845975eca508a6af6255653ece236d2b8df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 472 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl^qXECGJL`I`IRn618r!2~@SeBSwclyMk=FZ8CcB|K}UAJKAc1H6>yLKN~yJ7c% z{d)qF<}6yWeA3KK{02>PmTYS1oRgZ@VBu8~o7MRL|9}18iRXcKDU<~H1p~SG07FcS z^jo0XOP(%{Ar-fhCb%;*Fdktz^5{{*G6v}m77m384GhNm9Vc}dlo**fR1Taz!>;mE z;Sd8Oi_nDhiDFa5pC&P71C?!Hkd~fo!!Le=qq2d4iTlLq4<`#wHI$xUW)W~{@Q>>2 zwV&^@omAp+FCpn=1h*?PN;xixb;M*z@>18D}E4tWAC hPjdKf=g`2&koiC&DES@VQK0V_JYD@<);T3K0RWk}yh#86 diff --git a/src/Umbraco.Web.UI/umbraco/images/date.gif b/src/Umbraco.Web.UI/umbraco/images/date.gif deleted file mode 100644 index 8f73cb39a6cb62775bb7f9b9e625f87b79f9c6a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 253 zcmVhFn1O(RrJs5>N;Y!lF>P2#6(4r^8?}FqjUU8)kgP^w+004+M90xNXfhe?c@(f% zB$WpyQ@LaUCIS}~gbErIeo!O;6dDy7h#edpb0ZuF2$2y0iX|HX8XW@zW-6wqCLsVj D1txDg diff --git a/src/Umbraco.Web.UI/umbraco/images/delete.gif b/src/Umbraco.Web.UI/umbraco/images/delete.gif deleted file mode 100644 index b39d476becef1f21df1d34a692e639863dfc1542..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 512 zcmeAS@N?(olHy`uVBq!ia0vp^JV4CN!3-p8*3HQUQpW;(LR|lY!LJXWKHa?j=i9gc zfB$@W{P63m*MI-~dA(!H-^ndsbE3Z--ShdJ{ueR zWaY98f^5G&fBg3J$q`oeL$XrezkYtTeD=fa=)XUIo|cn8$-?yJ%H_w+6`!tNc)M@+ z6$XZz#W}YtOaA};`)>d4&#nefS1tZ{{l?EXuYP^}@MPZ9udiSJ`}ePBRZ0cWM~o#w ze!&b5&u*lFI7!~_E({&4vK~MVXMsm#F^~qaJs8|hX4?T7DV{ElAsn)-2Nk&wC5Sj& zthT>beQLv+C|$LK&;B2`nwFZF>(-tS{LFGy9b?el)pch4tG=vS<bUO_QmvAUQh^kMk%6I+u7RPhu|bG|iIuUDm9e?5fti(of$39`sVEw9^HVa@DsgLY T*|A0wsDZ)L)z4*}Q$iB}4f^h| diff --git a/src/Umbraco.Web.UI/umbraco/images/delete.png b/src/Umbraco.Web.UI/umbraco/images/delete.png deleted file mode 100644 index 08f249365afd29594b51210c6e21ba253897505d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 715 zcmV;+0yO=JP)C4}Mrzlg<+1Y8PEBfUp0jJpx4B>@E+cy3`^(Gw`Mf+2&yxZm<$to~Vpgvg&QKNR z_f#1(r6svZt%iF?s+n<8X?B&!h3g9Dbb8_=MX}!;HiQSAh`bp^WMl~Z-44teO7W_Y zV4thSL{h;rJY7!l3%5J4H1!tIzB`Dv+YxO(haWeausGZYkI8^hWj6mzo=L0{%;yxzh{5!Htr?51 zvG|W62MzC8BZ76hRpCyO2zOn<%e)K>NHge!-~)Ap33OdWw6hsLYbCxGNt0%wk_2z7 zfyYvXheSG)5HRK1VB~%mq7Dmurw#bi@hEcOr3&G1ZiF*$M=&9nB#VNf&Q^r$4G5kp zTURh&s)E0%5&hyVD}sp<72~zmAY`Y(9aqO6CXF%=zFHGzO-A&I(pE}v70YQxCPJ{Y z4L+?5-crdLn3ZRPEs!A4ehEY3ZRpL~w9>@aMN+{F4dI@v&>(QDHQum!mG~E^$OS8l z!7?%Uwib*ROP67Hw`ika)gX-(8Ia`-u_IEhxG7U<13kSsMW+$lbb2dUMm5p6pa}cjgA+U$^mJ^AjD?&bdi)8~y+Q002ovPDHLkV1g8IMc@Dc diff --git a/src/Umbraco.Web.UI/umbraco/images/delete.small.png b/src/Umbraco.Web.UI/umbraco/images/delete.small.png deleted file mode 100644 index 294830dedaada27b762744c665d6c8300b79c023..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 374 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLlW>qMVxHtH|!)gP6o-j4VA|NsB%EqT{~dId^?{DOg8WWex1 zIaU%Vx53lJF{I*F(gSy92F4=?PM>U2Vi0iPS#xHMh7w5Nz?n0r7*dOwI2#y}9z0J- zO=IgWXw+COt|_XzpTQ5eR6Q+u13&hD(PDXpzFxo&?K0170BWje(uLy|S>iy}bj2 zVZjFD19N$W7&#Q!LgpKOVrCIw-u0c4iHAXI4|o3hBevE+w=;OU`njxgN@xNAO$mju diff --git a/src/Umbraco.Web.UI/umbraco/images/delete_button.png b/src/Umbraco.Web.UI/umbraco/images/delete_button.png deleted file mode 100644 index d1d6a6413568fd9f1fa4f971500d32d1bae1447c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 582 zcmV-M0=fN(P)p00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz*hxe|RCwBA zT(;-Ju}gOuhyYf4$_zPuYyT4mfGU|F`kutvK1_7`ar-h>Qz5E>3??w=&+oTyew;q? zvN`|z={-O$9Q`=E@5AcZfB*dW{p%-G_1{0g#p)+=nA!a}d*WqF;kV;Ee|-P(XU)dHrwOO8$QTDUesk<{ta$$E#mIenQkR zp?K%-A0|%D|382Idik1(gPmPk`ty%hKR$nf%R_wm7l{6TeEXrZ@ZE%_|4b}`!Ql+7 z?C-iOnJ=ASU}FCF`!^7Q><8i>KfnLIeEQ40iNCL1V&diHZJ5NOW6WZ>_s6`+jMFP{re|owEvcr`5u-IVKb7H1_1&L0Q>+W UEh5#;VHkeSb~ev;+~(%I7&B1|!CaIGvW+erLFR=fVss-;gh=|an`M|6dTSRF-NXpI z5wFUh`I8o8$ShpiR79M}bcohwOr7oT%(?ZQ)kQAtg_rM~kN17P=Y5_R$z(Dw5{a-R z6=!8E#wmV=!h+iCl{cRP$+FB$`)iwxCB0fI7z`?`R%;*}4nrUi;IxoPB(RN*f&1ov z+a1`iYX83F{Y=vZ@5aZ4wA1Ni{C@u(mSyFnHgPHf1PQe=OWJJFm=;}f6wG=zxsC0% zsi~S}c~8U@2J|Fj<-MxoKTVKg`}Y?GqB(`v8{zcIw)@#i!}Q4onl ze@aE@>Y70y5FvLo5{U$8S_+CoULqprpYMPLxkf*AJLLunCU*fnZ=H}+XvP$yJ zgNP9fMxoOUL#yR5F~PuWHeqLH3ne8bSX*1e3DpFGW3;vwqOY$G6&3sF=`o?HNrc8m zKWw&LNTmj-)mf?LoSd8#!!+3`#9|aiMkWc9Ww=}(_-bisJk^73-z=jY?-D1_Zz7QMYi(CZ(erlt~dx%{L*=>WV74u=E8+LQ*Pqr*_C zL}+V!j8HI!iT8F)&&}Y_w+EphO+>O;n4Fv>bE;u98sT=kVX;_<28~!)p2zKa8P-b| zK;K=3&iMt+xh)wd0Aw;5Iy*Zd7K_2#%g@Qri&z5Be%MK~7Wi}taZ@%H?IJh|Hk)aF zeZ5{U*4NjGfHC4bonpc#50W}L4!&s*@bwDtFdMk&0)iFrZM@8&RVr1qqobqSY&M^@ z$_b^CVd`pM-TxRkCx>j1Y)}|DSc5W8!%<`%75)AFft8h&6!rAr88D0lSB+&i?mc)h zxT;7<5b_}Jpp>}N(oK- z7eSe5^a1%H(}k_;eze`%%c((vX5l8Mr|muWJKy=vxr&tXGJu-rc>(#*PF;$kpxtgG z7K;gbHBXIF*o;;r&5`y?}uUGgLn0srel8obuc}Bzf~v{;)2k`%E~HCGw~#! zH=Y@q1&J&L??AyIY;PM=;y1qr8 zADmm$0<^1rhGy>a`Stz@^MKc^#dOgsOG{@T89$MjD?M9E^=P z$k49cQt)x{8{GC=(k6pku0U+iok2w5Sgr11=$62*hKH|}2FyeUlXrB=I7QS|s@=fa zT0nEq1)viI81C;yrcM*pprILR!X_Z=KqnCeft0A-y~p(b1Ca0~sVfD3u5V*~w+j7w z4UAueW$WN#55AwJ%w0reBp!(VfDs^__zrQ`K#p-V-4P5X1%{JhvxdmpWCH}`LmZ_R zGz_TKEJUM0v|jb__yQ@Ugu4b!q6Vv6MIggbu92B!71ZlIjAzFTW@leud3j~NRC@YQ zh?XYh8TxA-9r)KvX;CZF)D5jg$rYh#nldvpLljMY&J`C!PP?n1_1VQ60R{1fy@8^002ovPDHLk FV1n!}VO;hzONwV zi=~t{c@z{BkWfdk*=%TRY(!E~us`|~SDSA_Wh}+^_7}{}g`n3Lkq)aQW<7XOCYWh>{=(SXo(t)oMK_c*ccflBcw7;o#sU_Vym4tgMW3AH(wU@~JG#ZiB%PD=#nC zj*N^9odZxv>HK^Z&dyw@s8GlWj`H6^g6T>B9K98EW+- z^z>w%`vc(hdecJEjg!bq0TUBIUEMm-@*ornT?p_?fB^swfePtsItefU0000qRr diff --git a/src/Umbraco.Web.UI/umbraco/images/dialogBg.png b/src/Umbraco.Web.UI/umbraco/images/dialogBg.png deleted file mode 100644 index dc8fde7ce69f75e35d0444c0e901eaf30ccaff0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 255 zcmeAS@N?(olHy`uVBq!ia0vp^Yzz#ncQ}}UtUlw6k3fng-O<;Pfnog#bJnhxK)z&& zYeY$Kep*R+Vo@rCV@iHfs)A>3VtQ&&YGO)d;mK4RprVPME{-7;x87V)fytbzug>fddy7tk`hj#)ktZ9vryvAwIY2!3u!| zD-KMk2zao;KtVwvKtUlP!l57{AR)lv!G{kU4m^0ULSe#+12r8}5;PS0XDpo1QQ#mU zuwli700)Bt1BHTwfPesp4;us&G#vhe0RthR_>+Z^fx(192c!_>CkD25hgk(4I#R7n zGXfS`C|Rp6WqK-dBuy!mWz$D1^~7(gI53m diff --git a/src/Umbraco.Web.UI/umbraco/images/domain_on.png b/src/Umbraco.Web.UI/umbraco/images/domain_on.png deleted file mode 100644 index a9e7204fdb8f4f8531c7f2195f57d088f96a67f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 472 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLlfCD=Y zC45dH%f zJ}j8guwcaoE*GxclB$rsAq!R!iod^GZsGh@L@tl zfP#WTfP+CofWw52f`WvAfB**t4TrQdY5&2X&3`q>cLpUve!&nCqDh9Nr57rIdMA0h zIEGZ*iaCE$s98aPC4i&tfmpfkl_PJuEk6CPpDrl4`jYsncwc#iSNccx_-Gyd$;IQK zH7`2hLb<}{!%R2kzG5zF)crEMu!JclbB6V)YM~_SyRA&D`YkW}w^hA7$@FPu-)<8NX5Z#1H%h$_S)ct`IDz5hJmtlU TIJV0JUB}?*>gTe~DWM4fzlh;1 diff --git a/src/Umbraco.Web.UI/umbraco/images/download.png b/src/Umbraco.Web.UI/umbraco/images/download.png deleted file mode 100644 index 0885c935e4b629033e74e59e0bdaf0187dab0982..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1420 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvv ztM~P_^2{qPNz6-5^>ndS0-B(gnVDi`U|?=$;$mvx;$-M(Xy|Ha>Eh((>}=ua=;mbN zW@K&#)9aF-T$-DjR|3es=X0=p1Ch=dPjmIeW<)fn|Yn-R3I@^|Nl)Si==CQ0yqqSk7Zpn+F z^}Mo7|JZpHz4zRha7J%BY7+!7Th9(Mh< z=X6_qQSkX4&L)N)!yU%*cHdPQCvLs{_QLntih1mX+g4qD<#wgXBhJ|O^2z6)Pox0{OFcDPfdqic7k3)AC|5?>$tTUgt-?u`=R zl3-expt_%F`8STEUt()2ehIcZG5)Rlzt$u7<=!~|u>PY-7fNOI-Z~5G+z3(#a=6&ywdoelieU^UNx9tYz&jpGd4H)l3*+CIpEuSK6`fS&QbJd%>kyHv^_} zsvOw%c7@c1gxT!BOC7!x-gsGJ*6Dj^;kMk3h5L5~ ld3Sa*wcpCEe?FgyhhfR_w%C>#TU|iqqo=E%%Q~loCIAGu4X6MB diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/Bold.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/Bold.GIF deleted file mode 100644 index d6a9cc2cd4117526156fa29c06156ba5971823c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76 zcmZ?wbhEHb6k!lyn83&Y1dNP~ia%L^OhyJB5FaGNz@*&Mzw-23{>3a?E=Bm(Rqw7V Z4AWqey4jFcx$b3$*C(Ir-+DzDtN~+a79aos diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/Center.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/Center.GIF deleted file mode 100644 index e3f2414e0bb2efdeff75aa81f48ef19ccc0fc832..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73 zcmZ?wbhEHb6k`x)XkcJCaNqy~1B2pE7Dgb&paUX6G7d}%E&VG`zvW*%XUnbb&G%&5 Y_}dmLccpaB%UG~3A-b$6jDf)#0K;q+)&Kwi diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/Copy.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/Copy.GIF deleted file mode 100644 index dc146865c5952aa48b82fc54677db621322465b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 263 zcmV+i0r>t$Nk%w1VH5xq0MrKnGD>2SrMb<|)0(p4GF`Q=%=coD-uwLia-!n0ztXb8 z>SB7D(cbRC&Eb``_99D}N_N7cveI&z@*+Zl!qxWT>hhwp!g8A0g0kXLg3_YG@}jE3 za+1=5r|rPV)BpegA^8LW0018VEC2ui02BZe000Gq;3tk`X`X1Rt}Ll2NgN5Sa@>ct z&})Jp0ne^?pcEK^!b{_DFiw_VrSR(z8m7&Gi9u+7k^;d~3fw9c#^BIo7zl(%`OrmN z*0>5KA}5&{3=It(2OS6o85w+K0F8|tI1CgD7JVrO2Z)Io8#2N9wc8v&jt5O5Ex Ns}n7+u(2f}06RB$bN>JU diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/Cut.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/Cut.GIF deleted file mode 100644 index 4e9a70b6e297cfa0744d581c42868b35b0a7a204..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 187 zcmV;s07U;sNk%w1VH5xq0K*3W0z;>dm7`y6hp@H0TzRe{O`Us&m(k6|m7uZ$S-vl0 zv81W9YJ#%8z`z1cvH$=8A^8LW000jFEC2ui02BZe000DN@X1N5y*RH`2hYzS9F#DC z&mja;FrrU@G*7b_@UslKvE>g#z<@B2axx(c_%I2W2u49rARu4_N5ZktItZW!fI&z+ p0E8?Jvj7|jibsJV{#*tC#rVODXii?U706WgML@)pV diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/DeIndent.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/DeIndent.GIF deleted file mode 100644 index ca939c639d913c90f44bb465fb8789c571e7d5dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 199 zcmZ?wbhEHb6k`x)IKsei_R_Ok_uiks_V(DB`)4jc%j-QKk~M4M`VU?6&ji$5b4Xro z5kAc{s4t}Ho`2;<+qgxhLA}x4@4{LhPTlabY3g1E28RFt{{u-}K=CIFBLjmNgAPam z$W8}Vmj$YQDVcqT1XYhj>@}!4SCV_bV%_^1P6hrG2SitY={vDxQp%O1I!Bjl8fXeE fK09&A!vGx#_pXOpt8F)~ziD|oS^xeL0S0RTaBxrV diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/Italic.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/Italic.GIF deleted file mode 100644 index 8bb330bd0bbf61fdb5ce86388ecbeae16465bc17..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79 zcmZ?wbhEHb6k!lySjfl#1T8Hs)z#IEjEssuS%3n}3_2iDkURsE!kqq;nh!jN=5Xl<{ELAJRLi`OvPeCwL9EE`(0V^O727v-Gs1O*6fC9h;473EtqJph4 d4hzK!aZXzV=z)RofG^|J8xR-QcnMhd*5;x8LR=sOCeMM diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/Open.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/Open.GIF deleted file mode 100644 index 813ad860f49348e10d20fe59875c948ce7872052..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 132 zcmZ?wbhEHb6k`x)Sj52a|Ns932M$zMGqkh-*$i-?_>+Z^fq{`h2P6Vg>%iqgGWG|9YRn38x_2iMb*3YOIbQSWv)`l33S$!TF}fogAT* a&nItM%$X9BKXqkJD6?jvPn-$^gEasmGA_UX diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/Paste.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/Paste.GIF deleted file mode 100644 index 1b45000a0140b8f9e04c933154c915f4322d7533..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 286 zcmV+(0pb2fNk%w1VH5xq0Mr}+LNg;3tk`X`X1Rt{>*It?NM0VL6(E zVp#@75XZ#A7$__!UdC~NbOwn>K!P;!f|hLd!H_&o5ubBI-bl_Ku=A;)xD5t@Ks^{# z+MN7ifk2od9Tj&Q6dVnQ7YGCeB^rSW3L6F-dlv)<1qEp%4jqtY1`CG`6a;PnBn}E^ kX95eB1O$f^sHqkY9smLtZoQ`}2mr#v!!X9j$Rr^EJMbBDr~m)} diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/Redo.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/Redo.GIF deleted file mode 100644 index 3af90697f0b74d0b6b07b585614a6dc67ff87f1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 169 zcmV;a09OA;Nk%w1VH5xq0K*FaEl-kjqwqa)*O$8QUUsNeit)O{)H!9gj;!cSf8=SD z@4U+CH(H=qeZQ%{>Hq)$A^8LW000jFEC2ui02BZe000D5@X1N5y*TU5@mkK(Nsfb@ zh`52taWqPx3Yb$cP21Nib_4)BqDn)z diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/Save.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/Save.GIF deleted file mode 100644 index 88cb06e44645bbb4ab96db608cf713699661f596..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 633 zcmZ?wbhEHb6k`x)c*ekBV_jixmKWeZKRxYCWW54b-u9-t>RMOl`FC((>pRQKYl@|1QF{6rAOB4uVY?ENPue&wwsBhI;XX?1vJ7S-G&<+#d%_=UdG~Cz9I4&2oUij+WHxJ*iy{8@?yZCg^!BeZZ9KLe* z&86F~ZajE<@9BqgH(x$@{_)6(bKida_Kq$zv2o9+=uIzaEw1n1y!)709{K>+|z>v?N1JVMD69@Kd4f##YEv;>>S|+@?J-NwY@v*_&Ozrtx z;(|e0(V0<6;l5rT_R~yFjSY0QHB^=4q=fl-rpfAPs)z{*@^Q19JGom=J96~c@g_qP zk>1|^087?s$|lS`{SzZ2Le1=^2`ESkrR1e1#H4$>xmcWd_x{7jPtC?A5@*l(v#?Cl zGZFl;!L=~qz$8v7&N+^+Cbe=+keg0}eDa$k~;=dBDhUpr7IMBq44V25SIkp%n@M diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/SaveAndPublish.gif b/src/Umbraco.Web.UI/umbraco/images/editor/SaveAndPublish.gif deleted file mode 100644 index fa7f4e61f1233be6267ae879d8dadb63bca3f5f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1094 zcmc)Jd0Wy40KoB|n_KQFw>{OPcKAG6#+viVt;cPiP02!CiXxhLOu>GD$2?$w?uk9^P3-dqzJGlofj8YfkT!rVV9)?w zJ|zYCnx5_hDqHI1Q#>-ExkXRx>E4g0q>5k6ZEWp%f_su1a4lVe`F&Lkb~?IT27@<* zhqZ`DjKf2w1@(G9QK-^vwDVTK_J>EtO4;mJ*?G&U{19kxQw4h{Adu`0=0Fis0?(Hqg)VB-vh!!{3!%xKE8OBL+k;7Gf7NN8DzFGyP7 z+$&>=xV$MOX@|fVyOT-Ft>PyoO75mAA~T-m;~(ecE}@GVIXLb&zGc~%8Aj`0IULdC ztm5ynN1~|D~cxlUIJ-Q>vjLsFR-q9w@Y?rBC!m;>97*F@m)N< znjyirj$+GKw^e3;cMLltuVHXS9DFqtdVs^(F?SVdz`#38s;iYe_m^h#&=#+H6m zd`Q4=6Np-uT8P>FCMkciZaL34re9|;RWyoYh_CK-aOoq)^an%;S_BOpxmPicdNd!H zLE#Q6SZlZ+YGg&6%`vI^TDV}z_?jX##3bVrVxOQ>yFI8pKX4&dM8enc{X!FJX}TZj zbZog+JoziO`N`|;{Xs1g7Wq{9v(B?F>2?I$U_2TS8qtm0ogGG4VY-_aL#}vQ8M&)R%^H93!gyY*e_vlae4hP7>CDFT5kkBo}b(ExrWKb zvG36IzUBAh;wO8Dl$mueKRSIrb0&CErSDrDNGofZ(=FE0^bpKUEp12GZ}_zRmia%x z|H08@X#oHlz~ycH?Gu2N8Q_0#K7iLYUS@U5IW}ciYVR%CI#B_~IDQ55401|P`rDp$ z7_+f4?#_;1ibZ)^7WpRoOI^Q@ZuUGCeB<={iSFH{pyN!zIib}e84ezd6>ZdqR(&s~d0w40*3O`PyI!|7WhHvC)Ji~~qLl_lMcNZC zCd;YszLUzld{ujl4es)G5MHpPkZqZqjNPys6Rz$KCs&OAbMHBXo{_l7RVf+@_Mxup yPuc8qg8gyu#}^UqJ@|GfYtq_pNgx{x_T1g}3Le^;8X^Ny_|wmxnXwiYfY-mm%n#rI diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/SaveAndPublish.png b/src/Umbraco.Web.UI/umbraco/images/editor/SaveAndPublish.png deleted file mode 100644 index 6c42fa3a0cd1ba5dbbd8762a114c316e2bd826ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1196 zcmV;d1XKHoP)f|00G2^EsXaZz4ia%?WYW4%B&2MQd|tbIbJcm`FV=r+|6eU&(2#g zD6#P~*chuacsuIgG#ns+FoI(Dk(>X|-oMYFX!e~!$3T!lMn;lh|C#d)@BaN^`1Sia z!yBeI43B?FF|65tj$!kj3pisQAb?PU;=( z3|HQrV7UIAi-A?po8jK?TMUeXM;LaU_yY_Y{{N;1iWn{j2p|-L?>>3U@Pp+S124-@ zhWlTyF>wF-#K6e(h2iTvX@)CTI2o82(-=g)d}eqqwV2`ezn?(ME-_rWrOW^{7{kQ? z0fb`kqv!7##6-0j-aNm>@ch>e23DqP3{2N_7?_y#8Ki#vWMKHYk>Sg~j|?oY#Tm{r z-(`5i_L$+uy~kLC0w91;41V?Q3xl$>IK%IU+zgLC{$TjaFU(LNS{_dmngi*Fg?5{~}oVEMx^YmPlUNB{x|#o&KH#ovGZXZXz- zz#w?(6vJDke+-NYObqYd{bo>A1q;q|8v4Bvlz14c5c!2kioLa1E*`SUjeBjay|t!GX%h)PH>>)nI@CB53f77ta=c{`@+c?fV;Gd2GsX>cBUKeOrzKgZewe z`%ld6e-l6X#cLv*TQjKOw;ibpW$-KMz zZEViA!qLUg$CZ@ywY2#E|Nf||wNF{b^!NFtu;YV@(w(TXySn(s@3CigV03}YCoh8g z`~F~!QGl{-Pgip?Jf*a=_oSz-4-_2O*!O*^YR1sVbf97}JxP<2`8-KF*4Frmhv$Wa z=sH5GfVFJ*`1`1=u|ZH^_WAp^;G;%WVQQCJs;TpLcGr%L?9tHlzv`;!@%oI5?Wd;r zPf?bip6{fi|73BL#O|&%JD<_k)nw+J% zYJdVJ$#l2+B! z^`qE}qP&%ij@a|_`>C$5ba&lnXsDHx^;TApU|+fa|NsC0{{R30A^8LW004dfEC2ui z02Tlj000NSfPR95goTEOh>41ejE#Tz~>5-JZAQMdE=mf2uLtv%A85FW}!d;J2DG2hX4Qo diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/TaskList.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/TaskList.GIF deleted file mode 100644 index 52761babffb198755c11d18656ccba5a8d9cbfcd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 137 zcmZ?wbhEHb6k`x)Si}GV4M5b;aNxj!|NsAk1we%2PZmZ724)5w5C^2zf!WVu*PVAw zCp4KE^IcCB<~eE9CgZ`y>9}`nJqqfAq!%@FIlZUqrgK)%9-I1lR(x(i*4sG fFHf>v));8A+467GrIUS0wHMBrEZ#Mhfx#L80JbpL diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/Undo.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/Undo.GIF deleted file mode 100644 index 520796d69defa118055d191eec33cc67f7551710..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 175 zcmV;g08sx&Nk%w1VH5xq0K*FaI$fTWtk6Go+_}Zyr?=K#e#T#ZwX4DHa-Hu#XSz{; z-OtzKVv^{Tv+z=g@&Et;A^8LW000jFEC2ui02BZe000DB@X1N5y*TU5^*Z3vJx60U zVAe<_A|#2DUTDd{g#kDRhWrRvKmt&#E(`|)VHiweM~gz>Krlp-Snncu!2ps)!r>e} d2m*##Qg9H=3xcDt9ykra@ocD)4hB}-f* zN`mv#O3D+9QW+dm@{>{(JaZG%Q-e|yQz{EjrrIztFf#=Bgt*RKxh^e@;s1Z*nKO<5 z|4%z}ChhHX7t`}xl?b10~M zH!x5x1S&oAI<{^96F^3j7%KUEgTvc8Kj!keWba5uLDJ|r>mdK II;Vst03P6}>Hq)$ diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/anchor_symbol.gif b/src/Umbraco.Web.UI/umbraco/images/editor/anchor_symbol.gif deleted file mode 100644 index 2eafd7954e6ebf24204b01db94b39299426d09da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70 zcmZ?wbhEHb3t6qtjy*E#3&d$!w&zD+OKB~ULl7eEw((m;2 z^kQ<7@$qtYhNk=Uwg3PCA^8LW000jFEC2ui02BZe000DA@X1N5y*TU5`(_{rgp5Wq zV<-tiQw0DJpz-{)hcHm#IKm8pA~9I(Ca@SvR8S0cm6VSWFgTt^N>UaxG!szEiFzP_ cvz1f8Q5b|bYX=qJK_CnUx@XVt`!Nv!J5VM>-~a#s diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/bold_de_se.gif b/src/Umbraco.Web.UI/umbraco/images/editor/bold_de_se.gif deleted file mode 100644 index 9b129de25e916946792c89f08a7b30b2f9dc8b72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73 zcmZ?wbhEHb6k!lyn83&Y1dNP~ia%L^OhyJB5FaGNz@*UAzw-23e#sdnZdC`i8W!&j Xl@)6cxHjEB<6*$FFxbIg;(p2 i&djr5P5jX#dNVh}IZO5VUiUMXvRj`2I%m$xU=0AHn;dum diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/bold_fr.gif b/src/Umbraco.Web.UI/umbraco/images/editor/bold_fr.gif deleted file mode 100644 index 2816454515a2b3d031ee1762f9308db99e9d3940..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78 zcmZ?wbhEHb6k!lyn83&Y1dNP~ia%L^OhyJB5FaGNz@*yKzw-23e%TquY+VNzx0~n3 bPUX;xQ|oohT$M9BQ$Xt4x8CPfj11NQWJeUX diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/bold_ru.gif b/src/Umbraco.Web.UI/umbraco/images/editor/bold_ru.gif deleted file mode 100644 index e000d461c96188c26fe081920ba332593e5b60ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77 zcmZ?wbhEHb6k!lyn83i`>+8$Fz@Ye(g^_`QkwFK@28xR?Ffgg~^shYqmVYseNeI`y fv!S2m6KACKPM+Zyy|O9RI8W2_`nTTOtPIuwq8S!> diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/bold_tw.gif b/src/Umbraco.Web.UI/umbraco/images/editor/bold_tw.gif deleted file mode 100644 index 82085432c622fb9b195d45480ce0c00f80d24027..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 207 zcmV;=05JbYNk%w1VH5xq0J9DNySuyH-QDi)?jj;0%*@PHRaKdpnGq2YcXxM0L_~;) zh%+-YW@csp00960{{R30A^8LW000gEEC2ui02BZe000Dh(8)=wy*N7}G;WM{HHkzh zs6t%9fi%d0HcQepm#{3&(I_#A0+$;m0FJ}}WxKp?w}cH!tRNB$k|H1#RxPhVL6BGg z09nkC_M1X4*#RtJD4)}o#(?mXbR9t%Fa-%dPzMPG0V6gC3@He52a9TEFU_>xB^?a&6>B~^ R#bfrS$(b43?=Ue~0{~-MC*lAA diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/bullist.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/bullist.GIF deleted file mode 100644 index 6e19467c70e337bbfb2a3fe3eff1bc3acab0eaec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 108 zcmZ?wbhEHb6k!ly*u>1h#Kfdz)l@%W&!*iMf|{-?85Jc}En;9`Q2fcl$iTqPpaWzB zRf;e$FdJ6vy7SNAl;>@M#YT=zObHxz&I%bBUa^s_ M6r5P1!pL9^0IPo@`~Uy| diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/button_menu.gif b/src/Umbraco.Web.UI/umbraco/images/editor/button_menu.gif deleted file mode 100644 index c3d8fa23117f9a5630277071a6e07cd04264173f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57 zcmZ?wbhEHbjEB<5wG8q|kKzxu41Cu~c|H{*E`Gsc$hp{fYx2K(n G!5RQ}Dh{In diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/buttons.gif b/src/Umbraco.Web.UI/umbraco/images/editor/buttons.gif deleted file mode 100644 index 6196350de88f382e73bec4906572229b66c5c548..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8399 zcmWldcUY2(`^I11!4l=giKYo|&4E^~vcO4ZiX*dSn3iVNK{MNh1I1N1a^yI;N0zus zZDCs3#>z6QZ#XKO2S+Cx%J2OCd!9d^`+Dxrb=|kmI&Wu}h!I#5_@M+6b0xL?i_czu zh&!GieDe9?;^NJriQs?QJnbAQXg zori4?|LiXMxGr?xt}@K`5p}~TV5U?3*PlfnN2B_>y5UQ^7rr|k5}fr9Y}vdgy{0^; z@~|OcUF+#HpP&72CgO;stLaR3=R$t>;@#@yz5PR7on0HFj=Ds3+bmx(_qpm|@!Y|f zkbJ@37Y{BcRD8;=AJ1!_emcI;(f4%pzJqsU@!5*H-?N=_b3gG;Ic1#_$&J&WuC0Ia zu%NTG+A%1r{anE8>|F73i-`}jbv>uL^**0jwY98w;5AqDweQ*DE2YxiHLZVmhVGe~a$&Kc z?djW3i_Mqr*#;HK9^I3Dk+$`{8k~}NC-moE`ZIIgCi%>{*?uR{h4-boX(7C7lhp^y zAI&`)`DJXZnUW{=h)B*Cx6Ugh*RL=B{{3eszjGBQ`uF>#^NJ?BknD@~i%x#=^WW#s zRCoJ_-DY|JU23yJLSH!;riV-pFYhCTmDoOq>S8& zkzRjRTHn?-_|^drYH4bXzF%x_ySRaOZU--Y@TG#;8E&*?Trn>XO2{8Qogi)Y%V^C| zUj&0{W>QAa)mOexX&)EAQj8mcy$3fpU%xr?Y1|4rM z_O0;kYTlUAx)cxp`UmskJgLW?dY`|wf9)fzXQqSnrJ;nUm2z&qPN5LHVuOC7^oL1N z!aIrI1#fc}NJ0opyuY9#;hi-6tvWn2v-YlL@`-u=i#kbcB|cQ+5(6Z`jg*0eu)&*6 z-bB64^WDvtzP`GodtJJ^!`6byw`LgiEE&(AxLUV+0rzZgLMoGbxJN|qN9;&^lRr=S zGQd4y^03nUnfz-Xa|$}UInZ?djgxRtEfC1bTlhWy&)(zxf8Y3hk_KBmiTCm}NhPgM zgMG;)M+*>JVQ(VU&PzZQpG{>z&Upcjp=0_7nHmOR&jeeXsY@zFB5#6n6_2z{G{m4KHHzhQ{puBSWcBt z?PyH3(Dq(qm0ohfL@lz~S>GK*xliKPZ(|`^(P8n3)h0%#6g)ea3W|d!Rp?*Vn;+f-k2xle3-2ECG7PD*pE&aN z&Y`yi%V{ae+brbG#D&$BERP(kXBbQ~e(vV`^1n`t3d}9I$q0LHb_0>P0w!s;Y}Ktc zb&lEGfVbt}MRD|j=>Yxx_~iO?ahF~)RCz{oIY^RKWQjv@2yKbarRhwN=n_Kq+lM>2 z81Uh>UC7h{DklE`X6_lkuMSJ0-DAGq`Je!yFG|QZN;74Jf-7*}>M8&N zs;n|3u|WcW*_x<+Cep-{10{1kzDWec{SoW+?Sa%aqITDPJ2hg?3%@dgDpPqL;PdGe zo$5OGPi(0*vPTMZf6vf!<|E>Ny{?JGD2u8 zgoLG{YRk|-`oa+p?e@l7KS&h1st}_31`-)lYV^6^nu{0ZjK!b-(AJAbY|OMF-2py= zrwQmN*uEy~!5K~G&A17Evb#f?hFuM6?o0=05o~SeC2wty2If?%)_NXy3H1DAkMkp? z6;!ScI0_#U>J5sK2UF0JDCWhKo|&_|y7eA7OS*O>?$bVnuVN*7Ug{mf?Ax`x%$AaG zOr$PnYkb_s*pqg7$q8Dt?V#=`IDId6DMQLoi(_Zy6<#~W5~Qt3Ggp^^@7 zv=pUi&ht{W8m%G-F7n*x9KQR&(@?PQtUt~nnc(L^d&*!fvwk9N8efs(v|&&icJH(1 zGh1M(oV8Su7NOWl<$b;fEd33q#z=7bU%2Eo)LS`o#_sWqWI|S8j-}=Vt+GTKv+mfI z9D6yZy5#qBP4 zX4}#%?>ffG;fb#=nCXry(r7kXdl>oWxv!lQwik2XTczo7n$lzNxBuFxlAybDlL)=~ zh1^-2W)ok!UZ8m(POG$xp6_!zuLL3 zDI~E&N!y~!wkj+_)jO-TOe{WB%>Fq;e%7yV*ogQ;t6t>1rXgJp?6qhWEYN3@IU~5s zzZe*OQX;KtuGY|Tfo$$LZHQX1S#}YrCeAmpTdVu+PUgYs_+-c(pNzT}pu+?129tbm z>ro6pNh&4itrQ}*@tK+-RX9gBU-hU6T(UZKsnyru^??#d6=euu#F|wyPn4Ej{X3x^WFUaVF2nIWJfkhHyJjuUaM6K)1}c! z{+S{FG2Dz8spdh8O-xs`#c)m?`OVcP4_qT1+UT;NH)Dp$t4I1wNSSD=r;LJ|@!E4${t%%7Z-xfqy7a;<8iWnvufZp;YV|fFVph*fv6BD< z@L*#;Fw_oCyc%Qu&D&SI6WXtB?yDf_U6D&A5 zCQFVDtDmxP*@#YPgU%@OW15tOX4A!Z4K`}cwltMs{clmCXqoZaSy14Pzf%BpQI5I- z|6pVbZd%2X`InMMoDB_KCg$O*LJ>Ld(J)4@F-zkI*-0x5A82GIIb!evUCWDKD><$2 z<1$&2g&nf}Y80A6L-UwG>MnHo9hF`biLX};RGjJm@73qYNSy=82q@hJ01DP2QQ{Mt zDvt{#G6*wtLjni6!Ngq;Mi+QQSd3SS$aO0bRa6ykkGX}aS~cY>6o};&Ekwa2i}RKb@D9%;cy2wgJvRfF@q9CDmh7H- zQjK?{#2nCIAOWHy#ouAl3{2O={j(9^%EL9Kh#^k|Ca?Y59ir1tUJ>C}a*sh2AFb4! zEsAXj4@QQ35CwGbU_2t@Ubu8z6*%FnJOrr~l0ytD}KpnB;MGh%}U&JpPFc}GIO z2`Z74KX`d)&3N+NLiFczv>@MD){}f!U01CN?*syliVFE2=!J3^UM4JK!~hPBV1MZb zL|*asCq`aMe4mfdSC~Lm@JdIhiHjyBMf-&LM@z7N)R@n6ZiHl$FYi$m8WK(bm+%Bi zK5`UD*VI#qmf^fOo5oDh5?NK%KzfCC^=FM(kNLnGG_}pNAk`qmk%oBIp*7RFSQfN* zXW;xnIFf;F%rn4ZFm58?c%Ke|YcAyD-DtVGZA2-@%|b%d6F~vzpjF%xPy`in`b%?q z-z0TEP6j!1Aij*YP6R_7h%SO*XVi$XM(sy7&VE_udo&$`nN6=%NnYDHwD!Sfa=Upk zgvqsKpaF7l84rSrb4iy&$(sNJrJUG5Nk*74Upmnvk|UdE(D%aAR=^qaVzfX66o`;c zo-Rm7ejP^pWl%43FEU2BDuR1yv814IkOmmYAgl}__n7VoCTe{&p_b+qaIlG7h{r7r z{IL|40j5v#Ei=)l;w^TT=z;`XkP%jtVusXhE^$>{6&K|93SfH=DZ?^=@8LX$`(J)` z00VLgBAjKAAragecK9U&8ZterrC>et+MaP6xqAc+&phHL2JIM_Qk=?dCrp5d#94=O z8J5eV{CHwQZ`#_QUXTF?$dbBUX7leWiWh2-Lt+FL!RDXAEzA-)p1V^4IXP@WI1cL+ z-1|x$oD*=OjvGO8$2{U=vC}YJ!Y)TaNvP`HK-G5dayS{T*?ex&C(eflUYF>ox1Bt| zBXsf!djQ~osB7z9^zzG;3`=NUN7agh-{TuDpjop*@%vQpd)K44d%(euQTv0PlKKs0 zE)~7>3d)e6?m1U4{9NfMuf!-2j1P3-t-I{?^4>a@9P0nN+SvxOM3C32R8soMr;%Uiqy;o zfE>8U2`;ka7ddvGO#78?yahcirInY$Q8MU!Xva>Z8b<*;<+x#`9OsLIM2gC8h2^|p zM^iGedJ76m<5;e>j(ofiw>HV@bR+(XlH>Ng1|yYXrHWcyApW51#;&cwH6y`i9AYkn zp=$)vv#Cx36#R~YQ?8&hr!R{K_+2^f5)6_*)Z-}RC+r+(8Zc!1)j#4k=Y_0bLiJKe z!mwN!atu%BCH3+N05}qhjt=JLd}^#6lm5I?2UW$F7o1iLY_j8m4uegt=TR5{2_mFG zysuvGz@K^GDr0jmdOrV_O1V_U;179P!YUg(Y7&!THowwc9J^5j=$3*7oE9Dqy_H(SV*Q>_?IDSqXlE z4;8qRUZR-!tivG$=R93Q;5Q1bmq-{0ei?uTR1OXkofTAi2$86b?3W^1Ke3P4sF$d1 zHHUmfqGHE=kf{K3fNZ#pgz<)IrO*};Y*q#biC|j}*?JXfe85;}*|GE~8SxC4@}MN1 zY8?}!>W&dJv*3uRrP_}(xn{fmko{MkyC!tanQCz%Awhp31m<9#$tW%ga-5tBh4&Ai=-B>haxXe3%~mUh zjkclFw2c~hH_uHxk>&vPXumq6|3@C|$tPV{kE)1(k$G~4DUv5j@qEh59&My583P&W ztZ{Zc!CtlO#p$xA@t-sraAT)GUTea)L`W)7j8W)_4Jx1^1ZMLEtJ~}Nq+Z^EQN>$* z6zKerd2vJou0Xk(n?EN16mA#^$P@HHrV0I7=sL<|J+ zaFOEwu7x8xB}N@wGX`cS@Myf{I5bAGsv$?!K5|nsG0$mWu`jC6FZH^Q@yYHCq`?6( z=#O$0nvs!F`*NV-Y^4>7ERdILN0x|*+t)`ngeR0_l$}k#v{ORxut85VELrJxT8z($ zs|Xl%$?gM;(0fn5Xi!eB`hv2?)L2h7X*IaO&xqt{_A%S3d;-_@8Y_3V$JZ~uK&3C& z){n2^Cxs0;fBQ0^m5g5RAZXp}GcUHk30JL>J3REYcYg1HUUERxHGn1=!S$_(z4m

XfgfVbD&|WP0QGK& z9Z|f5W%%?Hur^Od>%#^!)l-z?kHW5HdB)AI5@fZ^OQAr>0R2y9z~tjBb1l%b4?VQG zg6z~conB|0OdNa&?GGm-91rCNxQnB5V>Ox)x>=2mC*KK;d-q0uV)pyR;NTIW9gyJg zhDn%)J}f{VFGzz&P;CG#5TTVE`TaiqRupMv0Jzig62W=3yK+^vG0H{CNy(W0w=q0s z{Nl=SC?d&hKWt6IytTqub75B$4p$^8B{r&@UsG1Nv#)K&SeFeX_=`6PUl^fUDG6kJ<^sfpEqRU#>~VQRBhVkT7RZ$1 z%bOnLD0%N=*P!=BK0(U&2!|Xb2P0`nBnLS1zjpltCD9hxT4aX`rvqV4xuLjt5iTA; zH7&wAitNbeP7yQ`*emXj^?UyB>YhgJx*~UhdBQvQl>VAKvDeZ`ZD~U$-Z-MU>Fbo~ z{&LqrI3kFB-v0r4kKdDJvJUUZpkN?w)CBCmd+Ib%7YMoC6if*;Fz`CZVRyw+8$*Xj zhtEH6R^of~M8jGyTOtBZ+hmW$ZS686=zO0ylG$&lp{)Ghb*H-}!D*{7tAxefE$N;9L7= z)??Wtic`F)b5%0!j+*LVhwHynmSYB}O8W7dZA6oOMr z3uUZY-u+xAY56mrhnkXrX;gn6V@&XNpK9sH8~Vk&6SaCl%+kXmdEU`Q&ZKvkC>hZ< z6!jWo*3^FnG&#gQk-2Ky`j}Kr`a|fKIk+`00;9L4DuVu4#YBWL=HmBo`4n{nrI?T9$9H1f z`F)M)gh}0K{Uo!Ii7X?dd6Pm8_wM>UbxrBSvn4^MG=U~wUcDT*b0a&ni`(-pGm1-* zXPR@5V2Wowaa3Jc{n3=Ld2*}eZ}*P?c346w8^s{y8=nM!?qWF5 zsBgTm%1j0^-(R@#50hbQLrtIt&C>Nggz$$0A2DZANx>Bs;6F1UV! z<22p(W2V2-)2~{j{xoWP;g`JLlnkFwr=s|4^O?I(=^yiqy}ydxfPQbxpuzMUHEYY0 z@swNk;o2_K8yfxtQGR0hG=I|{0 zg|g;Qto$rKZaCH455O6rd`!-GMXZHfb#NSVtu$ZzYxpnV3fI$U>52|?-j2SBCRpg* zWorqvn}4_|?6r^pNv0T!FU`f*7%6tqLOwfwupc(#;+K(bl8%KvU+YXsNjEW~fEGnq zzs&=&<(@kYN^b(AvUJRRpUz47h?X+%mQ72osd8kOyMIyp**D+J1_v|OaVbvrBrmv( z#(pH)c?kq|?sxAf+md5uP^%1-YV}fBj`p&L^-T&Bd99wyt)6O~?v%ZWVJwgVR;$My za5@}Z#vKi%_Lsot5`OExNylF9K;#ZPx=9)g`K6FX9!|G}VF{S5NijYSL45oxvnC!Wqq!0(9Dsj_cJLoo)^}zcu%DM$7k&8Ug_j=>8$? zqHk5py~o6Ki*zzplxA@2pwH3OcU9HpgKzNAvf+`bl|8>Uf$NMg4_-Y)R?d@22?|$W zWWd0keN(-fp%n1lDm1=nrqLz|9mxq`5TA~VULhwVJWa-;!o!8_D_kn{Fl>>-LEZht z??O;bnxJ!4ZfYH7BHiyfeAaGF6?M0DO=GiqMv!L}l`3>MACAReF2c9GxL4%9lM!e* zPZMRsM7q|^ti(2Vl8Ts5ZI%E>ZMYc4v8esCfm!Q|JPJZNgBm2qH?7Z&gk~;%1C9)Y ze3*O6OyQBdw@Dkvk1M|R=Z+mx)qZYZvaio?(EgbEnJP^I4a4}hPQR~EveFwqO^aXI zKfb9eDwqbzcVli^f;u}kxH3-0=RDS}N+U`4eobs}KbpTm`*DYPMcAv+*XfV%Q2F+` zO3Sv)f+qzJxbd(aK{O;q?t3Mp^FuV74U*1eI`*kV)rD=7I?KQCk-d3ZU zUW~4t&u(>~c3fo-+k>%(uA6jbE8V{1C*hgdSVvEjtm^dA| zzkP9L^?m-5qU~fStX460Fd410Kvy1_HPFoj=lMM X^3c@}+y6QC;n>@S#Y1)o46OP;pSlB8 diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/calendar.gif b/src/Umbraco.Web.UI/umbraco/images/editor/calendar.gif deleted file mode 100644 index f032ce15709d6bf5d785752ef6770ccf22b782fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 394 zcmV;50d@XINk%w1VGsZh0M$JJfS~Z8xX^&0z_7s3Kyc8Yu)v_Gz>uiWP=MgTI1u1K z5b$6y;5ZP_;Qyew;Bc7W|A1hCfPiq2;BcVuV36Qoh~R*r;E=G;KzQJ=FaS_^&@gDw zU|3*KfY6$noq(wSKycuPhl#+*&~T8j(AeORkdXhFfIv`Skf5+-WOQI)V34r?HDbOs zV$Gni&_GCFaBy&tsPKTO@bK{e|M>rKh_E0iAn^GA;OPH=pwNJrz%WQ)|NsC0|Nj60 z00000A^8LW002DzEC2ui01yBV000K3z@AM=92$u!Pp{Mq^&-z6tf%U6da;ZkB&4qIx{3E zE*CmAIh&m(5DN;OI|YQNG!Q&D3^*dCkdTuLH#Z451Erdro;nH)2^pjYfd_|)GdXEF o4Y|aUlru9rX#&kV1_1&!4hJI++uAh;Iy)#k=r8E&?C3!NJ35)Mwg3PC diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/calendarButton.gif b/src/Umbraco.Web.UI/umbraco/images/editor/calendarButton.gif deleted file mode 100644 index f8f5f2a1320406957e50039beb9dce5f5ec28f62..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 108 zcmZ?wbhEHb)fLs#+_}=< z+2GjQ)C|ia%M}g|2-*$|4jAv++6)X8?f3NB2<-a^7%Bu**N)yjcm?$#=+|zcKYj)I zIjF!OVTE%a3KYO_;R1$_A3cH`Nit-~lPFRDiUhfmW6G2Q3W(6yv1UzC}*s)i?z8nAt58I(vAO9Tu2JqX_n^*5Vy?OEB-=jZ2zyA2} z=ksr%;9UfV@9G68;D7`cXyAbe9{8UkfB!Y;;DZoGDB*+@R%juG3|^?=h8%WSVGAIJ LDB_4Bh5!IN$Y@?j diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/charmap.gif b/src/Umbraco.Web.UI/umbraco/images/editor/charmap.gif deleted file mode 100644 index 3cdc4ac9134258a9a1a83b6afd5c7055bebbcbf9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 245 zcmVJ7UM8qsvB8x<;Dr zSg7=Nn%Hi7)&KwiA^8LW0018VEC2ui02BZe000GY;3tk`X`X1RuI#x~)x&X5LaDKL zBuP+gmjjC8P!LL;h9PruTqdW2VQ_jhm?kOJM8RlI0sywOkQfrBgdlP}Q6Q310n$b# v>y*a!grF>@BLxiv8314c77P(14Hy;xh9(V(3=AWV77>droSmMZC?Nnljc8>f diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/cleanup.gif b/src/Umbraco.Web.UI/umbraco/images/editor/cleanup.gif deleted file mode 100644 index 16491f6cfcf3e1123f08352c895f8010f791c469..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 256 zcmV+b0ssC-Nk%w1VH5xq0MrlwMm#2xdP3^ge4>(aXJcF2&#c0@g7@*TXJyAiI+Bx- z!osRT%E{EUw6tz+enw7G^6K)^$d=yS-J+Ue{{6y&f`-z%VxXFs!L^y{+>(HFVv>^L zOipLAk}~7mu>b%7A^8LW0018VEC2ui02BZe000Gj;3tk`X`X1RuBfCa3_CK)hB&I! z?6jS5DV>BNxhN371`1>FG$6!^gQF}JB8CHn;<-pFjYq{)@NCbANaf;iJjfX><#5Od z41@~dZSq94bpiqw6C4j!WCnJE77HB!h$k2WjE(>oEEpP%kC-wT1Ob;aBcK|jsHv(a GApkq`d~Wvu diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/close.gif b/src/Umbraco.Web.UI/umbraco/images/editor/close.gif deleted file mode 100644 index 679ca2aa47644793fc7096142233af6d18cfc9ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102 zcmZ?wbhEHb6ky1B=+vSZwA88iSX|!)1|*>PlZBCifrUW_BnDE;z-;WX>&`!e zQ=Y5$?qJ;7tnMTs#JpKBeE~;8w4TG9b-KTcEEZl{H}m}(h0KD}68$HZ6lMA_FjxZs D>4hO7 diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/color.gif b/src/Umbraco.Web.UI/umbraco/images/editor/color.gif deleted file mode 100644 index 1ecd5743b64464068d80fe0c306375bc0f36a3f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 125 zcmZ?wbhEHb6k!lxSi}GV|NsBLa^b|RS+g7*9Dr=F7=z+Z7DfgJRt6mq7o?Vf*~wzp z9Utctp08V)UYxZ)DHzz35?MDRW1X_prZuwmzXRH=pKYwZWOr?gDO&r}E WQznT77&6ApIg)ei@KZGg25SKIc`;M~ diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/custom_1.gif b/src/Umbraco.Web.UI/umbraco/images/editor/custom_1.gif deleted file mode 100644 index 4cbccdadf60dfccaf532fd56a567c47b131f80a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76 zcmZ?wbhEHb6k!lySjfQ8(7@2Zz`(%B2qYALvH&S21|1+9C@TVF%TDQEdHOB?;yGKQ b7!RD8^=Ve+T!9>q%7xRawyuk2V6X-NXyp|c diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/delcell.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/delcell.GIF deleted file mode 100644 index 21eacbcf1d1886f50edecc91eda795188defa0f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123 zcmZ?wbhEHb6k`x)Si}GU4F?V!VE7LLPyq%8#h)yU3=B*RIv^2{S_ftaja_%%DV$pA zRKYaCm*HQ+io&>zc`wZKQg>xA8%_~3bIn`#-ll5%^3dajhKxyu4XHnW1hO1eOP?Xc N)VqD&wJ8h?)&RrcDRBS* diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/delcol.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/delcol.GIF deleted file mode 100644 index 3c2d5b6a8678a708cfb90d7515d5f7963183b989..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121 zcmZ?wbhEHb6k`x)Si}GU4F?V!VE7LLPyq%8#h)yU3=B*RIv^2{S_fu3ja_%%DI9Zb z>p9=Lck}-R3;|&o^Ik9syop%5_T{vdLRZf8zOPw-phc9G00a9hUH||9 diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/delrow.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/delrow.GIF deleted file mode 100644 index 4b66eb24333ffda0488578dcb73c09eec3167623..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124 zcmZ?wbhEHb6k`x)Sj52a|Ns932Mz!!28I>}B%t_{g^_`QkwFI}22$(5>?pD8&OHS$ zhkLU(-8!2q!k`g!%Ah1;6U`PepUW#F9rr{07>vFpa1{> diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/dezoom.gif b/src/Umbraco.Web.UI/umbraco/images/editor/dezoom.gif deleted file mode 100644 index adf24449a6c8df06ab7e87fcdb23b297962635bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 294 zcmZ?wbhEHb6lV})xXQ}#|Nnmm28II%4m310069SR&;p7-Sr{1@SQ&Ic&Ib93fzA0q zQ-Oz$RR4)3MJIEt=3mhG?7q}%;??3MBfIBQdLTYHDQBZD;nuHr7H diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/dictionaryItem.gif b/src/Umbraco.Web.UI/umbraco/images/editor/dictionaryItem.gif deleted file mode 100644 index e9dc737967820c2eacff5ccae0a6b8ddc60f6d3f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1040 zcmeH`YfF=106^buUJDBm%tCC6go*@Ff@#nyHOmmAK8TIb5VbHcA^l)p*w!p4%?wPi zT1=aoQ)kn;T&8Z`CL21OCf>F=-PO%)y1m!uE&CIF>HL8M2hQ~y8Oaw5!l4t8j|HVM zE4qi3Y0S#Bdll)-N&=AW0}Aoj{PZe3w+^c}k&bVZ8Uyl0JjEButu7qHNw=3!X?6So z#kK=?>=QjwW%uyxx&;*sPPGU|1cTF{mkWA0ptD-F2@^sNij#VSS!ctXc9(J;UfxDl zc2JU^ota;?X~hs6^p4COAYgQM*^2-~#zBR}mL0RxWbvZFsr`u);QQp_veCDsKY(p| zzDV(`q5XM_&^j+$&?A_nrJVbrPo{!VAo`*5YWiRd_)tLfy)e6Q1Os*t?lJ9PIB+=j zJuo*0~?qjZT7dwCn(8JzakQO|B-S|oTsQTUBXp>J;v#9eR z!iRP@?)DLS8)mfmfS}5}hr7IlacRzG)U9k-@vr%SBv;L7w`6u`LPK;hbBbzB+SzR< zzGg<%n=mp^u2}IA{Uu(9-MLTL+}N)j)Z)Sy^f37s=s*9WKs4efcsYql<=NzexQ$*{|74oPz zOWx=`yzEvQEsxc9HNF00LiLb%yo^z&rbahhil<%+h)Sv9L?po?M)R?}x>sG43`KB! zI22i$15pFxZWZ-3u?<-br^|9rU4RN^qGGe=-@8KDA-v-!%hgG5{W;CCxpzB5<%}yY z1MVEnE@|R&>d)Wa&;{}76qf&kkd$PkZXzi4OvIY2t+;-T6%d+KSGvUZBf~8wSI97 zWIFC40w;L94i4pYcYs%`XuOo^0KvIA(P3qOLf;?oeLs9{?X3-s-5>x0Nhtusa6X^U zWHRY=dShc_eSJL|jb^jit*x!4rKQEi#X_NwOeW{&=X1H-=H}+=>gw|HGGQVX78V?K zyO;H^tgQI`ewJmNq%{(W_% zk~WTU%3)jS*%WjA$4yqt$9P zn!gbQ!?1~o2^2+t{{#46s`EQ|03Zd#+k)*UfPfm54!jx~ki72i?Ul-3LLY`dLT}$e zyq)K24qvUmb_nX1O9ANq)9xc@Z-}2Sm8c|P~G_;LHulgC7rcdo?bePx%MJD=4acmRT#+1=;6 z_FihLedA_hpAPbcLP?!XC6n#i_nueW9BZklx!G0sxxTfBC%!QIVwjg`E2)~Pl!?GW XKqe>yFDjf#dFPiQP}H-(7-;$fPd-Op diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/documentType.gif b/src/Umbraco.Web.UI/umbraco/images/editor/documentType.gif deleted file mode 100644 index 0fb441068af570201a2919e4d736291e8e427baf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 607 zcmZ?wbhEHb6krfwc$UquaN)uwOO`%+_H5z8#S0cJTC!xxqD702pEx;h-hw&v=Fgrx z@95E^&8=-sEiG%-uC1-Aoiusc^ck}jFJAoi?c3S&=D&IKX6N4BFJ8WwIde``b=}+11~>e8r0Hp8ln)R?eHhFgv$s`N~z#odfg`bLSm7di21dLzk{zU9@P) z%a<>6^NSWN1TvR4H?`I^HXl2Aa@&sW6DLoNiBAj;k2-z!{Nck#jvYU~Z26LuwCp8I zftIh!&o4ZE=JeV%YmT2hed_e-^o;BkE0;fi{(RQ#`91x8=gyt)?wzoHsJo$N>(ygOd(;{t**^M;SUHqSpCx-7IiTzYDX=Z JBgw&F4FJ<48@&Jk diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/forecolor.gif b/src/Umbraco.Web.UI/umbraco/images/editor/forecolor.gif deleted file mode 100644 index d5e381425fe0f58a146d8d68141686aa517c3391..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 272 zcmV+r0q_1tNk%w1VH5xq0MrHmVgdq!0s^>+A(FDP-mWX|+>7q%%Knm4;nJw;$6E00 zq5^UP@!4qj@34~ELeii*Vv@1~0%EdyB3-fv0%8IZO0wc20s(+4wP$kLBD|J7 zDvko>y+!`AasU7TA^8LW0018VEC2ui02BZe000Gz;3tk`X`X1Rsz$-QOtXFlqcphk z8fksw4TYkR;GGIL&%qNEW(vkqtZ}da8Ux6n@PL|Z4NSmNfK(!kj37En{Dy_fq91iUI{G1P_oC3mgd#5-J3RFrN~4DUKZp W2^|}!GYA_35_SkPxVgGBApkpwVP<>) diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/form.gif b/src/Umbraco.Web.UI/umbraco/images/editor/form.gif deleted file mode 100644 index 113dc8ca676a78eee3a28261906bbf8a6bd4d616..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1085 zcmZ?wbhEHb6k`x)_|Cxa?(P4F5B|S+{{QWp|A!9#KX&y0sgwWDp80?6>i;{p|KGp& z|K!R47cc(5b?g7*$N!%^`TzX+{}(U*zkK=s&71#k-~Rvj5s3bM{rdmYr$0Y`{{Qpm z|KGp=fBg8h|G@i`r+ywg{`S)4FT3|WKY!`nmFw^J9K8Sh<(E&Ne;z$`cj~-7$4=i~ zxbonJ?PoUbI5l(8w%V@6YqlIYdiwf<$1gK0CRMgCT)FYU;};*3in{B27cW?~d&7>S zw;#QY%xXz0?ny82o3r}hg7rt|t~s=L(}@+^&#vBeVaL%MhtEGedhyYT%a2!WK5+K> zvkNz$U%&tQ?vr;9p1ptg{KNIz4^2Ij&An5t{4#6&GuY zI)oOuM3#8ORr(~=1*A0vXSIdpc10HSMi=!*=QhU|w8rH(*G*Vn*FAgcf_1Z(ZN7Bp z#jQtg?mv6)7FFOKS6tXIamM2HPhWqs@=vi1%5V(Jc8|{YO(+RSsqjlI4NR#BN+}Ob zuL{ekipr^v&2LOCYtN|Y%&F(SE}A3lA_tm=IE=G}iVU>Mmz z@h1x-1H&o?9Z)cU@&p6Ne}+&_8IKJM4mNWLYsH+{uu#f0``#AK$8H@n3^Vuq;5fXr z+tIX<;qS-G?dRoeE0=k6GA$L5Y0hR?I?FTIxs6*?%|Su=xWa6ElXN?)mf{A60Ow~C zDv#A)u({9C3FvshaAl?oTl*K!P%jZpVGjew2_GC9)lEux<~>Y)6}-aBAR|Jmq}8R_ z&vBZB$Y%DdlO<-yJ;+#csFjChR^1Il2^aqMz&4|e8HySVTv|MOj29C>xXZaL_Y#OI zT(G%0(={z65<5duC#XmpTj|U=k~-1RAjn{eg`pEm2fw0?goeX{ZsF$0 zH8T>O(@sd5IMy-TycEbS*Bsj+B4t>l;wPlq`RY#KktLqI9=wlF&-dTIuQrsCk--`O DYS`GY diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/formButton.gif b/src/Umbraco.Web.UI/umbraco/images/editor/formButton.gif deleted file mode 100644 index a2519973ab1239f8b42f0b6666698972aa04947e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 109 zcmZ?wbhEHb6k-rySj5WE(9rPd(;F2PWd;U@H!pAe|NkGz00PCIEQ|~ctPDCJ4oEEn zvr)vZJO2z$cxDSIO*mW4?)-am L;uWjHz+epkOb;n5 diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/formHidden.gif b/src/Umbraco.Web.UI/umbraco/images/editor/formHidden.gif deleted file mode 100644 index 306a5cacc94d569f1cb27e6b533369eaf5570548..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 131 zcmZ?wbhEHb6k-rySj51fqN4oq3MLr&t~sVHipKs({HPo9&D)K@nc}H1^{J|Hktqc diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/formRadio.gif b/src/Umbraco.Web.UI/umbraco/images/editor/formRadio.gif deleted file mode 100644 index fef3b52f8fc909708312150954396dc1e863f60e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 111 zcmZ?wbhEHb6k-rySj5VpqN4of<&9IPjxsPX{09Rd0|*pC>B2r;au>G`x9vLq$cIfq~)w|NlTz@h1zAWMI$%5g@e; z%+4OW?&KJp3To5!%5v5|8-1npRK`3B!7ix<>*g4SI4xKda4_)e?pM6<0`dGFSruOj!g^_`QkwFK<0jYIh_LkUn=bi$C zl9uH3BR?AcKlrYZ__9|zbZwJbZ-LJF^_=_sPOZ~p_uMwQ-%UrV|G1)$=cI&rGY>9% b+~c-3FyW3udp6K!Ub&;kbTH+f8tqUysbN$lQ^w;v!hW z!pq(wXY{hj_W%F@A^8LW0018VEC2ui02BZe000G~;3tk`X`X1Ru1KI+h83(|W>rq^ zR7RF&f!#zRnN$X{6T;$HBo?`WVllvQOo2)zp>TL49!J8HVVDe#NumhpbS0R9z=Ux~ zAoz}Cx7j!jCJ$n5dR`j35?DVB6cUq=Rp^(V|(yIunMk|nMY zCBgY=CFO}lsSJ)O`AMk?p1FzXsX?iUDV2pMQ*D5XBm#UwTz!20PnmMc$7k99|NmQB z{x4g$?8S>0K=ww{#dm>fI7)*2f`MERV7S-3Hxejz+0(@_q~cam!f`GJrVhs41`Z4l z1=u(gWDYSfwm3^DG(0$zqvLSk7~4}u76G3bt*wkK5|WBB4Gc*R9h)|7I&_o~s4I&p zV9LyyGpBYo3g+-RFgUh2su>tJ3k1lhFbFtsB!*4k5oQ*S02(H6NFgw6Mpgqu6SuLW ztKup~CJv=D9FL3}p3G1HTF6q$sqAAad_)z*G*=Fgd19xwVmDCHGjBIHHGzhoLO>%D u4{5{)+~RJ$w}+Xh M?UBc{1V#pH0B+f{7d(^JQeF)%Rv|NkFIQVS^lWMO1rP-D;m z83^(d16!QK0Sa+3=zxSlW-zei2b}a=z4u4c+1=|M6#AZUG%OY1V6F^WxI(BULHv>RQKq-j zt7NjCDogO`gJ_lySi|J(sDATCUOUsGgu4xU7+ORVU(d1bXgSWKeMyMJ iI-K#xts6OY)eP+JZLJ;d&BZ;{lKm4VP7)Gium%8};6e`o diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/indent.gif b/src/Umbraco.Web.UI/umbraco/images/editor/indent.gif deleted file mode 100644 index acd315bb16c47c2f1679bdf440b511497f9d633b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 112 zcmV-$0FVDiNk%w1VH5xq0I~!C0s;a%TAPZd(ouxs%G2Opda$m#$^ZZWA^8LW000L7 zEC2ui02BZe0009uc)HyFFv__Ay%A`;yT;Qn5S6zzgeZ{axrjp`gM=IyhY1x0spz0f SC)ctBGpEmS`C~dK2>?4to+ugs diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/inindent.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/inindent.GIF deleted file mode 100644 index 5afc8877ed9ec3d27f730e4092ffb21476828ca0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 199 zcmZ?wbhEHb6k`x)IKsei_R_Ok_uiks_V(DB`)4jc%j-QKk~M4M`VU?6&ji$5b4Xro z5kAc{s4t}Ho`2;<+qgxhLA}x4@4{LhPTlabY3g1E28RFt{{u-}K=CIFBLjmNgAPam z$W8}Vmj$YQDVft;gk+mS_ZrlkE6KfIvF`mJO9y_I1EQP5%j-Ivb*SM{@%b~8qUvi6vmaYH) z{{R30A^8LW002G!EC2ui02Tlj000J?K%a0(EEam^mP;qs!bE`AUO3L|+$8UPOyVjeF51UVoIDT6~a023Kv zA2NR#C=eHtLIP|Be**&u5FwyGqGM*I2o)F!tF2+F7Q^!T|g{Kw7&1_p-z z|NjHYQ7{?;Lm~tef3h$#Fi0`zfLsg86AT>r45l119vc=MY~~QwiaD`i;o%kmm4FQm z4;LN)3nTyBj+{C!d(emK<~? rAUNUVWKG_CcOnW>Pfus7?NZ4UTIw}T&pPYwsjaK8uV-auVz34Pz2|1g diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/insChildTemplateNew.gif b/src/Umbraco.Web.UI/umbraco/images/editor/insChildTemplateNew.gif deleted file mode 100644 index 1463e9de5eb4e24e957936d1974fef3b66197298..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 914 zcmZ?wbhEHb6k`x)_%6lp<=d|dcfUM;^Yy@$FGnvvxqk0eTE&#SnhE<)-%Tj$N-git zuAVS++4h5{Z)8?+F7Q^!T|g{Kw7&1_p-z z|NjHYQ7{?;Lm~tef3h$#Fi0`zfLsg86AT>r45l119vc=MY~~QwiaD`i;o%kmm4FQm z4;LN)3nTyBj+{C!d(emK<~? rAUNUVWKG_CcOnW>Pfus7?NZ4UTIw}T&pPYwsjaK8uV-auVz34Pz2|1g diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/insField.gif b/src/Umbraco.Web.UI/umbraco/images/editor/insField.gif deleted file mode 100644 index 3a3721dd352923b0dd762d8144f383770eb9f040..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 648 zcmZ?wbhEHb6k`x)cvi&l@83UD&*aIAwm*IQ```Ds|IWAn`u%t6lATL89li7X*X2h) z-+un{|3vxUm$h#`{dxQ8&;Q@YAMOwS@?!nvhd=Lc)BE!M@Bd{=pTGY-aP{k~6?^}$ zHU9r`^118JfByb^==9D1pQoL={r$lX!-q!_YkHUbcsYC8(p}Gv#lF4K^M8Thx7S;~ zznJy;K$^U;J zwhzvkzHGN&V%3w~CjUQ9czh`0&l1Hick@5r$@zC}#-A%Qeyn%?v&s42tJ6>3{(k=a z)xVE--rlUS^v!s5w*TG5{l%`*S`%T=MsITYORX^8=O}_nr&SY?{7o z*X?y`3)UU_|FP%hDy0{XZnaHY`}hBc{|D2ypZ_{{_5PJxPrQG2YvH;>4~`TRwaoed zVe-#cvp?S2`S0a)ARlIiR=@nZ~{Qv)-fjFS}lZBCip@=~Tq#P6{4D3%E zikg~RTHD$?I=i}?($%w~EP{M{nu8T%?CnGhrZ%a_3m93-dð1cXM|rP9$Nm<7V_vmP5yuk@r_P*fHdNvb%XKnmm6CF2 z;bZ2EzTKkFz~U^%%EH5{U9)A-LdDbGvrI2?YaZ f<->i@83UD&*aIAwm*IQo8gS{uit;CF4_6{K6EzYW*ymsg5?=NP3xtss<)$E@yCeK~H|Nrm9&rU`^ zI8yNO;gY|v+uq*j`Tt{ocxF?4QTMc^yZ(Qe?3Y;eWVgw)W3fM8&Yrb$-<=~BE4Q9# zo3{4<$DVJmw>)|K`}qM&``{c)-;B36YwmA1_khd`6;EHb>(8s^d29B6e!8}(WzM6s{r^8sSh()czwd7!9!ad}UGnbk%Tc4~RrOGF z3+idsj<$9WmWi0!WG2rapds&Q6&|Q)pl|GB@6hTcCFg1*Wx}v?7b}MgW2>^bo*cip z&aRyzd;&cCTNIplO^sw!*tvF{I&-esB8=D0&spdMHaE8M7k{+!xTth2R!HSb#D#^- s({-2((oQUJb>U+Rndjq?A~9p8ol2_Nkb{b`tARR$we)5zQ33?clG{->kj>S)%@Z9oPXcn&Retp%iVnY;GF*-C#>9h;>p|J zcaBut-)^vC>xqX)692w#dwZj2`}wcam+hXuY?pgf@weAo9vmt7{B-S)m$M)2F#P{x z|IJlO|NsAIpd(QH$->CM5Y3GqMMpqTHD$?I=i}?LjBduzr+}a^ z&;AxuZy7@y30DsGU8l~RYxeS$(R8#_JmJjB#AbD;Mg0WBb%y6J>|ZzQzGGnd#3si3 zwTbyotgu5sBdg~BZ7g@TI?QY05)o2~nbCdyK)ay&yQ&Wgos679We$=C7aNE diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/insMacro.gif b/src/Umbraco.Web.UI/umbraco/images/editor/insMacro.gif deleted file mode 100644 index eeb3cdb444ac6581d41b75834279ecf941fbd9f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 610 zcmZ?wbhEHb6k`x)c*el+{Q1kbpZbFz3zh8d% zbLG|(Z$AAwaP{lrO($lp*t>k&nYpX?FIabI;krXhHyvHE^~B{zKiBR&y>aikL#J;( zdHehA=Rcpn|26eYp1f%L^kuuJE!{PB$n^m$|Gmo3p>wk3YqnelaR!MB6u-wsuOJ5u-USj+blZQoCJeLvOn{dE8Lvy*?E zpZ??GoS#>h{Jg&M=dJa>?rs0|V9)Qzhkidj_WSAafB*jd|Noz1pn>8~7Dfh!5C$EP zLqKujz`nmBq^Y^3wXIbkxU0LHSw>X0Jw#4YLY!MfSe0L1QBH2+q(zIH6uL~c^qIAF zELg<&7cSYnWvfO|V1U1$uaCEvr-z%yB&A?I(;iDhV-}%Jt#ZMZ!F@bzY+S6oiWj!t zfAH|pW)1geZZ1v^_I9>5)>f0Ay1P0%zIku;>qt9aPmdZ)&VmQI7dkZ6V^(Z1WOd_` zWZ-bo(C~=j7FTDB*?q97ja5iQT~kUxp@EHwQ%J#r!AocbpF#4e7Rz0004@NklPr%n+>$8H^hPNon+hbT)*dQnR%Evd}PvaQUQR6$J(}Q^-Akyd+A`%z6$Pn^)tv}By82qf%f3iUR3>PVpnO4r(|4{7 zwtG(5W}Ngxy`K3COFZPb1)t$jq3#!gG2vIZg z`8Nj7uN`ZHr=9zacBCkwZW51{@N%GjJ0u)<`n(- zQt--G@Ig?DKOdSHY<2DHgXt`rPrnY+q#tD@vw4Iv0;U`Ro$wFJ6OiVGfb{occM?k| zBf_5&K-X)Ax%dsXf=O%y;&79DO0U!+Ieq*3F`KE?b-|3CKp|FPfFH)GbyeSV2mfB%1&w`PA< z_2joVdLA4pIB@mbqqF@V?$7!E`|z`4v5yZ$e7T#y{ruOzuiI{}Qu=%+XTiEdpTGZI zvGv5XrMv!rm>izjRMWd;;krW`_nv$5_V>q!OV;i@{r2AC>C1LcU$*Q2#|ieqIqp%# zcaBuNy;<{MhvB=sGrzx>_4(=A=LalruT%T+a`vBB&EH;cX`8n8=d0QGw;Md!ZSwF) z;<@Y3|NsBbKu4hXlZBCiA(}x4WG5(27}zf~L^n0Jw6?W(bar(&>1c-q%IoX(G&}hy z8@WsPPHhU3mhm@`_71g^(^9lm_w-P0H4~TA2oaB9*tv_>(u%Rwk6%SnhTnA8P99+q z?)@z;;ZlY+62=TnOeasDZS{(j3J5lrWfBy;$;QfQajsSE1QRne)5}*1Z(FQST>tWw zOY{euV1v59Ln9NHhyH`=I2JW9Mu2fjrGiNgM%QrJ;80I6+%%$LtnjzeF;&>tof}4fM(S`H9 zRxshjqf?8s`B>nw85gEZDYwBf7mbILj_|;*H=O{l0{k1`uXIvgr8Q@(L8D5Cnieg} z8oE$a%_6s~2^ocTNXx52a#lH#(uG&-0YM^l9oYdl7qasw0pk4{e@c|4@uYC{ZP6y!7$3VQP z#r1j);9=<;5S~of0T|jLtS)^C{q_*lAH_*pY4=qurWc{DPWKk$${v+N2>JY_Z T!Jt=y00000NkvXXu0mjfyIlNb diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/inscell.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/inscell.GIF deleted file mode 100644 index a2d12854566ea2caf41b8066e2736def8908457f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123 zcmZ?wbhEHb6k`x)Si}GU4F?V!VE7LLPyq%8#h)yU3=B*RIv^2{S_ftaja_&C>9R1c zDs9MMQrI%RJ$FvVyo_17v0R>498Tymhvlt%Z?iwk^vYV(o1Q9?Hx*V$XsMk$w*JX1 OBj1bj#HKPZSOWl>@+koT diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/inscol.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/inscol.GIF deleted file mode 100644 index e3d132dae112ee8f3fdfd280c4bb928d741f9c32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120 zcmZ?wbhEHb6k`x)Si}GU4F?V!VE7LLPyq%8#h)yU3=B*RIv^2{S_fuZja_&C=^j{l zK!N$f*@bTsG?-jghN>3kIo|GSYF)cv&cTXx=1X>dS;d)P@kcOIWcoX1YbDDir;CMo L->sX#!e9*mm{uvl diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/insert_button_bg.gif b/src/Umbraco.Web.UI/umbraco/images/editor/insert_button_bg.gif deleted file mode 100644 index 69c131ce2992df593b0f4299bca3b823819bfafa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 703 zcmV;w0zmyoNk%w1VOjtc0K@s=_xSkz{{8#>`}z9$_xShz{{8*^{QUg;`}_L%`1kks_WJqx{r&y?{QUd-`}+F& z`T6KpoHUDH@N+q;kn@I-kkc5K66DuS_Z6KrDKY7RyI09*;fWvU%-gtB;niQErU0 zm*isj!{r@5Xn}%*gn@BrfQ5mHij9PfUyfe}2OBRWtq`cJq8+WUnh>9$sjjjey0H)tuAaQc9f1|Fwgs1#2OS*J)Y8+b*w@wD)!H1| z*x};Wufcfgl`*#qaLw)i7 z zoH>KW+zFGYhMgLD9zY-h0fGes24t9!p{j-j6DCB>;GpUR4OAyM=(>QbRjLa#3_uGO z>HrN@Gf0g)AZ^&Gar5dmTXybOr%e$Eh>L-6)xsGhB#tWqL*v4b9V>pE7=z`>iZc|> ztl2VQ$DKVp7OfcZVa0#}IK>dbvVaB+6ueeo*>VKfu`$SQfI)y|-o6|7?%lifYXPr$ z!>-Mob%f^%8bpZhntE^Fvsq(UkUGIr1<%7n7f-%?_yFe9t6xtZ{rU9?5{QT&pnm=7 z5zyOrUtfX!=lcb?pMU?kmmhlI6)505fd}4aAAtcXfPj1e&?n%97-p#9h8%WyVSMw& lC*p`CmT2OMD5j|5iYbQ3;)^iGD5Huk)M(?4INlfn06R=DYYYGY diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/insform.gif b/src/Umbraco.Web.UI/umbraco/images/editor/insform.gif deleted file mode 100644 index 361804565d6b561871c0ca12b888c78660acf125..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 119 zcmZ?wbhEHb6k`x)n8?iV|Nnmm28II%4gg8TpDc_F49pBVKsEzN-hs(uPX9{D2mEsx zRv)#VevjwIw@OL(X=*}CUhN9%Vm-L%cI&qpF}sSt{o{XgvdrR&tC)tI-I@MN7rt0H WoOcW{U8}}=XWevf?k-_g25SIe5G@b@ diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/inshtml.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/inshtml.GIF deleted file mode 100644 index 3442c48cd48c9fdb78d08459a533baef47997cdd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 191 zcmZ?wbhEHb6k`x)*v!E2|NsB$>gtx3mIDV4R5LKN07XE8KvoNc0t+y}f#OdVMg|6E z1|5(H$P5RToPractGB8*oV8B4zMxVig0-E)!%?CyC4E;(ZZgBH<=-c;F&#@ZcF5s5 z7U{~Mp<>3!9P;sHFVz3j_fxPWUvMRMVdi) diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/insrow.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/insrow.GIF deleted file mode 100644 index 2e6a774377e172f122d6f0b924c1218d2c6feda8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124 zcmZ?wbhEHb6k`x)Sj52a|Ns932Mz!!28I>}B%t_{g^_`QkwFI}22$(5>?pD8&c8ej zr+ zKPUA)%bNMMaPHTfDbMq!KQCYOt#BOuRdzWuJGk5j=1?vtiTz6>crlTvip1Azz=Y~D!*6uvL zaqqb;2QF?saB=66t2+)~*>n8*!83OboxXYM@`ERDe_y!qKe+fUy8e*5|N z+%@}u{{H*#-@pI=|1;19DE?$&WMGJ8&;i*7iW3L+gAK7w%`L5Mtwu3j+FdbWF|zLF z9__K7PS#c$W~PCLUOt|lQ>HFm*5uu5H$g8XLdn8#$?`2*w@E1~$Vx*D)N%;3u@0VXvboi>*V0{A*;HLk-{v=D&_+b+wcJF`jEB<5wG8q|kK!gZ`FaraVVo(3d({K5uXIu`5a6TsH a;#+zDYvZ#Uy^qv#6FHB6^105+U=0A})fi&{ diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/italic_ru.gif b/src/Umbraco.Web.UI/umbraco/images/editor/italic_ru.gif deleted file mode 100644 index a2bb69a725e823b8e671f0640098c4fa80939b2f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 78 zcmZ?wbhEHb6k!lyn83i`>+8$Fz@Ye(g^_`QkwFK@28xR?Ffgh1^shYqmVdFy0;Z@$ hzTc5`u943p7fMVz+n$-ed(#4|tKWK`2QxBQ0|3J`7vul{ diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/italic_tw.gif b/src/Umbraco.Web.UI/umbraco/images/editor/italic_tw.gif deleted file mode 100644 index 4f6eeaa2b2111e9995584a66644dacd01ebf33e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 274 zcmZ?wbhEHb6k!lyI3mCxFE7u}&!3;4KY#xG^XJd|`}?n7zdk-bUSD7T{rmU(_wT=d z|9*dee|>$uy}dmH1H=FS|A8d&K=CIFBLjl~gAPaw$W8{sAA<=#Bx}ZV~!Miqw UgwLW=vT`Q(s%`JMrCAxQ0qpY;qW}N^ diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/justifyright.gif b/src/Umbraco.Web.UI/umbraco/images/editor/justifyright.gif deleted file mode 100644 index e4cea971489cf2526bd45b5044e6d3435741f830..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70 zcmZ?wbhEHb6k!lyn83&Y1dNP~ia%L^OhyJB5FaGNz$Dw#zw-23e&HEgu66Wp{rQ%^ UM8cwz_vB2umD}FE7Gtmm0QJ%n+yDRo diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/left.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/left.GIF deleted file mode 100644 index d1af83383d05f3c6f74552df00f9d80190839136..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73 zcmZ?wbhEHb6k`x)XkcJCaNqy~1B2pE7Dgb&paUX6G7d}%E&VG`zvW*%XUnbb&G%&5 Y_}dhfyW%=0y(nMz_z~OPJ)#WO0LKLw8vp$Pjwrc9Y~ z?%cWW-~WIA|Ns5>|IeR4zjp8W^Y=t=Vsbbj&ZE|)?%Z0yEF;3`$xUry@sI#Z?ciQrZ@QJW!usc-w|e4FHR$QNaKJ diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/menu_check.gif b/src/Umbraco.Web.UI/umbraco/images/editor/menu_check.gif deleted file mode 100644 index 50d6afd50536aa9b942ae24012a4073c250753b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51 zcmZ?wbhEHbWM^PwXkcUjg8%>jEB<5wG8q|kKzxu40~1#dfBJzLcf(v(F)&yI02q@C APXGV_ diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/mrgcell.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/mrgcell.GIF deleted file mode 100644 index 7f14362fd1f69ecbaeef33dcf54080c9d73dc47f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 142 zcmZ?wbhEHb6k`x)Si}GU4F?V!VE7LLPyq%8#h)yU3=B*RIv^2{S_kG}ja_%nDV*|b zWNv=4wtAbthMt6AiKs(E^6PzFyM#=1uj^Q{*gRi9ou|KiGh^b3B}G5IEQ<}3bIn@0 hLUzwT&n5BAMsA(j2F-PBk8)XSE5l}d2w-Bc1_0~hFbx0z diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/newdoc.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/newdoc.GIF deleted file mode 100644 index 24e85821ff3e2bf7dce62ababba9dfbfd04f5763..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102 zcmZ?wbhEHb6k`x)n8?8J|Ns932M#cRf#OdVMg|5(1|1L&B=5jv-qXKQ`+@$Vh!uOe z)$ZCvzR9@jG3_{er%cyM9*&uxe6BBT*)2UgRarjzpp&dZpv9HSBFov6gBTdB0Y3a9 AB>(^b diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/newdocument.gif b/src/Umbraco.Web.UI/umbraco/images/editor/newdocument.gif deleted file mode 100644 index a9d293842354bade04fc9607e55763fd0ea9efec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 170 zcmV;b09F4-Nk%w1VH5xq0K*0VGD>2SqO#J~;-aN z((?HJ(%tf+t-`|0-T(jqA^8LW000jFEC2ui02BZe000D6@X1N5y*TTfdRNNeY>8o> z4KQZPc^-wzx@G{}*+AEJzAuCxrm-U+gcqV~fj9y(hozB$v;>w-LA7!<0TB$xmeMs$ Y1>z^>8fd%Q>Wl40uW#=7ygmW|J7pwMu>b%7 diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/numlist.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/numlist.GIF deleted file mode 100644 index a2683522f4fbca7c59cdce63c7ac20211e006f49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 111 zcmV-#0FeJjNk%w1VH5xq0I~%D0s;a&S(>c5(MoQ;hMLOK*yvJx*8l(jA^8LW000L7 zEC2ui02BZe0009tc)HyFFv>|=F}0bofd8LU3_`XHL13_jZKS5jZLdL qklCGA#V($?Eohv$~E=l}o!A^8LW000L7 zEC2ui02BZe0009sc)HyFFv__Ay%A`;yT+FxC|=hX#Xykdw-jk0V}u~DM=9B?30|g_ Q>jw#1h)7a$IY|HjJN(2Zp#T5? diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/project.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/project.GIF deleted file mode 100644 index 98eff56ee194db4e05686ef977229162e6e101c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 149 zcmZ?wbhEHb6k`x)Si}GV4Gj$k4jlN;!0;c8|AQz7#h)yU3=B*RIv^DwwGPZt8oTbi zV~_|?+T7nNaps;x2qVA4gmdPe$D%?U)y|i!^$28bT`J8hcxByXCSO0p*+nN~J}GRv tvqI4FYuKUFIzKisG(=`Cd0MTf8_zU0PBTuMX*}~xT0;ZFnKNl= zX~zHm&zw0k?aY~(YgbPAf5!O#?+<5Y8vj3&_J5}FnVD(-f!Nr1W?I_+|Nnpd`0?@M z$5*djJ%0T7?%liBuU|iN=FI8Sr#EfdG;P|n^73+^aq;o-p`oF^zP^^0mYSNHN=iy% zVqy%VU^D~wrFr8NQ2ybEAQ~Fix&jItMw&T)F+G-w5txr$SV3)sh LW~cGN1_o;Ypum?a diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/props.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/props.GIF deleted file mode 100644 index 24450f02cb1af06b87fcfe1fe1fc724b1d604227..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122 zcmZ?wbhEHb6k`x)n8*MD4F?V!`2YXE;!hSv1_mYu9grwU-hs(`O8-huV}%7a3M^OW z+$;FW(Xuch@%GP}SEsg}m@?(eRV5#cR9hjp{6sev#R{EQvzpGau== W2rsqUzV2F=PuIc2n-(%KSOWmXNiI_W diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/rel.gif b/src/Umbraco.Web.UI/umbraco/images/editor/rel.gif deleted file mode 100644 index 79e22138f87c7e1fa282877d3f1ebc591a5f9678..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 327 zcmZ?wbhEHb6k`x)xXQqAWZsf%hfZ8QbImaItYgJv--ajAoljGze$JfyynNC3@;NV> zm%Qv=_3FcyKNGh8UUcaH(hY~VpZ~gP@A=!$e;vK~eeRk=i#MHEzU|DO<2Ot_lcz7+ zJzq&;xw-!O0Iywz`Tzd?`~Uwx11*8#PZmZ71_cHkkR>2LF|egLOepZsk?KFOr08Uh z)qMYBPK^d492buto@l|d=yKM>*L!Nt^?Ir?ObS|iW6{&*i4Gc)e$l7DN@>eyiU>-| t%H<`cxJXI%_AyDPWn}p=6qlAu`ZqMS`1kbn1}t8(blLJ1D-|3WtO4qUa+d%A diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/removeformat.gif b/src/Umbraco.Web.UI/umbraco/images/editor/removeformat.gif deleted file mode 100644 index 0fa3cb79734b6ddadaded7a30dbbab4cdf7ee11a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 168 zcmV;Z09XG3d^!9RvqL!@E;^*>mjCP#$`UVUP zOice7{{H#L@aGSZ{`;5V&)@$HXD_~GICkVYgMH{31}iNO22DkEMr;NH1Q3S7jEqbS z@811pP*vq*U}3@3bNkt6hEo^zF_?L?GpyUWn8C%`@4vaO8HPfD0Ky20zd%?0{P~^X z*Ux__;y-@=Vfgyv7sKN8M)k>SzPrwrf!{9^bB^fCzR869OXcGX~b{`nEZw{Jff zzJC42@a4-_AO_Mv_JlMefG}MA_3K{-2?+rPPBs}}B)(^0`2CB4 z@$)BenEv?ngW=}I2jHOk4mAAR_a6*g!W;~@&OT%~xB3P{Z_iY?IRF8KVeq$a{~6xC z1%~U-FAT@8)G~bg@`XW7LmL>Te;HDpDj7JrIT<*)I2hPD*%?$^#2KDEcnLPNr)wHR zSfC#~i~#}&!{G1Vfzj~q7sKCQ{~12KeaUd|`elatx>^P!eNBc3HzgRZT@+(rV)(`2 z?0JRZ{L|B5Lpxe0F@y*Ep(Htg0Kzc%=g;53wEUZ4>zUIGq7o7eIoX*Ed|b>7FQ49F zc>U}?FgOGmnEw9;s|6WaSJlN39T9|)jsXG)Bl7Zc&oB7=`E~S@*H0O^*?%xdhzkKD z=p!(wzcaje`2v_6*?|e`4}*KeUlvUXTZZsZFRVEaAb=QYRnP+j5X}l9fB>SI!2kh7 nGlKyFh-L-@1Q5*(1_&?!&)GZ0T&qcW00000NkvXXu0mjf0SSYf diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/separator.gif b/src/Umbraco.Web.UI/umbraco/images/editor/separator.gif deleted file mode 100644 index 4f39b809e98103422a7a8815d9e0ff23123034b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57 zcmZ?wbhEHbWMU9un8?J?(9rN73>X*~6o0ZXGB7YP=m6P3N(4x7O<|q5`t188u7e^B F)&TE55J&(3 diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/showStyles.gif b/src/Umbraco.Web.UI/umbraco/images/editor/showStyles.gif deleted file mode 100644 index d71976f8a0ec161edc5c0689c782ed2455f8f7d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 197 zcmZ?wbhEHb6k`x)IKsd%XYQV($8ODCaA3ox(_TTHUV#l!iBl@-R(SFPZmZ7 z1_=fokQB&H2398pwZ4?hc^RwL<-Fb&u5&s+cY3kieH&-rN$rOxoJv%h#^w~jxJQB8 ii7|MkCSPaP)*Xjg*rMWon7K*1r_QT7o444B!5RSkP)h3n diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/showStyles.png b/src/Umbraco.Web.UI/umbraco/images/editor/showStyles.png deleted file mode 100644 index 6dfbe2c0b29c22d7d5398f165cfd628f7f320433..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 628 zcmeAS@N?(olHy`uVBq!ia0vp^9zZP2!2~4t9J`|eq&N#aB8wRq_>O=u<5X=vX`mo` ziKnkC`*mh9c75Z>eY>N;Laq@dj-@3T`9%yX*PQ{G!&3qja!D;tF3Kz@$;{7V5av`= z28wfm#GUgCDvL7HGfEiN@Sm{)it>R)i&7IoN)<{nb5j{izw>hfWjH}H!TD(=<%vb9 z46z0OLCU$ohG!Nh=OhA^7BOr;u%#a;%mES(Pb|vI%u8oDApNWzD8>%aUs{x$%JB7n z^J|zg=ltB<)VvagZ)c|M2a2(Ql!auL-4h`d}Hab0vVUs=Vq8JxLV&ikvByMKy)dcIvCgZ0u8 zrk68cEq%VNwd|}b$3t_z!wKizdn8|8Yc}4#*vPF#xbN#T9{%&Ms|)r7hD(^H%|EfV z>(BEGcP2ewdQT?YDZj>@1H;5>D1Zg;TFp%;JaE} zAZ`gTbc{h^;WwY%pf+glCi$L8e>_d~H!}1nZul_$#;FqwEAm3?XEKB=|9t$fo>{*A es`w}#3i6n%gZl)59LtvU{FwU8Z1B0(^bMU5zn+9H<8WR#R7GiC;p&`l5_xZ_s$ zT3fq_h9FiWpw`xB6BjL! zBK}|us6#=C+w69`&1NetElo+OC@wDU=;*LmS^%wUYisN7?gq_H;7A6}W>DV%oK0O_ zUErY$)VKg$+u7L(=qqVQ9s~M%kEz*Ya-y`Z$I{Z1`KUVebPcVurJe(h&7jEz?zpm3 zDw>%slk*reT3U|Nx&(VOa5jL(MnIQ$80!Fitun<9=%e>&9iSh8^2H1c)Vr9mjn#9v zy}ccjlyzDj1INi~lM@t`g={Imno?Z+$=Ne2|Y$3kXf%YkTSCzMl`q@lQDDF&4VE~gvyY^QS1i9=2YND3<}}$ zgHe7c9|{$41tM`+SXdAwIIn@&r<;l;sc#gBe6HX)#cUIoLo3_A)3j5;bZGN{bsHmJw0onUox6 zl>S|Up6H`BkX&~;Tol*y%m~2~aQSRWy+D=fzoB}4ADW~RuxEPzm6%Mj5Ew53Bk@cl ziZSp&HWeXL7%_yxjY&AJ>-~xg({TzX({Tb)B!)msv<3}sBHd_}N)~4zDa3$caY{MN z#B;S;jVx3mQiMpOL<*%?ED$K8qa=K#M8TIRqQ#0i3aO+wR*9pTddxue#%i9$iu;Xa z8$nMnnU$DPyBgC(8*x44E?B1RUyGn$zNfL8{Ub%ef;uoJ^`0V_-vlZoKXB?+beR{I=M2WrFcKle; z(N8}9=))r)ynpz;L+>6uuz%m)J-gr8wR6YzZG~I6Y~HkS!}^q-hS)N zH(t-Om`$rQDbl!#zzup`#%tR2l`Aj}sz%aYU7oru<&~G0E?JzMl$fw+Vf+Hs{CRWZ zVq>D`C>67#B4;ecIG1lLO&N(8LMjUwD4p*nlzq zqy0vW95H;@&>_$H4j$y=J&-fN%hLmZN$mre5d}f)t;62d!28x3G-qw@y1aaA!TJpw zH=!O|3%4OV5UZzp&)$7|cIEF*^Pj~++VczbydKW8l{Ok3-}E;k^8h{P`W3Wglg z6=tv6>cNpu&$U-Z&L12&@x%_l+yblTc<-S}R(s^AqS>A~19$ozs)~<^-9BTApAVS% E7fdjV-~a#s diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/spacer.gif b/src/Umbraco.Web.UI/umbraco/images/editor/spacer.gif deleted file mode 100644 index 388486517fa8da13ebd150e8f65d5096c3e10c3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43 ncmZ?wbhEHbWMp7un7{x9ia%KxMSyG_5FaGNz{KRj$Y2csb)f_x diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/spellchecker.gif b/src/Umbraco.Web.UI/umbraco/images/editor/spellchecker.gif deleted file mode 100644 index 294a9d2ef53a459ed38f2ec884795e1e01cf14a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 591 zcmZ?wbhEHb6k!lyc*Xz%|Ns9F^9%QI_6-e)N{UTyZJsD5CfVILUr|XdEwfZl-?*Wv z%fiZW;j*1oHO-rMUnwkY+PVL-fyadW?zvvpSyX~B_n_o@igl&5- zO`f@G{?hF;=Wp;2ie9s^sFB`>Og5ELeYa=CY$}ci&yS?&R9-=cD2>3?ep|$8K`SI1o^A!6R#1LdS!| z+C2+)d}yAwf9dx76XqXWwDIEl-B&m4zP#(umB4~EZHw=PrZml%zc#JajA^<-k@R}5ug z_U3Do3GoV%k4|9YiIGk)S5AJ*aq`M`+%FXCTvwM)+^$zD2i7*@MyX^wTb~-Kz659TOj-BG7nwnZp zdYUSA8eNUeEuA9j^PUtmt!3*HR4sY3W8y(+32tGJI}M3}7rHrZt-549Cj}@lSOWkL CBGTCa diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/split.gif b/src/Umbraco.Web.UI/umbraco/images/editor/split.gif deleted file mode 100644 index 1c92ef7a9f29b58dd2d7f6af8c60fdc4541a4551..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73 zcmZ?wbhEHbomf3>X*~6o0ZXGB7YP=m6OaAbAERsVV&o8B#ijnx|;J WSg^)McbZ}J^;iW1MP*J=25SHgXB4Uc diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/spltcell.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/spltcell.GIF deleted file mode 100644 index 4f63fe62793976cdffb4e22091bac91b20a54798..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 137 zcmZ?wbhEHb6k`x)Si}GU4F?V!VE7LLPyq%8#h)yU3=B*RIv^2{S_fu7ja_%nDV*|b zWNv=4wtAbthF*mDoH+KroYz_mjz>26T-QFydvIOcz6Wnyb)@>&cLfz~ygKK+jE<;B c?vkd6XGgXsZB<+SOq;K6y4Mn`DGUtO0BwLVCIA2c diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/statusbar_resize.gif b/src/Umbraco.Web.UI/umbraco/images/editor/statusbar_resize.gif deleted file mode 100644 index af89d803f8f0eeb965d9443473d5be009641cacf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79 zcmZ?wbhEHbX*~6o0ZXGB7YP=m6OaAbAERg(>|jPrv0~%%#ha d_MXKm)lqT2YSDtso)?w9Z@gz^T^C`n1_07v8bkm9 diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/strikethrough.gif b/src/Umbraco.Web.UI/umbraco/images/editor/strikethrough.gif deleted file mode 100644 index 3264635918e2237257811a745f3a7e31b4369432..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 83 zcmZ?wbhEHb6k!lyn83&Y1dNP~ia%L^OhyJB5FaGNz@*jFzw-23{>5`RB^ury^=8j) h^eUTr??>*V8C&%_D;C}?e9svC>)iK$`!yICtN}048)X0h diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/styleMarkEnd.gif b/src/Umbraco.Web.UI/umbraco/images/editor/styleMarkEnd.gif deleted file mode 100644 index 800807116f586dfe6ef92af740112b9b39937bf9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 968 zcmZ?wbhEHbG-Kdr_|C*|jN#aqGhZ0M;Q#;s5XLAu8UiCE1QdU=FfuSOG3bE&3(6A= z97`EkIanMvEI8QAA*>a{;jqBDoq@??j>pDDN4q5q&1!xaBpvHlaAv!4BCx@|`@hSo zD-lAEj!t3xcXEox<}}|~n(2O9IE$4$`_#)HE!io&?A%PN_P9TsTNYiM=h`jTI_rv3 z@N}o8L2G7BS?#-8*Eg;7*5&Lgo4upimaZ~UySgbqa+U0A(Y4VlJ@yCf(VF(|)c)$j aY^**zlK^t<1)-l4AuY+s#pgA diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/styleMarkStart.gif b/src/Umbraco.Web.UI/umbraco/images/editor/styleMarkStart.gif deleted file mode 100644 index 4de5d4aa50fe6689bae89683c15557a288182c29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmV;O0Al|~Nk%w1VK4v>0FeU##sJ0u008vN^#A|=A^8LW00093EC2ui05AX#0007R zoDHeV?Z1v3K-i13-rRzTbmGW;AVsFBIaOHzTkt%~fe>Hx?VTu=Zz$ Lk*Jt>PyhfsiyTG0 diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/sub.gif b/src/Umbraco.Web.UI/umbraco/images/editor/sub.gif deleted file mode 100644 index 4d7ce30ff9147412d24dafd5ebeef67479edfa58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 148 zcmV;F0Biq8Nk%w1VH5xq0K*9Y0|NsxQi79`kcOJVNJU7aqM%ZC%4un5GBq-gVG=Kt8X#(^7ugLSm;_S64DJ zGIDZqhMK}68X{71!TeAXEkg0%mX10Jt7ez-i0vdcUU#06Sc5 BF?Rp} diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/table.gif b/src/Umbraco.Web.UI/umbraco/images/editor/table.gif deleted file mode 100644 index 2911830c3cd9cfb588114b40205ac316ded37716..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 287 zcmV+)0pR{eNk%w1VH5xq0Mr-&4-XG=a&q$Gl8~UV)yls!N@Iej^5Wv+vYk|?#{7~x zGT6sxlB&Y;^7(>_g2~h3+KzIM(yqDEw{v33+VWCfYLcq*{{H^LvU=X>{iCqL z#mds6qN19*_y7O^A^8LW0018VEC2ui02BZe000G?;3tk`X`X1B5)?(KB@00mJQCSN zE9B*0hM>X2$p{t>MWmN#dOXbs;)npedOjg>x~OhU6O8zQIdH3l0a%P7cOqTD;bAdt z9{7i*gQ;>J6LlpUY-9lzat#_A1ttt^9EFODjU@vQ92^px5)&Jr43#7UY!VHM8yXcA lp(dna6O$XO2?L@dmz|!U8w?Bswyp)k#Kpum$jK!k06V*Ud_@2N diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/umbracoField.gif b/src/Umbraco.Web.UI/umbraco/images/editor/umbracoField.gif deleted file mode 100644 index 549f726921ebad6354c4ac3ee6b3617b949e2cfe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120 zcmZ?wbhEHb6k`x)Sj5Wkd7 diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/umbracoScriptlet.gif b/src/Umbraco.Web.UI/umbraco/images/editor/umbracoScriptlet.gif deleted file mode 100644 index a6b3cde117d50dd02d917cd141067b5d106b9619..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 146 zcmZ?wbhEHb6k`x)Sj5iod7V wZu~gNcWP?m&I^V~fmxzAM6y<&=rj)3n0@}4M@DVJX59}vYW%jVFfdpH01pc~^Z)<= diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/umbracoTextGen.gif b/src/Umbraco.Web.UI/umbraco/images/editor/umbracoTextGen.gif deleted file mode 100644 index c2b12c8d5f41bba1cd07acbbf7bb6f0b5616c9b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 560 zcmZ?wbhEHb6k`x)c*ej`=Dsh)xHH(OEz5CDyw$8=qgF57@(hO+zWOx&?uS=xPVY2W!B3m=#G9i88O>cp;RvpNpBCv7-<_QlBykBVo$n!4rN_T0hF6vDtW(p#5Jp5hudQECZs>1SKtQ;LW zrUrIR?&gZJyiBaT6Q#wP1qAz~Ia`l9Bi@|6ntRG%FK> FH2_zmp^yLo diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/under.GIF b/src/Umbraco.Web.UI/umbraco/images/editor/under.GIF deleted file mode 100644 index a1301dd9baac8b757f40a020eab25050f2d6473f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 91 zcmZ?wbhEHb6k`x)n8?6TU0r?PzySs@Q2fcl$iTqJpabH8)iK!0SpY*0G3P}*Z=?k diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/underline_es.gif b/src/Umbraco.Web.UI/umbraco/images/editor/underline_es.gif deleted file mode 100644 index 551d9148d302c5f58b3b20d2c6a642450889a7ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79 zcmZ?wbhEHb6k!lyn83&Y1dNP~ia%L^OhyJB5FaGNz@*mGzw-23e(4zsAr}6}u9vvi dPIT?{=$xyxd19dGtWDy_KKDM~70ke34FHj_7K8u* diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/underline_fr.gif b/src/Umbraco.Web.UI/umbraco/images/editor/underline_fr.gif deleted file mode 100644 index 551d9148d302c5f58b3b20d2c6a642450889a7ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79 zcmZ?wbhEHb6k!lyn83&Y1dNP~ia%L^OhyJB5FaGNz@*mGzw-23e(4zsAr}6}u9vvi dPIT?{=$xyxd19dGtWDy_KKDM~70ke34FHj_7K8u* diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/underline_ru.gif b/src/Umbraco.Web.UI/umbraco/images/editor/underline_ru.gif deleted file mode 100644 index b78e2a498fb9406ef378d7c543756db552d87cba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77 zcmZ?wbhEHb6k!lyn83i`>+8$Fz@Ye(g^_`QkwFK@28xR?Ffgg~^shYqmVYseNK2Ny ft@O9qnGc`Wq;$3l?I>E+zBOCw^tWDnRt9STq^=gR diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/underline_tw.gif b/src/Umbraco.Web.UI/umbraco/images/editor/underline_tw.gif deleted file mode 100644 index b715390484c95c3d3311012dd541e2b46a2f4a78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 245 zcmZ?wbhEHb6k!lyI3mihYSpUH(9n19-U$i{mY0{GIdf*_%$df<#%XD3=g*&4Qc`;V z{=J%-nvai9ety1-i;KOzeMd*f{{8!R@7~S8!0`Y7e;|nmDE?$&WMB|w&;h9d*~!3K zl%U#|k~uHKi*I6=T}Ytl*F$OTQv9tdV6_n`zCQJ Ua|%tL!OK2l4tMK(4MhfP08!6P_5c6? diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/unlink.gif b/src/Umbraco.Web.UI/umbraco/images/editor/unlink.gif deleted file mode 100644 index 5c8a33db8d4ce4dceff85711cdd43ea3f6cfb6d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 190 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 D__4+# diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/vis.gif b/src/Umbraco.Web.UI/umbraco/images/editor/vis.gif deleted file mode 100644 index 67baa7914fb1f4fa6b9ab25163224b8fc9d27fb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 589 zcmZ?wbhEHb6k!lyc*ejmYr^#Yu1T|}&D*wVXIJ;c*0!GCfBw69g&aC{`^cF)Gv_XS z_woDBUw=;A{C@N4uV?T6ym)9_ipZ+|2=JxSh-ow<=f8OeD>@4``@3x z|26eYHuFlc@lBq&_Qd2x+ovtvHFe3(dFu|(TDfn|>I18HUtGEK{H8-U*6+W%Zr_#7 zhi~jUdgaB3-!s;om^^RQ+!b3k9KN2MQ<_^=-`zhsDJ3m6J#*8}{ki4!PhP&w%FR#D z%GtU9(4!ZxpT2zEF>z{3@5CkRHuX=RHErI)J%^4=pTBV3rp?b^zuB~7$E4}guUx-= z@BaP$M-Cr6dN4dP^v92HzkhvOF(LNvvBbBV!#B;yFH85IT!` z)SK=tw@CBQP4_X*@HMe9RNuII<=W-T|NsC0@83Tl$v|zO_>+Z^fgy!K2V^iPP8ir% zHKa5(x3spkceJZ_cWbJNa&|VUCrzEEA|pIWW7;%DZ5fVE)wxsUWma^G&-M3lvA2`$ z6r1Mf?(AmcVA!cN&D%POO^RQSt3!F3lkH(0J}t`*g=vl!QUXG}a;6>f(_GCYB}4?x zSrXdsB_(?q>l^4A`33|hw!Pxv_Vlna2@j1}6d@UK;&yv8hrP&$MXKE*TCC2euEN*a>RflBvc=VMi?aIs{*Scsi=V=*#PZ+j z{;astf|jzv(Biz+@&Et;A^8LW000jFEC2ui02BZe000Dg@X1N5y*TU5Y5N}`E=ypZ zo?#$r>naM6uI>xVQ-d%k;xmkbaDbB#Q~{NMWoQ&&iQ?mEND2guCgGWgQWh%TE;t;R zrC?g~sYnb2jsrT}bhh1bIVEWT3JYd|XL~9PPy-4A2LldqQhZ!)ii>%V0GF7VnjM^- Io)i%PJHdZeUH||9 diff --git a/src/Umbraco.Web.UI/umbraco/images/editor/xslVisualize.gif b/src/Umbraco.Web.UI/umbraco/images/editor/xslVisualize.gif deleted file mode 100644 index b8dfba19b6e7a3dafaf9f69b0abec4ac5641e80e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 663 zcmZ?wbhEHb6k!lyc*Xz%|NsA=HDP*x*QD9g=55=wv#WbzYg^CnKmXmlLT1igdgA8y zn@@i|d-vzX`#)RHe!2Pd=kZ(LZ$A6={Qd9G-~Y~Bdt&q98!tZmp0V!4Ti^73s~Zhm@J&d&XZ9=&+|^yTZ0iBns8CoWmH zsek&cY4aBDIdo+D{Dtc_ZGQgx&88hYCQYAy<@)t|_wVmNa`@oUgW-{(D<;JLJ(l=( zbNHqi`DN+;lZ%}f_7z2k2R>im{$z&Zg?iJy@=g*(tzkmPw_3P))pFe*5`2PL-w{PFReEIU>!-x0p-@kkJ?(N&RZ{ECl{rdH* zSFc{aeEH(Vi|5auKYRA<>C>lAo;=yKY177y8#ip&uzvmeb?erxUAuP8nl-CeuU@rk zRd;uHRaI4DVxotK2QVbTAxZ+F_>+Z^fgztk2c!oSCk*Ug8uFW(TUy)NJK9sbds0*S zlarF#(-@~spTRh}EzLV1z;B_ix3?c-a(lW@c2stBc6MZTh<8eRhHp$xY+O!!Y)+`p zidLTtznI+kgv8vq+%Ugwt=?Gyd9itM@#gwkngOXkN3#OspT`^N8LDf`%zYB{!c2{k zLxfk7>v7A~PtSD&?(9BrfT>gU=p~Pfn_9WM1w~{WA_5p$4mojcO*zS=e1wHlP_DyZ z!jTJ-nh`=;Y@r eBVRS1JNe0}nZa>sReq7t;T_$5{S#On8LR=nwl0nU diff --git a/src/Umbraco.Web.UI/umbraco/images/errorLayerBackground.gif b/src/Umbraco.Web.UI/umbraco/images/errorLayerBackground.gif deleted file mode 100644 index 5ff1abba8bb307ce35f0fe86d9db309c3a4fb078..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1487 zcmc(cX;Tvi06;efxrhNBu7Rqk45H;yv4GN9v=COAD6 z=B1ep2AIb)w23XK5zfv;pDFdVHE>#rzUC(Kpbt(-Hp*nEa?~_AfhrX6MJ}9?fsTwA zT3d9brATi#($$4@c4)G*V34P)u0*=;!#`&t9UZ#rDqV#D=B1-bC8`)UBM2ht(3O|L zsa%6dgbWNIeLeb`8lvM z)(VHoEf-W&R#o5l{ig6%&Fwq2ckAjK?lp>l?-hdcU z)2i75;3!{&$5&&p*6wr?2L}e{`R}I-#2}b;3BN0TYN++LyvQ{;pL%`{z|ic1!s~%( zLIKV*CZxf20B1$R(}G+cx7Cf@q~}o9e={}-V8{+f#TenEVjRukz`CO~1i;14!Hpf+ z-ZuVBh-d0?8+TYDA2O15fE2*T-IWctil!fcIO`3E1#tid??mVO z*0^Qa+g$Zf$M47D33$Bf%i*rJ*%4uI!L{8|Ae-d8y~I}q$Yq-Yi4oh5Z3@BT3DHHb zpY%wUr=Uu&Qvy?F(jt%%TlY_E=()>(019?y#RevJ(y#&bAxxB z@VG4nSnqp5cjQ6;Dr#8X$Xrq50qg9N7ofGxyls`M1Hc}{tr;$#&an;kc$0f(QBS7; zPPXcOdjj0BWLR|;0KWE3lg8NHt$&;E*%?;`m=Gev9w!LN&VvF3YAfoVxwl1h!nPT{ z7?gu)Gs=@R3)y=J_>)-tG6&CtZiUa3o{VXz<;(0-PlNAV!5mTqeO|~NJ0*Ennb0{O zlTmx4%o*J6E|vv{hMc-=Wgo|O;j#$Z=Y+Ymn3*$MMm<1maFAWduChYLsnW}l@$=BT;}?#Rt|gD9L{Tn%I(P1_sPn^z#?Vc*CF=kI9T)N4!JU`MfZz4!k=+B^ z7sL-83JT78G(=!*0;LRq8c5n#{B6K|`@L3GOygo_wNoyCzcXWnAM6z8d+b08r*Wx! zA!hFp!CbUhVdI#<#{@y6$%#EC-A9&CN(wIAG2|gD+=ZGOR|cuL^Q6uBeow(I$B+HS zw#b}fMVw4bkf&6>@dw_gM{eZ_&|{A}5%ia*WC8lySMCW9`;8~SPXcB|up&sm0FTg# zUYgMm$3%@Xd~1{D>0$px&9kFnUfQvUs6_4ald(}R@aC7T&4#yy{!4~`_+j42Oi2_I zc_)Z%M&4JaE+MnRJa6ONopPpezP_&6_(3FDGJX`xz0rmCaVENWf3_K2>eVly%Tl6` zY2~3K%k*hzYl{gQ_Fp!^qhUU)nz1O>s#X=-vZ|X(U0&6z^L)&HhM97f8JVkVF&h^o q%Vu;%?qe}&##xqC!)%MiY|<}VEC5vq5W}Td=M|8IKjaDkd;bNYirEwZ diff --git a/src/Umbraco.Web.UI/umbraco/images/expand.png b/src/Umbraco.Web.UI/umbraco/images/expand.png deleted file mode 100644 index a0728673212690d37d80f249886ab21a4e8be66d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 400 zcmV;B0dM|^P)p00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzB1uF+RCwBy zlD$sDFc8P%kgA`wsH!rSp@I?76?i0`g~wpy1u7P#ek>IUNJub5Yzze^TuS4vX#Dvh@1P2x3_mZ|1}^|+V>FAv&=M3#LkIu5^7_WSd% uJ8hVok26#pJl#pvYIPl;fBl0S0R{l^iqX6rNf|Ey0000DIO(Acwl&EIXV2@^gz6kJ$v;D12HgN7CV|Np=C|Nrg(|6lo5uL$V) z(7ozJLdS!Gsk{ID|DW8w=Kuef$=&PzJl*vF|E*s?AOGF%_UBxUQ{lXTjt2$_8yXgT z*mmm0gf;87p8H%dmzn^R3ENcki0M|8KIyQ_Sc8)9n_< z>>IQX%zF3mr4;MsPakzAJJ~*cs`^P}o2UKi#oIJl;u*utj1vsrd^_HxaKraYs=#gC zKE)t^r%<-ums;Tl(>PAH8#iA3J#C)-Le*0jPoXWq@QyWyK~0>iFndOE71-CKZ8W$<+Mb6Mw<&;$T9v1@h! diff --git a/src/Umbraco.Web.UI/umbraco/images/false.png b/src/Umbraco.Web.UI/umbraco/images/false.png deleted file mode 100644 index 561460d601a5646a597149bf2c63e187a63d8e89..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 894 zcmV-^1A+XBP)H{e zgve~t#T#qeTi=XX){_9Ek}qU)^9w(f1NF;3=K5+stp*&`L}qjEi(fy}*S9IYz6%tK z0|aoTg5{Yzm_Bv-`t^%{e6&aLua5zSO=EYa|J{*wTefPx5Xdc-kSi9Tm|-QGk!ni7 z&@|k;aNZmH;m6&3rGN4f;47k6GJTmdZQUD8+pS|_{6=|peByhj>RzZb{AhDy=B@tz z0ZZ4QheEh_bE0~7?A)uLGGEpKAl;ch7-|Ta*_kY|xBi^U&(3}D3G>yJnp5d?FXwV7 zdJ-N9gu{_&Tl?CB0N$oA2rq2y?z&VZon+uU_ouV3?PvZC0B#eBaC36F(9t?5WgT;= zl$&=P>Rc>`paTEe%v_JI8~V$3E>8~>!%#T25-T!{rKVY~Iq0C314Q8pV!a+(@&D>iPMhJSXcwD0heJK7NN z!~!eWbqSx)p}T-*3&O0F=vKl6fSOLjHBtlQ`KX$vf7Nk@jxT%106^+`bY3GEz7GfR zL_JV<+H?&q0RxG6=0GBRG^uzHK>$x^{*oj2e)BN=F_EUlBHI&6p-~9PfTwlG3(hGa z%FkGjaZOocpi$UxzNA Ueb)Qkw*UYD07*qoM6N<$f~nH4#Q*>R diff --git a/src/Umbraco.Web.UI/umbraco/images/file.png b/src/Umbraco.Web.UI/umbraco/images/file.png deleted file mode 100644 index a20c6fa0c88a959109264446a69020a0892ca454..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 251 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPF$k8C-m2iN8 Zo#CR6tYFg7L+v1&Jzf1=);T3K0RT{JOkDr~ diff --git a/src/Umbraco.Web.UI/umbraco/images/find.small.png b/src/Umbraco.Web.UI/umbraco/images/find.small.png deleted file mode 100644 index c3f3f42e2565cfa2ae93591bfa3f5fc8c6791f1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 390 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLlHQ?;;f3qg7 z|M>6!?~}bVPQP1z{p-dpN2V@0Sv&dYzr~in_E+tkkq^`~?SW@4kP;{f@(cct3K$+J z$4Ua_4tTmahE&{2deF|`$HvB%=cmA|&>*2ulb6Tm=2n)MS6A1-(8N(tw0V=!recGl z;*ZQM0xk`+=Sr?%TG7@huh5{t#M2|wbH?Goi39us4hD>63mCMGjhXEo7!Gb>2;H!0 zQ<1?3u%7POf;tC|9s(*#U^Me%%UdAFr?+4$10##jgQtl}51A}`xKA*0D6kpK@0={p z#=z9UcqYB^^G`;mjz-?8=YiUIYF5+$U6b^{<>_ajiOlv=3>*dw=4xE?cJ7Q30{Vr) M)78&qol`;+05ztOs`9(+SbF*YujcY zJAG}+tffmg9b33|-^q)2{bKT$?z+}7ZRM0jTelv*=9gH}vgnvkOn%F(ty5MVGxSW3 zNG<;U`~Sv+=eF-Vvi0`&DVvUMz5Kjo>cYtU+Ob@VarmW_^Pet4J|F3j$Qlw zf9d1z$6kLw_WA#@-~W&O|9|cO|L6bzfB*k~?Y_Mmc5J`?;O@`gpMU@PI%isTaba?C zuGi9e#Xy&Tc>A!r&}-|WlhHqcJSUPJ8(8nO3kAe{&0*XIb7#SGs7<51(0m>5$97`BPIYc599eL$B zgtaCdnvmGY%P#zD#f1qA+1LbBrB-OP9%}0sQZ+fk`0yYLlbDKH!i>j<8HDwmCj6Lq zf$^|3tHikp8y~qGniG`MvA{8zjYouCC@aCj=}6C9eTM=MkH*AK9>at^2@{hzLp5a= zDD1evNNA&#O4l+k?x+vp907ALaRZnVXvPH|Oa>)FQ=yet|50 z!bYefcglfiT5~=09^8leQhj9fjJ1$f>YRv{=JGcmKi5=@UUKAF^w)}MgURRQN=Y*p zsL#NfZHiwSU!hr@fBOODu`)JzWccU+vIgZut{j*k z4T{RNmq=pv{59-`?>|Dsx&~YKVCn(4EC?T25&v#;gJGSkM||uk{ZR3`^s6ss6L+qT zQh{Rpkp{;xT6f$FCklBbU$im)0I+igF5bAR{1spTWQO;P<6#c-00000NkvXXu0mjf D2G1D$ diff --git a/src/Umbraco.Web.UI/umbraco/images/folder.small.png b/src/Umbraco.Web.UI/umbraco/images/folder.small.png deleted file mode 100644 index 7b6835d04100e6ea4d488a0760b72ba71b353144..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 413 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLlj%?@Rw*&i;PA<=!Tz=lg>0ZL++#&FlZ~DgS?e|Npr9|MQmr*R%J|GyMO5 z&YVh#t$h;z|NjS?=csE8a*||8kY6xR1S?=*G~wC~RJ7UC#WAGfR?-9qW(LM13W|z6 zEF6pt3`r9N#Kgs)yLT|K2sCfl!6DGm-N>LFz@X3|(Gn@q*x)I{;=lyba7Im|jYWll zsl#zbq|Y3AmIf`0pGRF7L40SP6b|c-1OY)|Rt^QWL^eY~JC1}$Y+OJiBOC;nZdBX^ zDdf;mU|>8G;~>hw$RZ}t#3UzX7znoCnQ;+N=u5YT(F6vd{*wDlECOu|4h@V9$@MtwUbVx|CCuD_3M1(}kL@rTAt`?@NJD7g1T-cVt+{lozLxII0fq`Ms9dUP2 S4@VK8*$kepelF{r5}E+oVobaM diff --git a/src/Umbraco.Web.UI/umbraco/images/forward.png b/src/Umbraco.Web.UI/umbraco/images/forward.png deleted file mode 100644 index 4876480518a4337a01ceed4a98176d5a398de4f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 418 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLlt*ika1(h4+o_;4!#VGM-K3Yoj%Pa zl$ieL2|I_v0tRMz5d$MeRt6@H$cEm|nav>UB5pLM8E)L%^wWV6sQ*Dao8&QuhsSA`D4Afp7WG*3*Rh; z3xHji`;f{Qr-YKUcE+jR?efSretxx&WDEZ5Kp6he@Ps65>d=M!?SL2nk+yO=?eq*e z(CtEpt^NypL5|1sYZ`?lUAUkN^c5N>XnA85@Q^xwjeFYOp3C#}a zHn963=}o`!<#;@v?ns|m4?LBXgiB1FGzEnM(t|}KyE^*qfGRmEB%Pk}RCt;_Pv;q; z76HNqq0F}lnk-J0#Dba+rBbqa0A~EtaONtWn}_d_M-ZpT2;?ji3JStqMa?@ZmdPX= zTq#gV_Ido8pfaG-T2k$Jy;cl*0_LLNB}Aj*q>CgnRRXGH^eBHh);MQI_PI}xb1tX|OddyW7d8doF(D4G0y&Kg=U1)lrvBkRdEgAn&YpWK)7FF_ zO`KDR+SUr8RB?R*V+siUO(){p8Ev7H;ZSCO#!x@rpA5_#7?f@4^r^(u+m}It5>W*D z01$DV!W6U#oGVnbeNyJZnHA9JOv$#X*T91F^U_IYN~VH2gffuvM6+8egCf_g{|0&7 zthJt^rtYhj1}RrjRxZ>}H8ti;eauI>>0UIql?R%KiPs;mGXsZUZf8&wEjF3bfvvMZ84)BX)l6^Il7Go3rYTLQ8s716vRLh5+D9^vS}hjT=Z5*S35 z5hDD6A!QEqGnj%>)-N!&3h*lxRQe;yRi@?+enk_n6j7*DCSFh4__kfqo8_%Cq}0A3 zC2Oj76@t~Eogb%n2;4$a)$^vZmg^dr3uqaJj_2bU3-~2QHI{05h%BiraW5yFi4g{M zo>piBzZhYM`U?{0zQ)0*L1~;ibKYpws4-QP7cP7(%6y^brj>(CJbsn8#=A_mO+-{|m9l9vi@W f?D3D|j{pMzZ4LyvY9Yo&00000NkvXXu0mjfjf`b8 diff --git a/src/Umbraco.Web.UI/umbraco/images/gradientLine.gif b/src/Umbraco.Web.UI/umbraco/images/gradientLine.gif deleted file mode 100644 index 2b359cca13e0d215360682a3651579658e2110cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1482 zcmV;*1vUCdNk%w1VITtm0QUd@;Nala*4EnE+Su6G)6>(?(9qA%&fVPH%*@PCP*BRs z%E-vbj*gG6uCB$z#iF93!otJA!N6KtTClLNyu7@$wX}?kjB;{vq@<*Xh=@{BQhIuN zbaZsOy1KZyxVE>qR8&;5va)4mWnNxhf`Wp+zP_lasC;~UtE#JHWMrF~nx>|vmX?-q zaB!fYpq!kXczAeLR#tv~eujpIZf+9+1>Ehzz|NsB|{QU0j?%v+s_V)Jp`1t+({q^gDC-=;-M0?(XmJ@4>;rZEkJ)`uhC* z`}g+tl#`SG{r>0Y=jrL_gwy~=H~6}?dCc)qM_^S?5U}#!^6YN%gj|(Rj8+@R#jC~R8)zHiC0)y z;o;$P*0qkoBI0umzbE0jEh@aTb-Sqn3$Kx$H@Bm z`S|(x`T6?T*wFMf`k&)}{>~C*x>g(#{<>bi6$mHea&CSi`=I74M&g|^$+uPgo z^78HN?Be3%A^8LV00000EC2ui03ZVa000R80DmwN$f%$}Mm-pn>9Mef!%7f0Rf?9N zqQ#2kGHNWRp`*u+rb31UQ4560ld?d_q9sdKj2OjY%8YrdST7wqc1SPgGAvep<@BiOKDDN=|SOSbH~vuKyC1M;n{+eVGV z8JP{QLgTR4Ph=yOk^X9b}+~R`a!-x@cSWK+3V1bz`qk9fugn+UC7(bpGHEcW5r$4qsg^F6I zVxfg7pn%AR8~$>{hlw;G;)t1K zGQo)_ilJf%ERy0PCYV$Z%8WC9^2rCHyiYmHLV5E_$oRUfjOfuO_ zGZ94a#*}aLVI>3*z@nv=4mbeKmtYPtL^j#jv%r~XhM}ecY_b_kES(@ACj$83qYpj? z-Y|+E0W|6;I^Cc%z@(G@VW}npWSS{I{Gfu$0SD+I zs;FCBvc(ouq?#%@0JMtBt5?LQD(gRZ;K7F!xZ+yRJ!`Bn#jn3cl8+sBc)|u7cidsc zvdhAwh8mF2Q^mB?PU4C@`}iY?6Wn&I#kXy=;f5Q=Kw<8=?Yxsl8k{WQ?z>C$bIuuO zpn=8{_~tu~Bj$YZ1s4F%F-H>w7u>`cT_D`WHfER+#lsJ469qT!#1V%gWK2T_6C8KU kgdB2YA@UPx# diff --git a/src/Umbraco.Web.UI/umbraco/images/help.gif b/src/Umbraco.Web.UI/umbraco/images/help.gif deleted file mode 100644 index de8c2f2492237e2ca8562473103d54467ef75985..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1013 zcmd^;?N1T`0LCxNvT|;ftM#%rE1Ra3S(eooiQa5w>TEg121TXS)*H3;s+pmsl@du;T@dFo1oIA0#+tjUix?R`}BN$_WYg$$@>#`3c`Re;Ma3t z8kS{=9?PhI5>yc;+D@9RI6+EYE0hK=-s@L)n>y5l*~%2q?8Z3p+ zZ_)az-gh?0$&YffOK)+wm@w{_6-Sy@A8m?EZ;a!s6Z7=jPIhb) zfIEcBoo7_si@M|svfQdb+O@U|HNVnDE!S(Oa`KV9V)#nA{?1G5H3{}Y@mJdTO;Sg_ z`h>|KJE$9C{49dfh}KB=_EpH)&pOt|uxixu1!F%qL5we;(dsP>+p5N!6=18{03x_| zoJ9$<)x31cs$Gr3XirO9hl#ed!)Pa>Q-Dx&JEYW1XKgD4qeehZnAi7;`+7iHYja#H4{b4kIq#@a{*)jkUph zmu@_e`j+9ip`cy$T@ATg_9#o|&f_JO`)Vs96V_#x_}&&AlZGU(p5TWg3#E&rQ$)C6 zucl~0l3TXr!Rh-8mTb?B<-`|tZ`kpa2ranhQl!SbM# j;FHz4hNz(I>v5^UA&a>8s>gjb?z$&$4u{ST0(SocZ?Dih diff --git a/src/Umbraco.Web.UI/umbraco/images/help.png b/src/Umbraco.Web.UI/umbraco/images/help.png deleted file mode 100644 index eaa43eb050e296bf07868fdf86fb382f4ccbeb86..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1018 zcmV!g%Zc0dIBzV&au?H_{Q&=#*$GZf=FK zA9k>{#kQ2v(=rZ}(w_F5<5%}4Z}MGy&y(kUp7&)yOHmYzjEo>2k7HtDf=#DWk+HF{ z!OlRShhZ3$O56EZ?CPCvC6tWCV&?GhFp|k6R87#J7;Ate04`|q6(hx^7% zuwla_Ff4#&O-MY_sfTOx(=)S|HBDR3<#Lp`^n}3Y^I>Xg>V?V4i7$Du z4=vOhCYnYI60y*GM1WVSC#I)oE`E3A%AeJ06)UHaE`qJbn5ku8*M^BHwI7GU-x_a~$$L<~&NF*QQ@B1qD8vxJC z=nZ-C@moIFNCUszmnhf{mI$Cvmr8a9EWOCf-_4nPdetW+vFc5d$K-GG}@ynFn0GUbi-~*^Q}p-+bQVawzF-3!LDhN}SlLIeW|9hrAw7sM&1Nvn}VBuHU#5 o=nN*;)*hOXNdNzscKs*70Elseumf30*Z=?k07*qoM6N<$f-e^90ssI2 diff --git a/src/Umbraco.Web.UI/umbraco/images/htmldoc.small.png b/src/Umbraco.Web.UI/umbraco/images/htmldoc.small.png deleted file mode 100644 index 61074a4d94142e72c0a7d6a6e2adc3e4d6b7c693..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 507 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLlOAb|77RS*>U>Wf`bo2LR!7sx0@_| z>XzB5=A69a+N(29|H~!JYF~LLa@wAV$j+Lo(*I!4&-SboXqQ4skY6xF1SiQ56C?c= zsP?O;i(^Q|t)vOlr%!KUVpCIKS7?xMU@8(gaDYjKkwakx!)6QR#TLx^=96t47#dlZ zI6Cyroj-rRqnJWN15?wbnwmFnE)~hFIkSgn69eNBh9}+~8#Z=!aGW+Y{`~1Q6N^Ce zo-H=rOBTpTi<>iRdoTb^Z>T9V)6+A{(@SIwTfpGJV94F#z>%;NUkm?^lqPp1r~XtiNUEj0U9`8JVP>Idk?nfowf+P(fcuQ%y~c z>q#>U15=0Nyos%?!2tmclZBNS1RNBWpH^UuY+zvGJaJlyv4Me^!S}nc^H1}-4Zr|k N@O1TaS?83{1OVk1(NO>Z diff --git a/src/Umbraco.Web.UI/umbraco/images/importDocumenttype.png b/src/Umbraco.Web.UI/umbraco/images/importDocumenttype.png deleted file mode 100644 index bd898de94b1659470a34a212f2ec3d63f153eff0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 601 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl4}QS@!?`?Su&*dX}$oD7diT!2iG7T)S7DXjt(-q2s~-|E~(B?hdGU z@aO-3-|7{AE;I&oeE5BPmO;XXgb5EEDlYsvT~n~&gHz$WfQ|=$&Nlwr;`#soorVP; z7Vm%e=XCksO|B)=Hf=lg;`g0-|Np=F^K_F_;r##q-~Ru9ub+<Xne6HXzp>*o9U1wkaIal)^X!=XV5}>dBN`m}?fn4eVhBdyw&jC%I z?djqeQgJKh{86Dp1`Mtj6B24lOj@b;aPAaDKytj5+xBd#Z)7CH9G}ipKHCbM*srsmk`}snh)4fyPy=&!ORLFM0&t8FF;oRT9 eUl`siA7ID`({-%wy1N(XOa@O^KbLh*2~7Zk(OxP5 diff --git a/src/Umbraco.Web.UI/umbraco/images/information.png b/src/Umbraco.Web.UI/umbraco/images/information.png deleted file mode 100644 index 121c7336dc5d7e72080980b934618d6608ee979f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 725 zcmV;`0xJE9P)TmiBc>G#Z4hnJ5)iUE?jh#Z53Sk9|#IQR=%*M zbegV(TAGxG&Y~Nc#xT{yG>_33Gsz_V&D`(3_v=DPe6$-q%P(I(9609~RpotD^X4IQ zbu=x(9ak4lR;{!+4Je@q;ih-t&fwWk9=`f-UUl%rm643KaU?Fl0 z6`pW^dU*z;{kij>W#0lEzMRk4NNyb3Ru|vhTF=y+rMM8`gOaFQLn^V3!m~01KR;Zi zhEuruAu_j${MFh3DAFBGo|e$4Pp-Q-~96)smkq(Y`T3*VcxVM$!im1F+to z*w^07SaAsvL4wBz$B)AkWQQlPVzJJ$6h(~BE^=UR8^+obD=FB>wl>w*P?#yiSjLzzwF*!rY@%*mE&WIKAR;&sI$9D84gC%1t<@%0qT3@6%3r6Gjj5ilP5hRhlxHuOz%o259~|RynZd9JYA?T`1>dm`NE7bvHsk^ zne2aV0ifrrD`{1K91SN4l@>H$j41*)4RYs?fph;qrgzZ`s6T>xS+*g$00000NkvXX Hu0mjfwNh0) diff --git a/src/Umbraco.Web.UI/umbraco/images/listItemOrange.gif b/src/Umbraco.Web.UI/umbraco/images/listItemOrange.gif deleted file mode 100644 index ab6bef08c6ebc3b5ad1635891269b7179cf918fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 114 zcmZ?wbhEHbD diff --git a/src/Umbraco.Web.UI/umbraco/images/loginBg.png b/src/Umbraco.Web.UI/umbraco/images/loginBg.png deleted file mode 100644 index 18d3f1387fa20952d01aa7f8ffa7a016454a8836..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17566 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 KBbFOFM*Uyq{^n8u diff --git a/src/Umbraco.Web.UI/umbraco/images/logout.png b/src/Umbraco.Web.UI/umbraco/images/logout.png deleted file mode 100644 index 97d15fa6b66ef5864b7400ff6a6a97794e5e289b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1011 zcmV8 z(vi>l`i@%~8y#R6Boc|$;TJFF4mCAJMn*=I_V#uxEG!_EN`bEyU?^#KcNZNU9Zg-` z-PgXna-~lMT7+_$MX7|c!=ZfY_eb7Gqrrb-u?Ljzs~?legg_v0;{1gR-`CaD z__etl#B3Jq_BP5Ca-jfO5U6Aew$0{09}W(F{qwVDZ}a&)xN3p6wl?R$jT;Y*9OpNZ zEFA^2xe0}eFqsT^x(lnTsD#51H5$9;>{;*B)YSO;`g)o(8jb9CckX;69XuE?CKAx| zJoLwop+vy_l@)-BaMUBNs|##Aj`H|81c$>eb#@lV$HsnRJs!{DCa3dr4Y6>Th4oB zvYMVwAKAu6q^R3GfgJR&nVkh&Z5lbo6Jed%?BE%eWgyGgqPOk5dCZhIsBA(&V==KaY>@-9yXWyRa-TquAMr z;>C+uq>va^9Np+0L9hD%j(_xL~V~&^?e7@1lTKjm~NFg3)N|cl{7?h;d`e7-R3jP`lJ}Ap_ zG)_~wN%Np?qe24pdOfB?p%wGc(6@u}_zyk3y~j-r4Gy_fVp7Ru>}@bOCs{0uA0iQ@ hwY62nRL{Qx3;P7$n diff --git a/src/Umbraco.Web.UI/umbraco/images/logout_small.gif b/src/Umbraco.Web.UI/umbraco/images/logout_small.gif deleted file mode 100644 index 67182329f2aa090b27d38fff6b64d446647fca8a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 996 zcmZ?wbhEHbL-H?mbQOc$)2VH%R|gkksQihi9p7AJ@$J_2mAqyVu{% zX!>^g;ID_be?7Q$KS1M|uhh%x#5a>`-b}84oZz&_ko(Kw?LV%cf12X-e#yjF?fFlV z9AD)6e7|t~d4ABNFq7wb0ec}&Gft%YP>;%{dqy~&$D}8HfFqP%-U=t_i4lY z-A24m{1jdU$~^Q}+-D>HIM(*()ziCdq;7kuJP6Z$7^3ww!R~E+)SW=RtIqO!eDt43 z={Is7kmOsP{P9 z^4EjgH=Si)6o$R&EqmlGeK$z^d79(1+~7MA7LTo2o@IHxYR=kZCH;0<)8k0<+eQo@ z7Iwd^P5FNQ$jfZ!2VTlAl1$!|`hHk4;ju0Ii}IM~MWK7m1fQn3zMIvt%T@DLQ|60I zhdrL!-%cNRS)cav+}#1<79T_1rPOiL#)u*RNLP0CCb1`kHP%6~jFF0r5N zb?AHbpwlJcte{nsh-^d1!bvlQy!J?VIl1^ul;@XWNCk7RCwAH zk;`ioK@i5Pt7m&=vYW+MVn~!AisZ5wLB!xeC4zX+KoInx2>t_l3rKDvo{S#VlOU)l zl7kRL5D|okips)@9z5v92g&YcUfn(2(`E0asfMqrzWRQQh`RT#QLv=t#0Dv4Af^)vd|5+(t9N85(Q&xUh+IMLZ@0$@* z`!R}1E7g9Nt~~Sw;{gxjz-1{|dR{5#2FC6sZ4);PjbVL89sZ(e0mMZ8Z!z>NpLoQwOn=o zZM}R$>}h4ocWOKLZeF|(2=d7i1MvB?pYtsSjD<0VVg2}gG(CgS5b4~s-xg1opuetl z^}PsLXLd3G#~V$L!QbSg;@-H<2J1WxV|1=NUg!BfKfHr^{~{bP8}K#USw@zOW!6Z8 er`rAh0t^6_ZU$>R@+KYt0000{jOkN^7l@c&GZh~iHcMg|6U1|5(JkXi<2 z3yocOtPH$erWgmmQTGg8WR@(l+@wwYCU-J%bIhAarZaPJHFj-Z8*mwuMNsU O4jESS{d-Lq8LR;h)iKuq diff --git a/src/Umbraco.Web.UI/umbraco/images/mediaThumbnails/pdf.png b/src/Umbraco.Web.UI/umbraco/images/mediaThumbnails/pdf.png deleted file mode 100644 index 3ed7608e3275f8db7a67075038455987628c32d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6074 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 B5s?4@ diff --git a/src/Umbraco.Web.UI/umbraco/images/nada.gif b/src/Umbraco.Web.UI/umbraco/images/nada.gif deleted file mode 100644 index a5a99828b8b55b8dda3cafb073b56d9fb25ce9c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45 vcmZ?wbhEHbWMyDwX!y@?;J^U}1_s5SEQ~;kK?g*DWEhxOTKZQ;FjxZs>ahtN diff --git a/src/Umbraco.Web.UI/umbraco/images/new.gif b/src/Umbraco.Web.UI/umbraco/images/new.gif deleted file mode 100644 index ef0178cfa48c056e4659c2f586823b18fb2d67ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 246 zcmVR=uD}2o(&5bkx2hNb zSVFP=fsJhyN&Fy3rVgMK&~l$Dl}A{?5VoSHYEperE&JExp_QUCw| diff --git a/src/Umbraco.Web.UI/umbraco/images/new.png b/src/Umbraco.Web.UI/umbraco/images/new.png deleted file mode 100644 index 0ee2a7d79efc3389e202d771b7327ec9728fee77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1035 zcmV+m1oZofP)u{|D~j-MrsUi$x{_3Efwf= zyI>TUws$Zzc6mcZDyq~ex-52> zO-3B>DXiDGuu**pugg7n`Fz;DyuAEpT@@wCGUfC5u8fDnpJs$2I8sHvQ3PZK6R%ys ztY;c_)`F(e#DOM>uaCY3-z#0a`|;g7CcTNKydF>Q?A+X)vT&@+;~2R*hwMonT$4jv zY(wB%NHt#}-b|n(SFnkf`0mw13|t(%?Q}Z(sGHYsyiOPr=ENF!r3F1<(UcoV)iNlS zO5pMwxLOuXu?nr+1|bqwTdUaP_k+_@(<4N+#;p+83+*y`%w3prjUiXgqjX$?P;H`m zQpOzK#yjRon000>X`kZ0^bL~YA?fYubrV7pT2w^puk1QH`A$qaFTzPXK&u9Gg#u0+ z4RkY|Ft7$#bvDGQE$Es+yTL<}jf8MHE^TsJ>(R`HeLs1CSUCoY5z))qu%MZRRej*H zehDe+0JOp)A;n>*Ot7>%luD(NrG9wyXq%SFme<;cdFQckMADtZ^s{g7`zI*sZT}g6pVp|iPghqu)%I{ zpo19UpiMBc6fBYv6T%eg$&(+`*>qgnYVp$E-d^g)yB~!7{lUO$x_=U(a{k^QN%w23;>FQyHGcgO|1X`002ovPDHLk FV1gtO`K-riQ}i58amQabLt!W>+0M0A2@vJ>Wv2vA3S^h z^xgY6PoF)0_x|;VkFWm!>-zt{AE@i&#}}VIz54X&^~aB|K74rj?%nf`A78(J|MLC& zSI?e3eERg^qeu51KYsB4PtpJ1<^TV7|M|J@|F?|)KPLSDnEU{C{El_nG7W z*S^2scD{PF;{RQ#{|`0(KM(l&wCDZXhyQPI|G&cX|CZAKTblpx8vMU!`u~yppNGl+ zpXL62mizx4%iH~|-%oP?zr^$ZGT;A8f?v)^|G#Da{yVC~cdpu%SM!k?aB6%1trJVG9AESB>Ymjrm(FU5KCz*#sWkS`#>qF1ExmSh>Gcz9=1y+d zyLI`q`8+Vtm-eQ{1e`s%F*d~W;GXTv7R&?22quL7?KXzSTB-Tln|)UWo|j? zOX36rKWVd)J3l@KJ!Y4%u6Q@+XA{f08A_^dM=o4^%+@VzQ{b^u`8X51kypqP1|^m@ z4gu#)98M1$7c@J?&S2S?sMI8A$R<+YV)25jS5&|$L~(-S!xl4_7@17RL&xXva{aWK zY1k^DprgNnBOoKKa~cz;70azlF8-}8nTKRD6h0iBXId|(ykvs|^C2#IgAy580S0RT DzvE=g diff --git a/src/Umbraco.Web.UI/umbraco/images/notepad.png b/src/Umbraco.Web.UI/umbraco/images/notepad.png deleted file mode 100644 index f8b8ccacc677ff71b71df8ebc40ca398fd04544b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 489 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLlD;r~=Wo|ux-s?M+X=Tng(uatoqOmt^Wp#h|K(RMivrrEP!i-94CLYi z3^6g%Z-Htbd%8G=RNP9M@cj8BR_8jsN&yBA1-44Qz(Ro(vFEJZ3@icx4ULVB{$5^D zJ%Iu$3<3@gjGA_uhK7b}iGFnx7#tW3PZ)Q5c6%BDg}AyKSQwZ%R1O?b(a||`;=~Eg zOUxh*b_V?u1N%6auq!k)FgYbKhJ{)BO^|C~VB*?P$W*yzjgF3uje{)%Ba6_4)(?>` z6%AD&QxsSpZEOhHaQZYei-1Q%Z~N4#!9kPeil=BeFf_8ZuCKxr0_#rzl{VXS=xXq@GcX=e=x1YK;$aAxAr$sA;m;zVcNsig{an^LB{Ts52dm1} diff --git a/src/Umbraco.Web.UI/umbraco/images/notify.gif b/src/Umbraco.Web.UI/umbraco/images/notify.gif deleted file mode 100644 index 55671b5b4765e03aff2893c6ad2e3461d4523b22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1003 zcmZ?wbhEHb6krfw_&$Z9E#K|z%J_3D-_(?`v1?#myS;V_j%IY12O-fH2?oT`~U9^zdmm~vY~6s z=I>LdeEI(4_vKU5E}fj^A9<{!z;k9t+U{LH9&HUjwr}dGQy*d@SNHY4IlVIS)w#_7 z|Nq^;vEu8OKZ_SXc>d<=_oqwm?1=pNvM)CN$kQ{apB}Xr#5ix?_CFzJ{jI~L|Noyg zwDtV|XKq=%`O*!K+w$C2t^7Q7%GcJG$N&HD{d~Xm>XolsxBTdAe}3)SkNNZ7|Npb# z)RD;-HzhuQ^YOx3-?O_r|G#dzxgmJ_!fLnB#EYv!UtB1hKKt&AQyCX8eED|2Xi|;a z{`D=bEiZom{&!(lMpez@heuLx?n*zq#N|I2FpL7qLO}5+3nK%=6b2oTF`zubz;S{> zlT*fH!-9j&tjk{cz-W6JZz@?)ZdBU3@bf*Z{T4o+4Kt@?9Q(Ia7=v5%Z3!>t_xSgLUIgw9O-~{85xu*74dKNgTx5*p1@o}VxBpp%^G~rTRrQqp3 z(IAkeYNulJK_wZNmH-YWmH-6~wy6c4k30Gs7$P~g8oqGs;&&Cg$8h7r0iHwG85tR@ E0h|Vd&;S4c diff --git a/src/Umbraco.Web.UI/umbraco/images/notifyOld.gif b/src/Umbraco.Web.UI/umbraco/images/notifyOld.gif deleted file mode 100644 index 55671b5b4765e03aff2893c6ad2e3461d4523b22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1003 zcmZ?wbhEHb6krfw_&$Z9E#K|z%J_3D-_(?`v1?#myS;V_j%IY12O-fH2?oT`~U9^zdmm~vY~6s z=I>LdeEI(4_vKU5E}fj^A9<{!z;k9t+U{LH9&HUjwr}dGQy*d@SNHY4IlVIS)w#_7 z|Nq^;vEu8OKZ_SXc>d<=_oqwm?1=pNvM)CN$kQ{apB}Xr#5ix?_CFzJ{jI~L|Noyg zwDtV|XKq=%`O*!K+w$C2t^7Q7%GcJG$N&HD{d~Xm>XolsxBTdAe}3)SkNNZ7|Npb# z)RD;-HzhuQ^YOx3-?O_r|G#dzxgmJ_!fLnB#EYv!UtB1hKKt&AQyCX8eED|2Xi|;a z{`D=bEiZom{&!(lMpez@heuLx?n*zq#N|I2FpL7qLO}5+3nK%=6b2oTF`zubz;S{> zlT*fH!-9j&tjk{cz-W6JZz@?)ZdBU3@bf*Z{T4o+4Kt@?9Q(Ia7=v5%Z3!>t_xSgLUIgw9O-~{85xu*74dKNgTx5*p1@o}VxBpp%^G~rTRrQqp3 z(IAkeYNulJK_wZNmH-YWmH-6~wy6c4k30Gs7$P~g8oqGs;&&Cg$8h7r0iHwG85tR@ E0h|Vd&;S4c diff --git a/src/Umbraco.Web.UI/umbraco/images/okLayerBackground.gif b/src/Umbraco.Web.UI/umbraco/images/okLayerBackground.gif deleted file mode 100644 index e6800fa08bad51a4a40a14dc6610a15626e25cb5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1469 zcmc)Hc~g=J0D$qA7ZfxlG)v3Ptjt^$k93lq+8J`)w9#$cM%QL%XR@-y+ssz?nCa&Lo^C64gopj+UZm5^96o?JfY{#;=Z}O3XcOm13<}Q z+X^^uo<nnm*pG7TlUsjI!keO)GaWerC@DC?h^o?*Xz_kQ-n9A|!Ek;~)P7g$(Yp=<-3 z9ao(LY*AL077qmqrD|KP(du^e2BYbY&@#UgGdQvfMA^Oev%4Hz2$NSDYr@)}yeW1H zNaV3@)I%UVFib0lf^D6#w9d94^M>+v+Xvzu*zHYIHMpDWys-{SAON$;C*t!UYt#VR zDmx<&u(djDgCMSUJp1tcVn-r>ysH|5Te*{6)ANaRkO0RYhOPwX!UN5H<8|RZG=5)p z5_k}D9fI3kw>#(;%Lpw8JY7QYezA!HCh=(aqllL$JOQ*rAPb*U=n4pJY+U_A!pgvK zk4gfW0gIk!yE^gchrW}cFw1>WZR|lJeb71<0uFy)1)=wOdi!BG-AXb0X?Xo#Uceh3@iVrNCfO#H@`i{d@$r;giA9;Sm;I~to$utg&sm2vLzy5_y+tP zOE-^U8_3bd<4DnI_3Fn`eAAVK;Cuq|0(c5T%r)CfaqeMgaigQCEb6C(#?s!k(VBeg zz%$U^14G}TGRFP0K2;H#THG@$6Gr#%Ikcb5uwu>QPzZ-OMs-q>dQDspjd#Udx($nF z#3CJp4X#H#NP?uCvz}`WWfxS@7k9rPM*$OIBOvTpuSHB_11WL6g&WojA;K_iAex+W zlC|3lgULfoV2jpi{pxiuZ){PBy~T-G$N}NR652sIC>a2d-s!~GIM<(# zmN;R~orVW(%7?9T@1iKR=fSVdBTn(HTcclPZEi7L3%nI9w~A!Nm7shIF% zdMhUbImyZ?oVZmveOj}roWUcpDt3rtit26HajNQFM36-FJ~9-$JsTC1vi;#=Jav2S zmn_LPC$<2qo{y_YQ7;fERP|zVk3`K)XJR$HOiqf1pDm_p1b=EInk5M7qZQ^mrfQc9 zkJGd(B|%c{M`EatPE;0?s{2$KPt$#_&64WG1#HqIB901R!ldWZX-e9)PEM`(>(wH diff --git a/src/Umbraco.Web.UI/umbraco/images/openfoldericon.png b/src/Umbraco.Web.UI/umbraco/images/openfoldericon.png deleted file mode 100644 index 15fcd567111e13cfd9e6f1c0cd8103ca0e2d76ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 232 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFKheKdVb6i#4OEw{M26cw_Gj{yv63W{1!+$*s*N3f2 zemsk&HQ&u~&f!%4#A(caCa+!4-!jYT%bH^M9p4*PZTkKrpyj95hk2~iCLLVQKL3cY Ud_U9dR-h#ep00i_>zopr03|O{b^rhX diff --git a/src/Umbraco.Web.UI/umbraco/images/options.small.png b/src/Umbraco.Web.UI/umbraco/images/options.small.png deleted file mode 100644 index 01665deda78b4df9e17fcd2ea6fb6bc0609cbe5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 396 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLls=*-O;K0bI=Vs=nwty*Y0#^eA6DLFJ%>y+Mra{w_nGH1)7#tWFSt}gP zj0_omDlmf7G{i7WV&`UH5r}ALWYpmo13Ga6gU|zz>1=f-3<3!ZI*pu@yCpMJfj(mJ MboFyt=akR{06sR1-v9sr diff --git a/src/Umbraco.Web.UI/umbraco/images/package.png b/src/Umbraco.Web.UI/umbraco/images/package.png deleted file mode 100644 index f3296d552b72bf0f03ec79e36b4ef84feefe0d18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1012 zcmVeDu)D+k`hH!fE7_Beoj3=kg5vGp@r=_I`sYHdQM6^jurutrP4DTGr3?9d!3;)-gvk#hqw2RrZi5g3 znzo4f`4`?le*E$4ZOx!}a(8qna)F3CB9SP@#@=Rjl*F0C!)7i}u%VbA8Qt;cMKS=h z*F`4d+$$C*e}d;CmC9kip~JEqod_;?h65Xt@YkPL-;|};kAZ^iU2~q;xtz%Jv|FEo zydx}%$zu9TDbq1LwhJl7QWX==bUKk) zF&abP=?$#!wm&8A=%g(2kmC$hH5^@AyMNAc?1whqT&YcQY%!9F4Zsg1NAlr%UH?qB ztQEBtE5S}YnT<<$(Ac4C)#>Q_p+X_Agu@~jKmW`Bl+&y0+C|Th=144zv^0v4A8&_R zMl}-RTMg9fTlBoVFfuaqaj`hDO~YjeBk+V%lVbB+-@9|sa5cy=Dw9|U2|a2Xu(fv= znsJ1V-h^#?y<{>~VHn2tX#)bs^(YCyJv%e?p5V3@_v-6dzH=FEqe-2#uvxyM)@qlo zw6z;Qr&7{_fDK`V_yvpCOM_2H~;pm^mP1n6P(azj9CAnNxD zap&-smGy2r>o45UgWh3qTz^?|;~R?kl?gLq#(5gf5(Hg9h7&93cW<&u=XRJ1`9ygBX6=)mw>!1Vm5{jLy!cn6c_aKnG8sI z4#FY|AOIwZNrIT9JZMWjJBL0()=K9+uOh-o88i?Oi6nw-5$cS=nGaC_?T~SaJ`gm) zX0C%5FF_BsVC1O1jI%Hdr-fTy-28X*_p8FP*Ynk7Sfx2?9fvvtprK+(1Ki{=&?3sz zfJPmA4{B(g#BgOS*!?d)BgRtnI-TpILFe7s3-fPPRx0^oh7+p<4CesDr)|`?n|Qo^ zjH98ejdHbKynJP|RG#^i{rveSFez}XIhvgw;wYNRB%E7r)O|1S+t>Qt1{y~yR#QuV z<*I8x<>zL96bAo=yd1fXL(BC4R%tSi78!QNYfrzp@l`7AecjmJdR2O>E7hCJ_f6Z} o(s3jW8ucIuAjvmPdH)G802_XA+b!7j+yDRo07*qoM6N<$g0)_?82|tP diff --git a/src/Umbraco.Web.UI/umbraco/images/paste.small.png b/src/Umbraco.Web.UI/umbraco/images/paste.small.png deleted file mode 100644 index 3169d26d0fc21fc20e3bc5f8463879b83b4919bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 632 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl3sR=_y4~~CLa60w~_JPy~bIm|L0A<`0LlN|JM`#p9(8)ocjND z;s5`g|38)g|KE7@)aCyVGyi|9`VRuj(rY%h9{4}?9OnzyXI zF|u&-xvZvLm#-g*O_}`vOV9Ur`~QD#=%4ie|Bv-4g-d2l%DZ%Y!@1>V@4j#P{%&!7 zUEbPesiPAF|Np-7|J&S>nO75X=1p3B?Ay0*ap^OFHvQVS7U*#Xhms(_V6fHX6AXu{ zH@X1L`sC^27*cU7X#+bWUt(gSsKIGDg$9W?Z+k^TrmCo@_?$g6vyq8`@kqk?Wor&H zva#K=abRp_}J9QDel3b(9pmXV`OwJ?2p0$4iN?pg#``sR;*YtYfD3euKMEM zy|xYvjI26Gj~=}#x^$_irKF_fTSg`hjRmTzsz=W;F!}82`NqJ=B62}cP%tq4!Q;o1 zpQj~lVFwvAW66>wI?OJdi-ATovaqnSrb<59xpQY^1JFGzLIOfUm)ch(q=MA4RXfYE zva)9HU;tY7_t2Tchm+Yu&MT?2gWSW`A#WRq1}l<=psl5*5Xz9i;M}s*NP=ugs7Q#8Z;Dyx|}!`#}xw_C3!B-yaPC&0j)XcpuX@rNfq|q}N(wJOjA& z>u+z?dfJEuLePrqzy!)73pvLjxk4d6XNZt?hm_iYES{i}J5y3l?}PPNYDBR7oPc~6 zL^d)Bi4Q2L3pnp!nFxN9c2E+=@XAl&+;2m6a~kZj1r3Mz3C=hmUG<{+vWR@t4q?fJ zhFc(ozZD#Mx`^Q~g1v=K6!QnfuqyD4>U4EjF0eamL}Jx| z%&`kR-H+3GBYr*Qx}frLU4`%n9(`uSomzw)t%%NagXkA*R5Mbv9VLDp1wMo$cOMa~ s3Wm%r7^bwK$2$}-<~D8p`#1iScU4^XCLAA~0ssI207*qoM6N<$g3sK(Qvd(} diff --git a/src/Umbraco.Web.UI/umbraco/images/permission.gif b/src/Umbraco.Web.UI/umbraco/images/permission.gif deleted file mode 100644 index 26e0aae4d193b349d2b036432b98daa68a44b854..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1009 zcmZ?wbhEHb6krfw_-@JY^T*G3?>^nX|6uRFBj+z(yL{!^snciw{QdXs`;R3{m;V2^ z_}{N-|9|$rdN%*#yEFg4G=6^B{QrIBlk27bU+4dOU3TG6$%m&ajvt);?@9Xi`j&Fn-}Jtou|5{!|=iW+y_^8uBqbPvU>W>i~FZe?E3Tj*OkkcKD>X=z`*eD z-@m_q|9<}b`NfMD>({UU|NlQwa1@M&0NEj+_>+Z^fx(hN2V?>$PcU$tV<_f0Q@G$j zuQV$YPmD$YJ0qiQ1%t+g=7$a}Y&<*?0S(7_WyFfV7<_1KYvwm+QMr&9c%YqMOlQK6 zfCX*5ZB}VNJ|rG&WR_>sTd?Eig{CekT?LJd2?+;UIaI`|J`^}Px3CLkU0Gq!*c!sh zt&wnJ!$X&L_SmI*0SC$$nb`!7RLNR2F|vrNU(*thPFm+6$nUaa!oq{PZ2}5^o_QE5 z9!@yG&o@0|qd*&{K)+_djeti-+gq)Z<`g_+Vu=*cuaVgx(ZJR%s_nPs!@-~g2L@{Z DsHx&G diff --git a/src/Umbraco.Web.UI/umbraco/images/permission.png b/src/Umbraco.Web.UI/umbraco/images/permission.png deleted file mode 100644 index e8b99447e68a3165a8e6c0f635cd360e9df9d893..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 581 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl*Jb~|H2!-S z|L;lqm2Dws=c(@Awe8=pX+JLe{{PuKbz;|-TLrJq#;mF0{r|r5|LgpZ@6PfMza1Lia!@{Akfq}ifql2eihQEVXz#)M#Fi=oYF)%WsXH&zu<= z5p?E;ghE3Dlhv&l2SznDF;C6~(hN)-Zw$=J%Jjs<^aSn$r4{M|6Q)c`5@t4LWa44a WDOJdM&@Tf@9t@tYelF{r5}E+PVGj%d diff --git a/src/Umbraco.Web.UI/umbraco/images/protect.gif b/src/Umbraco.Web.UI/umbraco/images/protect.gif deleted file mode 100644 index 51323c0869b968fdb5d0f482a80a80cb5295e67b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1023 zcmZ?wbhEHb6krfw_|CvkVDGrFvU~;9v!r3u zg0&l$Zd^EZ+5bPk{{Q{+|Hs$=?_PYfW%y#l@ZOr?xh2CRbA|`z40p{K?wB%MGiA7J z!f?@;;k+@!Ib(*?Mhu4ynGP8+{dZ=)V#)T!ne(m<=YJ2LUv50_oOvEPaR2w@zi7+* z-&^3nkKheQ{=Js`{{zIndyAd06aF70{lG)ws*CvlD3$-ws{dm(enhH2jnw|1YIxK~ z?R$#pnIN72*;fB^?Ovr?|1WU-S>*P=!uNl5;GHbD|Fywy$^!m3g#T}fd{Ys8uh9QZ zUG%l0p#Po8|GQHDcc=dE&;CEP=>PQ6D>aG#XI6YYwDJGxUH{MR`*~{jzY7O{Up)N( z#+m6J*ES>W<2FLe%*2&bh_y5Ji|F56^|N3cpgzK@ItWyoSE%F?Tf*fuvoppP~+{=q+ z{{Qyn-qy8ux2(Cic6q6>R=JtZ)R>SH(Kti4#KI}85prO|!^7=1 ztSm|e%8#2`xg||X3UrnUHaGTN+VkMT!RE*ONj$0=8V=_rjTD@EP9FAPJi^K-!s*5G Oz=65r05=;8gEau!MM^^e diff --git a/src/Umbraco.Web.UI/umbraco/images/protect.png b/src/Umbraco.Web.UI/umbraco/images/protect.png deleted file mode 100644 index a71c88b709a659dd2733a93ca914051d5b2c2347..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 628 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl$6WS6iq}Ic z=BsVFcek(qKRJJEtlPDo(tl|tZ)>9(+@1fwxPL&E;Z3p6|2VC?5!!zn!Y@@PHmV5U z+O(>ntmMS>-dC;ypG_Ek7&H8BhZ(=H2sjaSW-rl~j<{!kjL^z@fmF?#5=2ko-7-jgx^zAf}CButbIzS(U}O=0#_!8m6a5*aVTtH5aymd z%TRjuHcvO?*()`$epk?VdAxv>Y4eoFK2Ad>|V4juK&TD zCA+39*^;y<$Zbx_wE5f4yg%EtY}(ncZ`uwnjGuP-%(VwWn|*g5za6us)V{GlapH38 z-mP(yCU`A(OqtdiJ?Tl~g2fIU4SkFD$gFhfyHn`UFk$DhDPy{O-U%BV=B(b8d&<#i;Y6=(@gYks+^be@J$xhPxUJ#>n+Z1x=B_`M z(D5MQnB|l?Tc$1BR&pTBE2ygRZmWw+vdZMfsMhTH&o}5!on=>-?9;HlV8VmK1zS2# z&d!_C;Z(6ced~&)Pc{_HeDnYRe+JqB#h)yU3=ByOIw0dgal*jf-jLMP+|t_C-jQVH zFR$L2#KF$W&BMmT+0MYs!pq3zfsa{ts)g}9NZ!kWvi>ECoUvx! z&~{N(RZOLlp|~UBFH2EKk{baj0QoH-`6N=F<7HaL=VVais zs1I0K97pGuGccUf;Oa|A^}k0fzVFD?>9Yf5Tl$xS?uH3BBS85?W218&`NBNX&seaV z94g&aIE)rjeQPN3I^@VZSeCQUb_Ccj)6_uG9Evi7Opb#}8!(%Lu$nF?#IfR9gj4&D zj$RQBUDw(KJRV`8LWR-ixdS#ihFVTUR*GS%;}vL)hS92GEH($XCSd>3!JQlk9`6f@ zPxx0ygE-l|iB_hLPQi&KGln6i+UBKqPrm74<`7S}3B`9Cp zLODWEju4a|5R_H&AEp<1=XD?p8{rLIIuV247JGoX~`O$yu zkGpqm*O^5!Q#B*g<{f$X|KW%Kd6O?j#r7~ZGX4hxi?ge}fOaXA1o;I+L~xP}F)`9_ zfoeZ^x;TbZ+)A48d~zEjXQcuIhXPxrou#FtBO_y7T^Xm_0tN>LM%IdoCk+iVXU00s z3XhXhXlP(+vPsw{vb%*PK-hu7@PskfsgoN|oa#F1wP_8w>&%Ko+c7 zqabY2Dk&k+z{J4FB9f3aX{xBuW058f1{Q(lJuDs!97iNwTwI<$Wdzzbk6T0Gn9?~V zCPA?KCCkb@6Bjh{2rxDFk4u ztCzFlqpGjt@&9OThvoDCtj6i8sjbM~|HR(^MS9`E-2b`H?QwgfveW<3=l*Y+>d@x= zw#@Rp)Arcx|9yqAw9D&Fa=nA3=BTT&+S%E++W)lH|CGGgu&}UhrPq(C+@rtJvc%Pg zkh7@H{l(z^q{Q-|syo803m72QL=k=GH zyQ077R)*oGsjX|H*kzC6Re{^E(fz5!=&Z1|#O433uCLSP|DB=0o~5ifh{!F0!`Ru} zma*lKmbb0G%Biig+}`0xa>$vTypx%^P?65v^8eB6|D&d;prxyup{s?9t%sYfvB~s# zf~LCJ|Mvg?g`v=NpYe%}vEALZL2hLlI1n;zGhWkz z40aSiWOg7o01DrDF?KLNMs_M`C2Z?VFLwGiL>2`G>$qgm_Xf&>4Ftlh>!L(L1u-Uy axWI5>-Hs(zeDJtJCyEXS>mV5l1OPi<7))gV diff --git a/src/Umbraco.Web.UI/umbraco/images/rollback.png b/src/Umbraco.Web.UI/umbraco/images/rollback.png deleted file mode 100644 index 0f76f58a604db8e10e97aea36561ebfe01d58a3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 626 zcmV-&0*(ENP)uS`bQGzxa!HZ8d+f)x$&a>7SAP8gD zRBC<>U~VYZnekHFrRVQ!TH~V|xjr)hdPzrSGy(EAiA*B87`I+-RxN7XTU`=DghrbX ze1M}SAVNojVj>iMyjE!WBEDaG^Xm0V?pY#n_Ov8`K+{eYW=&uVl}fYHP*#{Si_vD- znx5h8jxumTu`tvReb>XcTh75j)dct2^5v_m78+c?A$q+U0f^Kgc=wfs+S=XU zc$h!_<8OX;uHQO`s?&GKh#Ywkk~8n_Ikn$@EE4k-}hasBw6{gA62u27`kq zcWqQuF#_wzWt5QBi5hA;sLBF=l_(UnUv_QHSMBWJ5f8}}nZf(rQP2Y?(pmO!ca zU<7!5r|7LMN0P}|Ug(ZX7Z^9K0`QvPwxEsB)*_Q%2ox3%!7`!M){`C(v0koy`S39a z0_+KPHT!`y{gZOkii`o-Y_`|6hWuwRPp1a5xAQ_2Y}+=B&|d)t0CJ)U)b~)=+5i9m M07*qoM6N<$f)>gckN^Mx diff --git a/src/Umbraco.Web.UI/umbraco/images/save.png b/src/Umbraco.Web.UI/umbraco/images/save.png deleted file mode 100644 index 169699cc8fd529d0a2724fa9496c50722d11ae45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 549 zcmV+=0^0qFP)&^DpB>u_D=?a_}8qW|$eC_j#UoCUDL%ON7iA(VxZ37t-~5uzuO2 zh7N1S7~^MaG1l?%Q^MQV`z%I=$4@qcDS#jdIXKX)uEs=BFjeVmYsk*-78lN)Hn2I( z!1^ju3Y1*%0CR^IN@=ie%zl!xcJV65Xje;^&=o>3c8$YBp$vmu`8`9wD3M;44NSF( zlWt^L&eGD~>TWUuOXw99M$^j%C{tE5C<@KS#tIJ~?IDa}Amr~XeM*VUEN2^a%Dkus z7>z8==5gZMiZ?R_tq6u0H4)O#^Jov^g?bs{CpP`7LFhD*I|88HZap0iyEkj1N4#)_omT~0&)#?|1ZiiUMYs2> noz9H@>0|KUKhw+rKLr>7DZuk9I~en^00000NkvXXu0mjfZHV`) diff --git a/src/Umbraco.Web.UI/umbraco/images/sendToTranslate.png b/src/Umbraco.Web.UI/umbraco/images/sendToTranslate.png deleted file mode 100644 index 77a0daf79ca5fdeb51311f8a965ae14305b53f41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1047 zcmd5){ZCtE7`@wyPBL^e#5jXVMW>r?q=q>^v%MW3wu;>yAQTMdEw+@ojoy}atsm37 zriHdTxU2(jX&kT8HfslXk;p~@&D}cKoy_uv2tm-~Rhhvb(3>rq`GeN$f8mpy=j7x` zo}8SVAZ>WLApfC!ilPeiy7Fp;xBZ9Bo0Pp}?4qQQ%Bp=$OHp4)&*p`GRrGdeh0$4K zYjL_w4hy9<+nOzE{c)4kQf)Dr-?3k}yhKqOZqZe>6|yXoH5qb*VLACuj)pAsaH5FO zc|pWP0oJKSS|dES6*80XBZ@Oo*UO7I_qixhywzR2$!5#05p?IFFb$v2woz=a~IK?kmK3?^>*JtqOJeu zFMrRBWR?fKU9LgS(9-3M_aOmm8yq+LK6d%zgk{9Jn}Aom!qLm~@)8~xTVN3LO7J}_ zW60y?_A|7H(I9B|4Fe-XLq@|PjB%)cLZCstE1>4ViPQ0`->y44Q^BjoMIc%u7)JvuvK69rY#%k=Z4P1d;6< z$#JXYbN)){iD_zR?^DZJ{WKm8;0`#1$T8I+AB&8GJ_$hA)T`A8>K!AKxtzxUq-z|ISnZ@-wYo|DH0gv-xdR>Ek7(pENFPtmyw`Ho15RrvBV=|Gw}~C92Twr-w$PgY(BO zKQpL{tf*p7?d`c<#+7+bP0uBqB}+wr{MPk>!9F!P{ppSk!#~26zb)I1Pv*V*z-qm4 zDB62(p}jR=g3RhRKD%;d`tjI(g^?RlNv7eNlzBsZ`t#1*8LOEZ~bsjQ)<`Osc(i%5$g7?&11SIo6Pjm-TEa{HEjtmUzPnffIy#(?lOI#yL zg7ec#$`gxH85~pclTsBta}(23gHjVyDhp4h+5i<*dAc};RNP9sQ}E(bXLi4YK||{_ z8Rw%tYyWR#TwuW+aB_cr{RNZ1zkUjPY&sCh(RttAZf`a7RAweWJ-L*Z$G_(v`11GP z$B&O^-`~Gr*Z%sSf8M@UUm1SIo6Pjm-TEa{HEjtmUzPnffIy#(?lOI#yL zg7ec#$`gxH85~pclTsBta}(23gHjVyDhp4h+5i&g^~(gND{= zGR{YP*8bnfxWIxt;N<=N^$J;$5fT6Y{Nzpf^Y5$hk=D%$3@845{e66Sd45BVT8DtT zIKP0S#GgM(0m%&#D?U6vZtftfwCGZPoc-T_Pd{(ZpTDrkL0CaXq2l+eo9Exh|8L4= zJ-N63_oL74<__U9cQ*bx`q)~)ov}Cl$Yc8k24;qDd8u0ZN|l>|&SUU&^>bP0l+XkK D41j12 diff --git a/src/Umbraco.Web.UI/umbraco/images/sort.gif b/src/Umbraco.Web.UI/umbraco/images/sort.gif deleted file mode 100644 index 26285563e83954d94275ec4bb9ea43edd4695cd7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 388 zcmV-~0ek*ONk%w1VGsZi0M$PLaDugsuHyau{slOq9BtDAUDE+fy&d}iE=`w`if0V>PVyvE^wF6nqJdENgmHSY1wgPnS0Z_xPzu}Ur*?O4Od5pgJ z`u!~2oBp)Cm0}C%XHWM&39(q3^0V5k{1_K5$ i6D>*s2`5$t5eHcg2UXM$Swh$v5h~q778xnyK>#}m0-lZl diff --git a/src/Umbraco.Web.UI/umbraco/images/sort.png b/src/Umbraco.Web.UI/umbraco/images/sort.png deleted file mode 100644 index 8845651fb340417bed0367767d131d8032073abd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 400 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLlPD z`O}}P2Q1Z#SX(&jg}VPD->S>5c?XjEp0zA{RX*p1UB+&!Zai|_0F1d~y8Q RCZKB>JYD@<);T3K0RRfUmskJ* diff --git a/src/Umbraco.Web.UI/umbraco/images/sort.small.png b/src/Umbraco.Web.UI/umbraco/images/sort.small.png deleted file mode 100644 index 8845651fb340417bed0367767d131d8032073abd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 400 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLlPD z`O}}P2Q1Z#SX(&jg}VPD->S>5c?XjEp0zA{RX*p1UB+&!Zai|_0F1d~y8Q RCZKB>JYD@<);T3K0RRfUmskJ* diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/error.gif b/src/Umbraco.Web.UI/umbraco/images/speechBubble/error.gif deleted file mode 100644 index 314b1718c2b4065e53f24455387413b58bac31a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 582 zcmZ?wbhEHb6krfwc*ejG8R_}_Im7?|On?7Ah>rIB|DXBA3x=;>FEB6w1z8_IKJ))S z=l}oQ@82`<^K*Xv%JB9rLqr5`T^;M1HChb~4Bx(Sty`!1@FD-xr+i0`3dY67MnnW8 zB*g6B@3ecj)alc^dwOjC|L3WxslR@m|HB97A3qpw+~{|AXIQmr>FLvg-@Y+ixUg^K z%Cet7c^euUPo3Iz>(-qA|M|1CS$FK15)ckY}pGLrxI z@8Qv-HkFkO<>e+vj!5p`Z+HB-_@__TfBd-f=g*xNFZd20E=Wr9sjABQ@k8j|y_M$PgaxQB~Ct7RD15<@WzSFjyV}1BY5b@h1z|C>;<9iW3I*#SLjq z%`L5MX$DTlHtj9!eyoo2%)L$STB#CJTvNj>cvN&1VqALFQpNauqEhsGZBy+y0+@u8 zoZGykQ$;w`Q@s+kTjR}AQ#BKeSeZg&r!)tt9yg2#F;7*Dvub9SJf6zw;Kp=ZPUC_v z{{vC$WX|K_GL}vL4^jgKg`TEz+nY97nHVIfHZXETGHJ*(y0k1%(wPKI5Dg5BT?}SB MW(2JaRA8_M004#L2ZZ>EP)@aT_#nLAhKKmY!H|7FKqj`vrnUV~thA@$l7Np7mK6Nd zWw!nz3nDy81UQFhJ44;+)U#dp?tP+p_f9p^-VP}iB`F5aFi@MHL-E})J~evjPq{sv zC;X85vSZfad{{Z@VL=4%8;Ftt-}9nrSv=PF=)uEX2OrVoY#T``bjmk&U_$`Jz!4wr zQW3Lnz2%>Ob@Ps37Dq;blRAY@vVICk-!qwe@}7f_4=qhiBOVRG zS}Z`3RSZ0I;J)cUp84tDO#5(S5QRWiOeXByes}kC;ZOvV(=(7FVJy0P()V4IlwU)4QctRB4mS#dqQ&($dP+v1~qg+NJ)d>Ag0o6nTZHfe* z%fnD*L|dEo3zqr&RSw?D?gShW+-x`m6ri;3+x+WnmOYIQYHo=j_HUfvZNFb?rE~<40ImbKuPs{nm8#8lX zJ>xmms-_X}JS=-QR!c>A6s@a;@E3vMRBrZ1f33UUk8(C#@I90$Wy@sTqnacnsZ*#j zmM9W_@^rXF_0Y*k$@lX?G1oZz?c`GF+cQ)sm(pZWE5hS&;}ML8RGcd;{oLaCZXg?D zY_TrMFeM2F>8X)UnXx9x=FIn~uomrE$3crjp}&VaJfW~KdG3z8S=T{`QCCsP#INSrwZG3kr0HQ0ab~a3@=3) zp0-GNCGd(oERc*qosuTGE&(5F5ZLQaDMH$k#H^w!FbL@iX(tS$;`3#R-tXv+tKS_` z<0+#?T;yYSZ|~z3#C>^l0r1;qDW_382hLj3ca$dD6uR7 zt>Ug?Q-qk6YEbt4+k#E`E*i#_D23NaTcsTL?ccNIi=TM-P}UO=A;l?CLZioU(X@~J z%kjR_;KFXANTS41o5*OZ;~eN{$?QpzgBc>iW@?H%mOVj<8JG4|D*8K##OX!X>E9j; zVM2GY`1UY7Vsvw?V}Okhs2>6vwePmNk0jN<@k7&2(Y6S+bHtoijGdA zjb%#Elxe*=WEeSVmWoRR_Tu$o>A7v~+3sG#LTSOKLE)phtsUKc58@xgum9%jX5q+( zsQ%Z#WI|I))sEWTS+*-2f-yOT&9b;ogl9{7rQrF$3Ytf43$dK#Z9wb5qFDK{OV@Yt?fgvFS}8>zr!bZXnZdLTE(gn|Cn2-5k+Ek9rjPbU!aI7RkwlV;DbiV$+Htm` z8)HW8Ceh2;MaGVk*W>f}?{y&!rSgbL{`Q|y!yTb%{ zk`<|Qmuo;S%GhL=?=P-9NBeo&9qZp6AFTffFaSr;e9@{93g`d;002ovPDHLkV1nrR BnMnWu diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/info.gif b/src/Umbraco.Web.UI/umbraco/images/speechBubble/info.gif deleted file mode 100644 index 6dd76db6b2a4e20bd0f8a75119cd8ebd9f1ba410..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 630 zcmV-+0*U=cNk%w1VGsZi0OkMyYIld*-ptbM|K8xuimj-(x;I5)!NIlB`2S{be$VRvxYg~Hug$aE|Gmf1MM7hh zvd_TR?BDeN%kBS~rMi>2*s->-y}q;A^8bgb%4>+Hh?k~ZaEQU!?kY%hA3luP?*HQd z|3YMx%JeiniX1b!Sm4N^NJIg_35&!@I diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/info.png b/src/Umbraco.Web.UI/umbraco/images/speechBubble/info.png deleted file mode 100644 index e5734da6ed44bfe2c6efbf91adfdd6f65d36848f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1542 zcmV+h2Ko7kP)Za;TF0teSz?ysoa(zt5b+U3jZ>Z_|u9X_8-_`v9B z29Zd@xO}OjqxGjN=Z0gEBf9lXz;cwSX<$xECAC0?z%D(K?HOY z=;}%1?AhNEKb|3dxi?9t4q4pw=)@TvUR_HLG{JxsB(t*4H;}g2IZ* z&7r2o@>pk#HX7gs9z35nMxyB!uC$w|EcM$2ZILW)-5#`Kw}HpaG>LGCM@9YIqHxu$ zSIPAq&V}_-!@9Me@QC>3+6Xdg&W?U6IJ2B# z!0q=T`qz!F&I{+8L{V_pI0I!3x4NRpCWjzN(me#sSfw4*I={B2>!zMBfpEUTJ$ck5zoT0O@Gz1A^=ExUe3 z&a~`9o`^n|v&|&Z!e9k2`Mq;Qu|N(esRTxQ+rUdc7^aU1Oft%X9X6Sw4hM< zplBAAWpd&)%|s@v*rg(Pm@IN4WSwOwNf?=aYFQPQC_*%HM!(?n*`VrZKlHH(^bAcB zPi-lY_K38qRHn?z2?7R|=;?;0FrCw385xoinoNC^*ppxqz+*8h)g4jO)ONusr(OPP zS4%yZD~>coa8X<(a25u0Lwau~ zuj%SdL(N<-3^mc%$%N@5CprIYuipI1iBlAe+E;MXJ5Syw{OFl?!#lH{%7$)T&9?Ju z|KVJsyW@0m%|bZ+vzUC@PPQ=+-ZM@-g@8>o1LfuXdtuz4&X98!VMgszj1BaCo4s== zZO?wPxA#jr#^}riPv=X-Ir5uWL?VmSK^-Mb!n5uYN%chMB)DBx2WHePgF`MzjP%Bj z;I6nRU7rj_uU>8`Yp6%boRxV3NK^(#e$c2Snr==Iu$Hv9vPow|4BQf_G<7Ijvf!#j_BV~IAL-LxnMG?pZw@V5vD+&Z&-PG+1vJ@y zS*A>6It!jASiwjtjS(8O5xP5*TJmG`l$zte9Ws z^LYY3Hw2P~#F&bBawHq+iHGB{?$blD*jL66Z^!@Fe-#rBYYvE<*F9U3N5e28hc suBd%_I^AuhGF{g9Z|T$R|3`oU0GMi>$mrPZ4*&oF07*qoM6N<$f`J9>1^@s6 diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/save.gif b/src/Umbraco.Web.UI/umbraco/images/speechBubble/save.gif deleted file mode 100644 index 89d75c110e21c4b20ad72e60ca5832c4a149a60d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 562 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 diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble.png b/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble.png deleted file mode 100644 index a9d025ddcd9e150ae44a1c7ddcba797b52ed6c49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12444 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 diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubbleShadow.png b/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubbleShadow.png deleted file mode 100644 index 40c875982fb5b8047f57abaef758244f714a2ca1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 916 zcmeAS@N?(olHy`uVBq!ia0vp^?}0dmgAGWw={-sUQj#UE5hcO-X(i=}MX3yqDfvmM z3ZA)%>8U}fi7AzZCsS=07?{mGT^vIyZoRqFo43e8q#^L%uYde8sb+Zz->uFq2y0`IfE&Z#Hwv}f$! zlzjhKq5hhxJ=XdYA8+uvX;@&LqFAPQZ{MaAwrdx~+A?{z&tFzpp8M(B%_E|EInqVu zt;d713P&Jj{K)i(6@-s)gK&qqfIbK-Ko!CiH6bhP5Jy%BG3~(PkIPQk zo$Tj~TXrio{HA56*gd^RoAq{kJ!NY9&TBq(+S%o~Jkea!4}81WH&riK_g?Z1H^#%K z59xmWvHhyun>%f%%VwR*-C^4=W9Rw%h(>;--0=eb#M#PzhYvlmZOQ*pQf+_t`Y+p0 zjtjT7zV3IaE_@TkQFU**$h-X~twoNN&5y6O7CbKcDe|sd z?!$%;EbT9U$*imRxuxQH#_kW_@BdYJ5MtS;%X{Y5&gIUN=T=K;Ev`MY&(7{c@8P(| zbN@eUU)ika^LRds&Gs*MW9BtznOdYBobUVk;i+3SbrUQKTTd@PYAqJ^GxNZ4rvqD; z7XJ$h+-y?m{dN1pPm=8KHi#GRSvuM0JDW^%kN3mmryD;%-(%rzd*??>?&GJ5cG<6v zg+9M@dhvI*nCiFNb{cuiu714t7sChcr2;8YGxmIEnX^u(`@Pu@+k;mh9Fv}YW44@M zmw1`fhKj`HjNK5%--gK*iSrp}?@>tjcxW1sy)k`ZL_A=R(n$;h8_+t&HPF#fjgDZdj`~125PkMDl;@^hH_FFpUt5j(`uxH|7 Zn6%tE@m!0aH84joc)I$ztaD0e0swxVyrlpD diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubbleShadowNew.gif b/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubbleShadowNew.gif deleted file mode 100644 index d2e68a15e7cd74573ccd08b4ee46b9d45fb5ff99..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1774 zcmVe5~Be{CpdSIx(l{XwA6zn0?*I{kr>JHy7|2`j2 ze{Y{(zmLza-w*JgK!5e#!Kz?roWgPo&oO-HFk&}};2KW62##BWRsV2sxiJXRPRNfU zOOiaf3gs}B2UEIS`LZRtNsxv{R4G#Fh-xx*)Tq#+Lz^;vdQ{>9 zgJD|kylN6>R-IdIcE!pPtk$q!%aR0&whtb9>eL=er}5FJic!tIwHw!>6}nTZQQ_OD zQrn(Z2^w?f%P?ZahF=W?=$K#RfW+!nF(^dNc1x>v zoi_IC!D~ZTMy*UEG+@Fr~722V#y%k`Do95VNnUqS!=xUeF87Y~@MmmnBy#I=s*-At%mSv|2>38X3^%aWk zvnz7wtWc26Mv1GM$(U@g$^tnhnxxd(=OYz}lPWlhHa^Z#eN5yDNhc6D!`sG`0)zuMt~{aj$|INb$3O77TK;Ia#M_n8*#8 zEy>U_#@lQM>yhif5X%cE#?ZM7p_$jId-JEB${BRdNane4mq`mdD^CKK6|hgRMyc(b zC(rA0)#tT!F0nbgNgb%+Z5J%OK4x6q#b$Ty^VYh$JhIFyvKSag0GdVcl8o%x#Ek^lGZ{v2PL?X%iN2Q8kAfB#pVuc#oG>mc|uPxWMM}D}? zxU&s;rm;^i{p{Goo;#{CXFi&tEidmG_)F@_H}j}Ay)F4!L%?(Vl>huZ?$v9rX6vk# zyZyXd;0m|*St2DD(U|&9^_t|pENhAK2+SC`yqb9oS+v97uqt>zj^S*6(y|~1&GUs0 zatwL-fMD=?hq@1G&VAog8sJQ*wG%Xqh6V40F@oX|2WP%6;fsmo zbD?q&$i~Y7F^iK6+ZVByLzdkUfi%Qob;7tmxe;iA_hMljohUL@nURGTOp+m+S2!(R zP<|TRV-;Cv5g%64k47t8AcWYy#8K^xQCuW!QbtM-IWm>tYM34`Ikfz7QkGb9oF6AR z%drh5JHYE3(g?`PBpz@>!g*u>3mHsY!Ow&8L!2c!_`y{^lR&xjr6-5DGr8=mQBgdk z!4eok`E(O=dP*kAVw63z;c}XLB$~yFNXvKCkew3L9SW)0yflvUmEcsQ1I@WAV+!<# zbIhMQbx2PuKFoj#%}7JRXrL8Fi^Vd9WGFzTLptw;O`ip`)gjsV6AyY3Xw@v~#^xzc zI^Hs*b~+kCA^I^RLYy!-JCXnFjj5sr$~>7ejYlAy-_ zUJ6wr%=D=>1tB0inbfRm^=PGn=~lmrRIGw^tV!KwEy;@3vOX@YY*j0k>e<$~?tpu8 zt!od=icY)gH3wbXt6%5Z%fAZt2HhE~VQH{0xE^+~Cr~V7ADaSQLUyv0tt?veYT3+c QcC$ioEN4H9f&u^lJKo`bDgXcg diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_body.png b/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_body.png deleted file mode 100644 index 17a676f2f4c94ecb1e13f4215b2bac0397dd812f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10694 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 SRr33vm5Q>eGG$Wcf&T%Q+%`-A diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_bottom.png b/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_bottom.png deleted file mode 100644 index 7d213a9a715a92a6388db889fac104243a7168be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2891 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 Y3tW?L+rLQLHh*uO>|8NrHo*!10R}<$t^fc4 diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_close.gif b/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_close.gif deleted file mode 100644 index e6763535b0508aa412ea7eb8980104ff4ba0c7c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 169 zcmZ?wbhEHb6k-r!*v!E2pE=6)%Q+iLotzbduB5)7xQ*P@yYBaa^kS^2fT_o5IX%#Wn`6VqWHKUZYdt&B9;} E07xD=&Hw-a diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_close_over.gif b/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_close_over.gif deleted file mode 100644 index b5958a555225fabd08b99ab7545ff5629714b538..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 696 zcmV;p0!RHvNk%w1VG;lm0OkMy`py#n%6$K|M*rI*|G;7Yz9#>-NdLud|Hg6uy;}de zQu@jT|Fc2=$aUVO4Ey`t_4U}kzNqx{*Z;$5%geL)_}l-xRsa9t|FJv&v^?G2!~gc= z^78EexK019G5_`D|GiiD_woO#E76bu=;-9#qzBQ58Qh@)|LDd0`}EU|BmcQi|F1Xy zzhC&NFYxfx|F=x*>(&3iUH`#k|J9cN-mT}(pa0H4|JpR%qZI$9A=#M}|Jn@y*`fc= zgz(a)_OmPhvL*lY;{VKm|J0NE#vlK@M!~_b|Khd(^riQxF#p^b|IR-Dyi@X+5dYL3 z|Gp{Df)DtvEdQ-6|G+x`$~5}O9QUU$|FlB?(TvrT7yrLt*OwpJof`kuEc*K5)sZ3e zxdG6H71N9t|FJ3gz9aa(C;!Do|HmT#%Om%!Ec(w1_M|EHpdS9C8UOX+|M%hewJG$V zBGHN--JuWv%PIff693*8`OF#hr7iWRE&r}G|M%nb^VYbyqW}N@A^8LV00000EC2ui z01^Na000O7fEO1J6%Z1Ah>3_25ETy>Unzf+l$DbqF)t4(C=CZP3M&d70t^@)az7jz z6@LLeL_?kvGc6z;3|A(96cB$1G+6-%6I%%iFaj7#bAJ&Me=|M-D1Qkn%6~N;E-HV1 zeSa1W2$TjHlvCZ`;R+KI1d|&=lR;!sVC;VaEd(+kA11wlQlLN_F8UZ)NRXgOz&~*6 zq(M`ri@rTD6j+eQXVQQtR&wYQ!2kxOGdCzL0pLKN4i;FD{4sKd364KW?5ruF&y5Hc z8G=%&;$^@c9t%!>2d05MYp4}b+>Mtp-1;ztxQCQOXcvV)F;5+_F3AQ7QL e3=WnR;{G9Vgu;Ut8Yo2Y!?&;B2oy8~0suSG-)W%$ diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_shadow.gif b/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_shadow.gif deleted file mode 100644 index 2f49604df553b82bb923f713548b3db81b5d90cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 660 zcmV;F0&D$8Nk%w1Ve0@`0HOr|&d$!ax3{FEq~+!1larJG|Nj6000000A^8LW000F5 zEC2ui0P6r)000C3Si0Q)Fv>}*y*TU5yBuIBj$~<`XsWJk>%MR-r&I{sc&_h!@BhG{ za7Zi~kH{o&5CP!?(5Q4uty-_xtai)odcWYXI16|<0a1c_&2GEj@VI=PJ@e)l9QB`mIMKgf|s0~o}ZioOa+0Epr@#*s&WFEq-d+L zva_A7Or^BBy1Rt7uL8Wm!ozO9xWvfGw8pT?&d;FCz|hpylhW4N+J@NM-rsiJ;N#?M z;^pYy=jrU$>+SH)@A34*^Y!?;_xb#>`~3h<{R>DiRDfH}3M!0t@YlkJo(4jk$gSZq ziWnD3%*gTo&c==)Sy@aNvLv35CQ}kXsj?)?mLOlk`JQCr^bwe-aET z6d=)}{Ei~Ur*tX3rcU8OjmmB+RdiOblFOzcC5RR)67yVs}>sDw(`)z z)hTyw&$@PjHY`9guTfv5()u+TU@zan0ihfmr9d#^r>zVxaQrx3l)@k$1Q^4)v**vC zLv!YnrSRe=rcM2#81g9{%{y!Z?~worIj&b+zv z=g^}U$OBlq_3PNPYu|8E^7ilG!;4?;(fj!G=+mPQXI{Pg_wb+BsUE++{reZ_(8tfe ze}6w&?fEC*fa!_E7=Z{TC|(@1?&u?aIsPbQeFsjE002ANH({Fq diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_top.png b/src/Umbraco.Web.UI/umbraco/images/speechBubble/speechbubble_top.png deleted file mode 100644 index f4fe6dee858edadbe87afbf496af38f9f35477f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1045 zcmaJ=TSyd97#x$H9!hsEySn2{Zo2NQx!RiUuDS$~+bl zqK2K9MW4y zZI~GCNUEd5s;JS8r$9qiA_4})3dkBGrXyv8bY52?YxkI?K^}sKgY>$oRIC$(kqto| z!+BLMz<~hI@cvps5Nd#r<2jb|vAoaA3zFX_aU9tAXcEoV2Bf&$vJs2yg7gr^mc+7| zOoqw$8DtN#yeNvUhR^3E2(Obfv6A(gPE|ochK_3M7S@poTt%fHrE!oZPS;Z~tW8DBHrCn7tLxe&NqHebO}ZPWgWABkkU`%t5i4*79&-_<|Ql~TX} diff --git a/src/Umbraco.Web.UI/umbraco/images/speechBubble/success.png b/src/Umbraco.Web.UI/umbraco/images/speechBubble/success.png deleted file mode 100644 index 90b70efcd109b084d4239071cf8361509b5d6b63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1036 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 diff --git a/src/Umbraco.Web.UI/umbraco/images/thumbnails/developer.png b/src/Umbraco.Web.UI/umbraco/images/thumbnails/developer.png deleted file mode 100644 index a5b31220724e19263cb83009fba94c260297d20c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19431 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 diff --git a/src/Umbraco.Web.UI/umbraco/images/thumbnails/doc.png b/src/Umbraco.Web.UI/umbraco/images/thumbnails/doc.png deleted file mode 100644 index bb18553f072657ca1fc574d9623782c4651d78a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8311 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 R_l`XRr>xIe72%1L{{iFJdjS9d diff --git a/src/Umbraco.Web.UI/umbraco/images/thumbnails/folder_media.png b/src/Umbraco.Web.UI/umbraco/images/thumbnails/folder_media.png deleted file mode 100644 index ccb128592871a3c2d098eb36fcee0e7fc969707b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28459 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 BTe<)M diff --git a/src/Umbraco.Web.UI/umbraco/images/thumbnails/members.png b/src/Umbraco.Web.UI/umbraco/images/thumbnails/members.png deleted file mode 100644 index 44e393e8a6395a821fdc3bfb01b524855f554a3f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19684 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�gmQv-%%6Ns8HF_!kp9%_P@?=5WHE1 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 diff --git a/src/Umbraco.Web.UI/umbraco/images/thumbs_med.png b/src/Umbraco.Web.UI/umbraco/images/thumbs_med.png deleted file mode 100644 index 59af8634e2b1e22acf2083c7d6f211ee53446fab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 967 zcmaJ=&x_MQ7!4LgiVIdmQ9KM;SroTPCT-FtZd`Vo)-9M?+rsv?ohH*Zbd!n6tWDR` z>czc^e}I302;xQXpm-3xdG_d01aE?Z;H2AjJy=7M`N4bd`{w)J9M)^MmeQBf9LFtH zx6DQ|Cf}(O?Em{Py3U4+wA`jm(xW33Vs67BT@0!o>f;7R&iLL(yvA`U-fgvM+q$jW z#1l}05n?Z3Y>r!7j{{^6F$G=RcYTBZ{^l(YT*u(I6-%;$5+1l)`yp=b*IM@e(AFG& z{U%t8btd3pia_k``jH+R{6tr0^JH7(!308w27i=P+p2>S2{BNFT-KH#1ga{)yrQab z7042lMTxy?7Up$D)#U=1K0J#SIz7E%R;IDo%HRi-2D&JYMk8UA6G+$>p{8kxhAd|p zA{&i;isGyvEzcB89ND28P?z{1QAAy`M-853dUOOYn9KUnv`nmEVvGV23Q}^U8PKx+ z5B0n`I-(7HobNw{qt-aUVgpBHFSJ?Xddo?uKre+DQ4+QY*_~CfJ|L7t0}_BzQw3MI zUEd+2=-LEtS$frvDDrJwH4UCA2(IhsQo&RexhR)63pohOP}`_LO;U=LVnHocGF*mo6n^?gMSY#82?tSc3LgInRk#%=*F4981Dc4z?3tY$*Su$eM*uNS* z+hXoXrgPh}#oYMVXKoLft>3SlKVVnlM%CPC#h<&s?p*GmPN(?b$7<@Vq^cjzp@Uc8 z^y_p!vvTqa_wsA|)AL8Ee5Uej=dnVr{Ja3(aZk?Fp0+dJ?j~2OTCSNdHaidh0HUxd A5&!@I diff --git a/src/Umbraco.Web.UI/umbraco/images/thumbs_smll.png b/src/Umbraco.Web.UI/umbraco/images/thumbs_smll.png deleted file mode 100644 index 081159777dcd91b3790ee9ef668fd35d2a12078b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 957 zcmaJ=&ui0A91jlJ7!)0z^z_IsiZo2(7ZO53EaVoGT%>(MlMZ9|v5n7o>_7e(-} z34#Z2{sEqZ;YIYIC&7c~p?`vk1J6o*>Dq1wYe@3G@A3J3zCYi7y>>IPer26um_&6) zZNzK*t(~L)Kl{OTx?Cn&n>2BkgoclpqJ=vMR2`#-8pyCl_dcOK!>q7&t4-SaZP~;Q zXT%sTay-grn0z7f40C`8=%AkMD(vyQ_bjk2h20hPlkb%OU1l88-pp1P4L@u2)QxF14;$TLUBzOY| zDJbwM`btTdkwr-sw!rLRsW;#1$_=$L^F?6dZ3M>jtwj(Sq@j;qj15#VO5!y8eHt z<1EktX`s`1|0x`_MjqlDD8NJCq>bxt#;!cM>?4C05MXj_YK&Zj2CQ>^kYOt8mDmh`!Rk1mA zkW1!V>y#@k<;J7nc+|6seEUAKDn53=G-cUd9!q+u-i&K4k0rB|%hO=^xUpv%JwKw; z6Pt^%rJKdzBbQFQPsh6SYxM!W60oWkThZ6f@5!XFvhi@^<q=r2|xEqed} diff --git a/src/Umbraco.Web.UI/umbraco/images/toggleTreeOff.png b/src/Umbraco.Web.UI/umbraco/images/toggleTreeOff.png deleted file mode 100644 index f165e788071b63c281858571ac35cee084153139..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 266 zcmeAS@N?(olHy`uVBq!ia0vp^tUxTu!3HFMRzCrAN?apKg7ec#$`gxH85~pclTsBt za}(23gHjVyDhp4h+5pwA@pN$vskoI?^Y@qO;rsjRCHK|;KX>lPku6_dUS2*=hsWW- zp+kp$e0_a=r+(ZX4mPb{Cs{-iSh?2hE`1%grLmE*dBTJX^~?C>?Q*OuJ|r~EoGID7 zVS~X94K+KveL2d?i><}=<5GTne9X>lY;4SYd4K)?JWnsLsLsyLx%QQxR1PoqpZ~8} z_t)Rw-?JAic)vaW{x+tYzk2@v|DW!_?!yzoaC)|RzMHJS?Qv!n2?lxn+ma?58xn!; OX7F_Nb6Mw<&;$U#*>3;< diff --git a/src/Umbraco.Web.UI/umbraco/images/toggleTreeOn.png b/src/Umbraco.Web.UI/umbraco/images/toggleTreeOn.png deleted file mode 100644 index 0107bb4e9dbfbcb9f5c3f9901db9c7d3b5a132fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271 zcmeAS@N?(olHy`uVBq!ia0vp^tUxTu!3HFMRzCrAN?apKg7ec#$`gxH85~pclTsBt za}(23gHjVyDhp4h+5pvV^mK6yskoJNXK(fQdP&Kd-7zsS_ujmD<3DreOvxE`H9tQ5 zeR_KOuk~?zZ=E@HO6wE@i%(5WP0zDu&v+ah4?jOYUp_f0DJc2Z@9+A|?Ck8#`}Wx# z-dX%S#IE+2$yCmiW4E{GOZv^VI+~E5zu(cp;XwDxySvMK#Pwnl0)m61Pn|t0`vqvo zeVtFAKGiQ>y7c-#ph1!vALN%z{`mj@f1Vc&os8TGH#euBU-5Y5^NI!rW`_JpyVor@ S6yFDQJcFmJpUXO@geCy+aC%$- diff --git a/src/Umbraco.Web.UI/umbraco/images/topGradient.gif b/src/Umbraco.Web.UI/umbraco/images/topGradient.gif deleted file mode 100644 index 1eda56057a4a0cf689042d61dbf866a609a4ffd5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 304 zcmZvXEl&eM7=$-RbF?)Wz!cMv5RzVKS_}kJOo0$A36kLZA247rBw*?Y3WtDSfC7a< zksulnw|lp@ANRSp`yP8}9x-AvlgaEIjJLK=9n?b~2q~qqEHhdYObh_$yeu^UlonO8 zh^1+kB&j7JL~wx#3BrJev`{KZ5&#n?agCIWGaH1e0s|4HS(GyZh&MyL6!Vv%f diff --git a/src/Umbraco.Web.UI/umbraco/images/tray/traySprites.png b/src/Umbraco.Web.UI/umbraco/images/tray/traySprites.png deleted file mode 100644 index 2b4f0c9a7837ba41aa3fd3c795d4424096ee804d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11965 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 HD*S%{@I-EM diff --git a/src/Umbraco.Web.UI/umbraco/images/true.png b/src/Umbraco.Web.UI/umbraco/images/true.png deleted file mode 100644 index 2f86f0ae6bb797bf29700cb1d0d93e5e30a4e72b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 612 zcmV-q0-ODbP)rBUO4f*s#JWo)C=2NW(z=Pj2y|)=wUAF^X9) z4JKxy1u6j0)RIi|M|)sH*wG=?lkGp?mG>pB_MOq^I)2;MG~R$-+5s)qA?%3_N2eo} zXv=IwoqbWvE*4At)uq*@s1n7bC*oOgSF=+RwLNr5*I9GqvaR{^Jq*&<5EDB1n?fTb zm9i$Dus8s4icGE-l{gAN2=9q38c~p!Py4(B*$}~^&cCXrMFSPUdx{aWOhk+U~sJy}m-eUU844&0=2l-7&OZcb~TiB5r z0AlWV30J0SgN!1!`5SNM*CEVJV1gTiG6i&8eG$y5$Xq{AJQ~MOnYT)Dr*_ais&-j# z+G5;h%n*ox)FklyQdiJ%!fJW6w(}QwQIRtCsoTnXOA{Bur&;c)oZ*vSD>X|Aw&+rK yNc65bzq+rET@n6d=x-0l;`?bjil?RWQuz-0He^X1)ISyg0000KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0007(NklYxd7=@pE=fm@1=T7Y4G(iEJ0B%cDSd>j^3sQHLx@grMd;S1F zfFD!|wW`FXQhxx5N~CF%CL|!^*r}aKJd+uFGG^v>LknK>?$4uhbj~ZZ*1XS|06U%j zMs}91yOx2_C8N{y;c~O_z1D(KmrRS-NMVpo3%X(S@aBiBFI3h08Tds!+t}8doj$f} zqov7sl9EI***Il%9Fn9d8j0yRv@W%%2LacXn|!+77Qo-8@_lSu@X^&4)6yc-Ht{$E zT_Vv8!;rm~9a`rUD{Ctp?Czp`|80bzt}yl_h3RlM&MBovm?l}8&{$}ow4|8}l&7g! z7Org()YV&n3TncXLQnFXY?3m{#{Bf8isQ^a zQjjDGE9aLu3_ToM^WX=cBA@c~`6(x1N*qU^HATgoX+Z+mmd(j1rt_-Hc5ll4Z|89> z!QHQ$><{3=as#)j@I9C5w3yLLfTo(mkb*%N5yl3Oejl;j&G_-RF1^H}>PXTwMF@~m zvbp;b;B&x|0Lu4~6^nwB+v`>KU&ZWoBN83(<&}a9jS9<)0nZO2s;3GZstLLbB?(7m(P!q)@)2)ja`Sr=4 z93TF}PTv9~k2elE9`+dxTq;IMeXhow@67@Lq-Bw&DPLS)V{K&-LznDsZIWae?RFc_ zb=c~5$@Bb%^t}QQGk`(ofcT(8e(ht1gCRTnFBru!HQ&dOn$tMOsRmrSb@SO9i2?Zk z1C|!%`Qxt*0;L!p4e1|+C#5QQ<|d}62BjvZR2H60wE-&n>*?YcA|c6o&|A+rkb&Vsas2(@ zRZChzd<-rL%s+C)K`AUj&@4exERnTGr7g|ufjFlYo2X-lQc?WBS5|%h$H~-pP=>{CWcO1^&sC zas{(rHNEeVX?R|+D*2koKAT-1TVxuXF7Gt2pCrhe%W27Qah0e`>%4W-qEi^2ys8L$ zkzFy>C52%M!-~#H9Xt(|Evme3 znsTM3@O9jy7lMk63GZ(u8Hac+{l2Nv{)WYyc2KzeV81veQkLz}aZs=_c)I$ztaD0e F0sv?9ij)8V diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/bin_empty.png b/src/Umbraco.Web.UI/umbraco/images/umbraco/bin_empty.png deleted file mode 100644 index 7d7dd1b221f86746464e490e6ab1e03e890f89f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 659 zcmV;E0&M+>P)r1N;Df4hQZWIafk(XaN-ji5xUkI(z>W$ANP zg0o^nqZY@`ka+uJBv)`tjMw{bRyDmNAmT4 z29r|OS%??Ai&(#ExKk=`l4dYyFMZBI)Hp*tNukl}R&N5+DDY};0g4^CjbCk_W z(iB4H^Ib560Y&)}M;vuC_vO=NwT}O5UK>)cdq2^GiJNA5ll4>{(=m4;@ZK3(o$B&LDgTle1n+lt&6*p4~ z<54WHtXk)FP%b6axZ6dNrPzC5;@QK7TcMQiZl!X?7H*X*3?@^VMGhaqd3{Z|<%xvpIQi|PqQ$VRC3c~dxe(HAwq%K0k$hqTmKB^j#LMo;z#9Uh zGB4$SMz8$~zNTPXLiT%od~3EaBIPX-pN-88jL9}F=l=n&tu5p5%K;qQM*pOb tD4ywKn&dCwI#jF z45Rda{r>g$@4w8}#ZR6*`TO^8&79IdfBw9C`EpTVX>hLhyN_=_e*SX(-qZJQ-+ul6 z^V;FV5AWRh^ZQp!gWuOLU+z44SU+Rkf;IDh|M}Iv`oNt%J3oK@w)@zTr;nff`SZ6a zJ#%Gl_M>MHCV6;Fjf(#H<42aW?T;VdU%r0+`P;W2KYm<4er$4L;)$I*eR7@JmTZ6b z@$<7+Po6w@U=plbwYZ>dN!y3d@5(1EUASQR&!4{+tXXg$=#j^dGP_pnKDN^_$)-0Z zwZYBx|G$5=Rt_sm3a=eMo-jG|>5C^1uUx%x`C@6$oXsb%_7&%S{`BeF&tEl5cRzXc z{^E_t%l4jGy65zXD|bIVd)m5Svrm=%oik^@eEq(0+Z^fgz1S2V^8D zP8itdHKe69x3spVHKlfS_jILnq@?n4`1CULO-R)=w2ESKWm1c9PVMg$kd2FpwM>wZ z5??x#fh{yr#houYHF;BKysDFqC(&Sf>M~;q~Rgy{l{{5K4 z+IyLF{{H^^^z?qL%(!4;s$5;vmzJ(tTGo`6;_dWvoVFJKWf4_)`hQWB6#*c%;gS3N#ca(3S zM@PPxnP+85d8Nhi_3(D1!^d}c#Mb1xdwZtH-j1iXS&v!h)6;f{rq80I;JUh%xzUrk z*!=kT`~CgZ+~t`th|yN*_oEZyv4Q0v0`OXz|g(L#=4ThgnzemZ;_+Z=*&oYK%cmqonBs* zzS#Bn^pZzQoV(Ng{{G*_#eBYl%-hLov0i79y19dc!rQv~`}&Zpp;v`SYMWi!t*!d^ z_npGq_V)Gi=jUOgSAwI%bg5>Ww8{DK@b2~b^!WSPrlz)9U;qFAA^8LW004ggEC2ui z01yBW000M{fPaF6goTEOh-G{fNltq}h<{H7Lm?nhG?7wdb2dUSd67Xq4n<)V9EU%8 zRWc$9K591!g;P67E(bby7F=#@T>^u3Yco_(BN%5UDO-3MgC#2x6Mjk^DrqQoaA<=A zU@Z|3V^=r`S_W@eg9J4jClE$2UQI_#7gmD}IXr!FX0m)?;bDOW5`!4XA+jVu06Jm- Qd;k!mO{9w$2?YWGJKB;x82|tP diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/developerDatatype.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/developerDatatype.gif deleted file mode 100644 index fd922b92dec8dede7b09c1a62c415c4533cebd6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 125 zcmZ?wbhEHb6krfwSj5Kg?c2Az?>=q1@qmGW;s5{t$;rt;63ABk$->CMz{a2hWCK+S zFfcGXW$d~WW#E;`we!a<9^;o`3S6@3k|6Ss(@6Ihd-#z+epkmrX6@ diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/developerMacro.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/developerMacro.gif deleted file mode 100644 index e66d10972c34803f6b3833dc26b62b50652f9e57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 604 zcmZ?wbhEHb6krfwcoxR+<;$0E-@d(i_3HWa=UcaKy?OKIv17;b^Yj1w`SbVh-&?nC zy?y)k(W6HX9z1yQ;>F{~kMG~VpOcf**x0ye(W1h_!V@P>ehcdUWmDwJj|zJv}{Jwrr`bt$p(3$@%l=`}+D0A3pr>;lqm;FHV^< zB`q!O&Ye3KE?jv1`t_A7SC%YUGGW4mjT<)}KYsk)y?dKCZ*FaEO-)U`diCnvyLb2P z+qY-Wo+C$&)YsP^Ja};a{P{nB{)~%@OGrpKaNxkgg$w`v`}hC(B{r&882}Tt62ZqTuPCQclJ}RMn!UA2s zcCsNFLfYP;Qf{5hO8k6+%|VP7k`m40a-M9W&8{A-68g=|>@ESS&C)8i?3|s30t#G; z=1N>#Y=P=J&5Vv=)47#pSQJf}7>%0c&3K*J{PYZ1E!kHwC^VdNX<-u6kxAUJ_z)Yz vWg*=e28Xy>`I*HpoG{3oz#z;dVKU*t!p95bjiUkt5{v@3<%BUYGFSruU~usC diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/developerRegistry.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/developerRegistry.gif deleted file mode 100644 index 40d496f7ca0a1e416b18d7448ec52cb9a4df32b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 zcmZ?wbhEHb6krfwSi}GV|Ns9tPMi7k!>{jOkN^7l@c&GZh~iHcMg|6U1|5(JkXi<2 zYmHrZtPH$erWgmmP?XLK8h|hD0()1o$KqpIgw3DtB>#4wcRM{jN7ZZeVR>0 SCsVTKwXZ1gvQ=SVum%9Q{4^o} diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/developerRegistryItem.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/developerRegistryItem.gif deleted file mode 100644 index 0b19ec1607fd58d9b78ccbb0672c3a0aa92ce15f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123 zcmZ?wbhEHb6krfwSi}GV|Ns9tPMi7t>+xSdAO4>S;sON~f3h$#Ft9P`fK-6gGB7)2 z?7E|7;B|s!rv!iC$sf+!N)vt-c89KM?a+D^+Hmi;fUaM^=DAH<{qiK<2o}1|cz^wo VrtADChf_BBt}|;do5;Xm4FF+%GF$)v diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/developerScript.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/developerScript.gif deleted file mode 100644 index 87a04a5f677778c8e60923b9af6256b1ec41373a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 399 zcmZ?wbhEHb6krfwxN5=h;lqc2|NgC7wd%{4FKgGX{rK_Yr%#_gfByXU@89?D-~ayo z``x>DYu2pUzJ15DXU|rzUj66KpOYs~zIyfQ^y$;LZr$3nY4hX9Pqu8?vI_)uA3JvJ z?c2A94+Z^ zfx&`72P6sd69d~C2bptjI=50hM7vdwT+O?%;>$4+JNI7RMa_(N6a+#i7n~MRTGyW_ zG}TmcyLisSNz6&c0Vv5AQD)f%^3uox047*Pj3rH25SIs{m8rk diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/developerXslt.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/developerXslt.gif deleted file mode 100644 index 7204e1b54fc01e2a310c26068f8ecace98b45acb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 410 zcmZ?wbhEHb6krfwxT?wU^5x5S@7{g+^5xB&H{IRct5&Ug{rdHb7cbVXUHkFl$6vpG zeg6FU)vH&}pFdAbOsuM^`u6SHpFe+|J$v@`>(|w*SFc&K=E;*MpFVwh`t<3>jT<*@ z+T`Kk@!`XVA3uKl{Q2|k+qduEzu&N7!|&g}*R5Ok_wV2D-@mV4zy9C9fB(UNfnuQe zlZBCiL6boTWD>|v3~bvEgq(8GndNk%$4%L%H{ikwk)tAgescX8(w1{wR`>`zYR$Bk zZS8o%YpOl*sFOm3k3!TW3)fX077C4w3Y@~SN_m>+KSGvUZBf~8wSI97 zWIFC40w;L94i4pYcYs%`XuOo^0KvIA(P3qOLf;?oeLs9{?X3-s-5>x0Nhtusa6X^U zWHRY=dShc_eSJL|jb^jit*x!4rKQEi#X_NwOeW{&=X1H-=H}+=>gw|HGGQVX78V?K zyO;H^tgQI`ewJmNq%{(W_% zk~WTU%3)jS*%WjA$4yqt$9P zn!gbQ!?1~o2^2+t{{#46s`EQ|03Zd#+k)*UfPfm54!jx~ki72i?Ul-3LLY`dLT}$e zyq)K24qvUmb_nX1O9ANq)9xc@Z-}2Sm8c|P~G_;LHulgC7rcdo?bePx%MJD=4acmRT#+1=;6 z_FihLedA_hpAPbcLP?!XC6n#i_nueW9BZklx!G0sxxTfBC%!QIVwjg`E2)~Pl!?GW XKqe>yFDjf#dFPiQP}H-(7-;$fPd-Op diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/doc2.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/doc2.gif deleted file mode 100644 index a8edddd970b164a1b4f75f9ae974071f98217fab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 604 zcmcJM?N1VM0DuofDa2yrTI*F>FH&jtj%ls5DZQ<=^+JZ;^`TE~{Rxh(wwmI=&8^m_ z2Go~$U#nSP3}+fA*AZaIOc3!n26x>B-W?G9%KnF*f8g1(r}tuyrv07*Qa}U(+3j|R z!yyPlHk&P#O2uNaTrP7QSE*F0)hfd`rw z!G!=CP9&0KIydxsC6QR6>7O*kd|Cv8P~p63e{pnn6fK*;Y8Qxt{cb13=^ z!y;eP-~|dMe}GrZe?xM~rL@=Ubvm6MkH_tHyId~t59oiQDoR8Ugg}SZBx_F~>9_$I zc=ULn|B1e@4>8__P3BQ}cm%GMY~Fr&m-<)_tT!SeSX_HVaqvLrrPD7hw##C1$JV+Q zU6adbleX_^+_0_g&W?`=1UKy7dFtlXjS>yqZ~4-7;>N8j%CpCN!*lDK&u=<5_W%F@ diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/doc3.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/doc3.gif deleted file mode 100644 index ec577f78b5cf98b372ef73ce4054082e3557a0bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 616 zcmZ?wbhEHb6krfwc$UYocJ11)U%&qR`Sbbn=MNt~eEIU_-@kwV{rmUv(=eNKYsjp{rdIWw{O3E`Qn|_{pQV^BPUOuJa+uno$Gsd?|%O5+2?QHFI+gk^Wo1A zU%nqcdScsyA2pMsOz8`t9_|6EB`W z|NQy$-MjauF5LF?+4JX5pFMft7 zX3e^F>o#oIuyNzY_3PLF2LlGuf#OdVMh1pF1|5)AP@FKZA8p9XX>Q5MYR^tj&+Ey| z?Vm6)x4So&i9aq-m6?^#IXAl}k0IA5*hO0-G@RRb!J;Km%)A_YoYsuI>wA_s3JGh5 z38pe~?9R)`Wld&`h>>y^kIl_mbkamw-dsjK$V~FeHFg#ce@ib}HD9BLJsE6n4q7?} zX#%c_Z%*lpUt8tYQ4U*TLd(dL^v3%0X+3ir2qf` diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/doc4.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/doc4.gif deleted file mode 100644 index d9dbaecfd70755c9172fe072148bfc3b0303aee4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 597 zcmZ?wbhEHb6krfwcoxU7cJ1046|xvN(nKYr}(>~iMPg(=gzo;`cEd(WN?t2QiJ zv}DTmqdWHOELiqx|DJto*RC&G`EK^)uEK?ncW>Ku@!W~L^rE9DPhY)!`O>9J%}dTK zUA44-^KGxhjKlj59X@#Q(W6JX@ri5Kt=YPD$Fytz&s{jTeCe{ohYl@Yx?s(^br&w4 zKYaAq)yr4cuUoh0z=8AU&)>fP@bKZ|SFc_@fBO9C(`O2ozUV*sx2Lyt$JXr^&Yin> zqQY&TrkY{rJga-@bjTTJe0#h7D)WoZ7T$)0#Cv z8`f>uuwmoIjqBI1{|^QXvUi!_!&R#M_$BO8@F9&LH0QV*q6aWAK diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/doc5.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/doc5.gif deleted file mode 100644 index aa4c644d632304e4c5c6b726aba0fbf9c91a0d2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 609 zcmZ?wbhEHb6krfwc;>~hcJ11)U%&qP^=rwJB|m=r`1bAF@87@QzJ0rN>C&G+f4+bJ z{=h!6vIT`Npi2s_Vlvt8j5g%7RJ9+l>=?VMH zn|!2gy&2f57|h!Q8q(6vpFe+KllI4tAOEitol;pjXXebC>O$`s7^b6p5|36~*{Q2{;JpHFnpFUw`dGX@KEiK(EmoL43{d!BP=iEe{Tio2AK7BG= zD^cpIG1I}}(W6J3Hf>t7X3h8S-`A~Mw|Md5jT<+vU%!6Ch7AV}9N52q|9>!G7~DYd zCkrD3gBODi$eExxVPN0a;N{xf;_BA!VsG!&tVWMYuuP5_#j+y-Y+T1QZUJRbj zyzct$hVFLm?DKmTIkS3Ld9bf>XI!^%v7-m02bcRn$6XGK_bYiYxi4jOTX4*o<&=ts zpsBm#`5p%z=e;_XCgxHSoVUFk`0jhys*4y{YiYi4VB6>+V5FufCo6W8<-u=*#FlO@ z8I>1I6A})XNQ;|2crfwc0VgJA2Zfm%4|sHnF?KW-g)}%YF*5O3Bz)+YB&2ANDCD%H JBT#|C8UTvG(+mIr diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/docPic.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/docPic.gif deleted file mode 100644 index 3985e9d78677e953101b03cbe2269029c7dec042..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 607 zcmZ?wbhEHb6krfwc$UkscJ10@88d#KWEK( z__Uzg7=g+66hxq$@WY2ljef;0kr%#WbJbB^5`RKTmtl9VX?Amkx{{6!T5ANT7 zz}4I_*U4|{mH*G4J=?Nj12?Bc=A0)x_wMameca0}`0C}$j~_oiec^2OyvJuxom#YL zNq%Wl%i%vw4fzGjU*{G?Ihz|MBzdp8_cz1U+t=UK&D>?}x;4eC-j=U>w{`1|Sy%sO z&axc6=)+j9P^>?leh0b zTyo=K*{=V^Yd-GTdtm*#b?2^LJ$~}ow{PD{*M2&G`uy^x%QkJ=v}Vnkb?eq`*sx*a z#*OROum2AQ3}gYtpDc_F47m(CAl;xiVPN0Wkel7ylAYO}m6n#H25SH()F(dx diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/folder.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/folder.gif deleted file mode 100644 index 4d4fdc8e92ec1bd25e16c2add42610915769b3f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1030 zcmZ?wbhEHb6krfw_}iFjsqyRczFYgF@0_jp z@@nRV?Y`%>cph2lab-v78y=j@KeEE* z=a+3a_k>FbbWr>bz*(+!*kh>PsKkxQ~KxEi?{dN zADzj&d(i*L`s91ZGM-;4xqmY2=RcR>veZCb1~U+n?JrG5GO>!_M3mDi}X+E*|w_Qe0NEu;H;IcVDrM6vv@s$L6vd>HK+E zacDv#W5tpW4#|n`thP>avnsk6`nU{gwJdfjEIfJGkXb-PV1sMB!X{o3iOLG+wG0d~ zFA_2n*qfQm`RA`;6-YR|z*ORqPe8!4h4Xha3Y~c|DcNbEQ{prm5l0J07Pd@<7(JI0 x2M=3KRNj-&;PU0EZHio00~5;;K{l^F9fbjlJVg51Z44w2E$1!hV`OBo1^}mihams} diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/folder_o.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/folder_o.gif deleted file mode 100644 index 4d4fdc8e92ec1bd25e16c2add42610915769b3f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1030 zcmZ?wbhEHb6krfw_}iFjsqyRczFYgF@0_jp z@@nRV?Y`%>cph2lab-v78y=j@KeEE* z=a+3a_k>FbbWr>bz*(+!*kh>PsKkxQ~KxEi?{dN zADzj&d(i*L`s91ZGM-;4xqmY2=RcR>veZCb1~U+n?JrG5GO>!_M3mDi}X+E*|w_Qe0NEu;H;IcVDrM6vv@s$L6vd>HK+E zacDv#W5tpW4#|n`thP>avnsk6`nU{gwJdfjEIfJGkXb-PV1sMB!X{o3iOLG+wG0d~ zFA_2n*qfQm`RA`;6-YR|z*ORqPe8!4h4Xha3Y~c|DcNbEQ{prm5l0J07Pd@<7(JI0 x2M=3KRNj-&;PU0EZHio00~5;;K{l^F9fbjlJVg51Z44w2E$1!hV`OBo1^}mihams} diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/mediaFile.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/mediaFile.gif deleted file mode 100644 index 76485276e04186f6b9f1ec914a419051d0648aa4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 621 zcmd7P+iOyB0KoCzrfX`>ITu}5xvd-J7K|>gQCN}AUDR`4f}Ykt5*v&5;EWjxAwF1{ zFJ6yBR1)*@;1zTzmPmT^9JMqR5ygA4{)#?-!56+fK9{}xk`Cyg00X^VZ@1e!oldvg ztyZh;cDr7$w^}WW#e$+}zu(6&ET7L;Diy6(t5&N6fj~4GbvPVOr!$+)Hk-|2u{anE zN~Kb*R_pb8!{HFkpemKBRQmP1Tn>doVzJok_2zNh>vkiEIg$7#lgV5zS1yMIgF&Cq zhi1{EqbOoBWl$8yapdsOX0xT!X@x?ONF*|uOgI#d#bO?h2g_kU(rK&JDwoRxe!u6d zr(7;K8V$GGol2z&g@Q(-NhXu=c-&|(hQndA*{sv)3ya63Z-yQ6~g+>V!vQS8dC=^H}0npf}^767$ z`U(C_-XLB&DZ0m{o%*PTX!nT|^GmlM@SlBw*P-jHbF9-m>Xoar(557KXN0sUTzN7* z{V?(U!sHDGeUdqK^VUT?aEA2g+6?3A-GwPpef>O%%3?C+$M(;$yY?4L+m8W(^J>er zwX?VQ91!V#Iuj5HO4C>lYW@fs(y9)~o z&zLczqM~Bv%$YnqJV{AO2M-?HxpQYiLc+Oo=l=ZplbxN-&(F`!&VJ_18Ch9bB_*Zk z=xAwaX=Y|-F)^{k#Kb30p48XZ=j7zn)YSC#^(7}KfBN((Jw3g$vQkP)%FD~Ex3@Pd zD=RH6t+TT;BO}Ay+&nio*T~3d-MV$^>gp?2tmx?IxP1BY+qZ8`O-)anII(EaqId7! z#m2^ZdU|SUX>HiBVfO6VqN1XcCQXWpit_RCS-yOEOiav@B}?}1-TU?H*ZBDOy1Kf5 z|Nb#BF#P}jA4m=!p!k!8k%1wAK?md{P@FKZpJ)gO5K9nEbi?UMjlo7e1!TE%)N3-$Q?eV`7X9 zZZF*ro&4Y^_3>d76PJCRq(K8?lc6L_h{z5#1&0P!W*-S@M+fJ2CKj%m9To=^Rm7RN gSOgRvG&QoHw5Sj&Y3OL4%~Pc05a86gz=6RU0QfwsbN~PV diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/mediaMulti.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/mediaMulti.gif deleted file mode 100644 index 28994c8e0c1a07f84de2c65db1bfb0eb4173cfbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 618 zcmZ?wbhEHb6krfwcoxg>{rmS7D^~pZ^XKQ!pFe*5`26|vuV24@|Ngyf*)li3=$N#k zwLA9v_($J+_+-iQ_20gI`}FDajhlC!yd&ybdT-vjck=A{j~_qInl&pVKI`F=XS0{C zd-&w(%9Sghzj(1>
RA559MysmXZNkdn6@AT@r&gU;*N2la3Te<$%uRo_wpMUZ4 z)vI^!^DCO)ef<3G+mC~XPgK@+e)#aAytZ@xqLqG;X)oWt|MKO_+_`h-FIc>4)v5;% zA2)PQnm>Qh+qdt|UATPc@QK2zww^glKY#i5?D@-Q&tCvdnKy4iQ{S{_FJA6FaP;-- zcax{jn?7gJ{Dq6VduJ>Ffko%9T%S5~p@V1GqD70AFJDzr+qr!C^1pxoE?v5G$&w|D z7cc$~1`Kooia%Kx85m+2bU?O&;)H?yctfnE3?pNBdvu76TR?bhKV!_K$pT)^lDvHr zCeN9qt;^9L8y*u8!{?{sZf!Iva?ui|2tF<)1qY!?QHwZY*qMy&CoAt-6dA+J=A#+G z8e?#@|2PY?wq^-*YF1o4 cFms8J`tFpIi=H}7*0PwFbF=bsiUNZ*0P{Z;?*IS* diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/mediaPhoto.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/mediaPhoto.gif deleted file mode 100644 index 99f8285ac7eb4123c51d7732071771599080d3af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 561 zcmZ?wbhEHb6krfwc$UbJyW~~gknh$9+?l1W^#efN*_nGX-Y|3C4-Hxp%@vRT`z zHhpW{`~T<9-&?k9`Trl}3mO5%pDc_F42cXnAoD?S!oc3wkl57R(%RPElF-%N)7uq4 zVdA7o{pvhyEKDY1+z~n!p7H&Pb_}6T4CcZdLCl7W65>TgxOn;EY@Fqc7-KiY`-IC! z2^kCcD{0xg?AKS}ck~c55HNTT{+$L+iPC~6kJ@l%3>lbc; z6z#yhl4^~;Q}&%aeNRZbK-v4GR?zOMh7F#@kKKZ2iR(=G`}gm%l}CR3{4MXW&7$ay zw8ir4H=esSe@dx8tQvG#TyL6H@yq;{{qj0>rdbcP1CND7&2H)3a`*02ZKJwLGY>>h zdn;i$-7jc@n%90A>owjTuRTMiyG1Rl?>*2vd9RH5(!)n?8m2v2u;jRme(&aO=f%|8 z-oE{;;C|@$?>|j_yEVeED40yqi#)0C&>NVsOW$pox${(Mlll6wXL$uu)IzWD3a0vW zzBYB8I&aY-*Oqs3Ce!sI4@XS5>i+!Y_ul>2bRsWSH*C@~X>zQ6Yvnf8%yW^T zWRa*`v5Mzb&$f5&VRMvy&q?S_Pi;DU>-GzuqEp9?U;qC7*TY9|T$|rXsgy;=&S>b^ z=H@p+#dEKUN$d6NkNmpcD7frRs@eDK*?U=wB??|A^;4b(_rFtjS>Lky^Z)<<8AbsO zLO}5+3nK$VFM|%qY*3zH;5f`6%_$wvRLZ`#QB@QAr6#`yIPNXX9yDU<;pXS*J)vEB_&@j zTgDKoZK-DWt+Z&ke-lhH8qBFWUs!u z#GcQ<6@FpnqtdSF=SXJ9D0=&$T?R6Ovo)C`7CH*TB`4pyuPwVd)fbiYOA zp1iEn9vR2H!_znGd9O6Bd%YnnEakBGS~0w=+Q{zLuO&nLEW3JzMo3}T1o0f)f55u|E)|MN|-@ku<^z^Gh zw!i^9dpX+$np4C$L(c5!YHU1KwDw=Ms8i;Qk{L3tduN!LIjz-6J!~@bR?92~hMQSg z8K+&0vh?nim+v;WykB1Wq`m#(omY;9ryVU@`+^I8efp%>&i~`_V@{KmXS}?2D9Q0V z&F3;ZoOs-!_F~@io{87z%&Fg2WxYw>p!>Dk4h_$qAI4MFpHH9eRdx3M=Ubwa8TcBR zYiCD2?CJjh|3AYhphgHN{$ycfV3@?912PPhCm1-6GN^LOcx+hU=o!|;V3{#tqAZ_* z1mBxO%bgfagxKXGlad=$^w=0(gbdX>g%wK`UMey-BuEL1SS45lEOwOSS9u^Hla%N- z+ggudX5oPau3Sx(Q|1UTxb^hdbL2@#I0~>&)34xboFU|Nyo*PN&8_h9@)I-pt%YJ8 zK5B6H%T3y{;zmHKACskkQcL7#=8227TRAigoSvU#R$*GXr_%ATfQY^n)0fW89;Xx< hn`&}6MVdW1orH>fA~v)qD4V%l(a4%~F~Nbs8UUvE5x|I6Ez5xwY4I5;w*X@;8w+>$L<=Sz#=1-=sQ+&7MVv#& z8VNN8uai}sJ3WgZPn|mZ`t?WRdhzbfZzjz=VCt}_xqacYXYch>o`y$FxqJ6%%j(b3 zQ{Uvb?B^Cp5tJ<2tF2Y<8qzy?udKzAU%&oHsx_X_)O8D<_2cJnt)Sh@Rvw9%_T}@J z-wG~!-NWWsxlJufnRHx3MTM%JokpM3us52pHfh8_{hzahNH8#JiK(_;z~oa z>({QiH$8B(tuf7d;MeuWGE{&Kg?}C{53w`sq}_T^UBeaZc)olo;<1T+v=wuyLbP!-Et}_CaqudtG934uNHbm zTxY`d8_(s;XEbzd)AHXbBwg?z*i|p`@UaO^|G|J^6p$DKia%Kx85m|V=z#Qr@&p6N zMFwL|k%-h&;v9-eJTVpl2c_(%hv$ zGu(XBSJk--`Orqw>d diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/newsletter.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/newsletter.gif deleted file mode 100644 index d00a6452b7c5aa77ef13ac4e8efa405271d5cb8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1013 zcmeH`TTfB}0Duo*4Uro2f?Cd`%c|*|r4P1RSu?HKW%*EMFIc&`)q^(Lst2PKW@+lO zmdi|!IF=13h5~xva1m4{V&!a!x)edo6c2}UxSYeK>__y}_Xoc1+rGl{1-Zw{SO5#~ z2>_qZkK?$@wdr=FeSLky!%#RJ@%#Pkx&=QO_INN1!%-9qg~B-Q9U2-^D3o5*9gaj! zRGT0OA_$Toh>eZS40+&G^<;m)%wn0Eowas>pj2-MLD1n?@dkpi!ugdIXA?NB)9JVp zhgPe7J8H}9bgI?x75@0O8q@fM4OYW{WKW*!JwzCHBbj#$^=)QYqa{NT%txT8N2O4# z|Jrb)s8}pcZeQm#EX37VXJ)=tYVf(ad4s_ScC@QhYLQ5^xrw-3NH7#0l*^UMA*E93 z_4*`|-cSAg_g_ufZ1x1qB#Fpy+WDwWo^z`eb_1Q`?xpvqB9 zCL2(x)uCW0w#FI=5RgiR5#il!Ya~e)Jf6S;q1J9mkzi7z(dIujCv>=UI(_Gd&o=}n z1aaeD-}16Osm0ONCE8e9<<*VpM@H&i8wTZyk6&D7vza9eL6FLgAh%>rjYbps3-F(R zSb!6Vq5yyZ$k=jjJps_a0x{7k=cHT?FXpvS&QMv0ZlBcy^b~ef$s#beJ&_kLVNRE8-*GNAt^=U4=?$HBl}hF37rr>a z%|RZrw#Dp7PLIiIEf!no6e@Mebc8bU{-9uh3f`kg`0OgKGTQcYf?AdzzP2ffNji)Ux&rg{{8y=|Iy@ktLp#XXk45Wes-Rl7GLv^C_1-Ili(PyPM=?fJRW|9gxsE}j4T{fn!)+W&8KA90cW^Lp3g1H0a4ss7uY z{P)kFzdyd6iPHODtNgRw^v{Q9&*!y%-rx3ZUE-VlTi>myJ>FCM<@%++Qyq^MM7}z8 z^me%9$2A52|NmzgltA$(3nK%AFM|%qb)Yz5V9#psZE9|5ZEMlf=5B9MQZzH_X%=)9 zah%j7>trRWF@sgplts(N-AlOLT%CznNx?N6}Lq|Z(PQj#I-r0v;!h=^xx!pn1 oSYOPL&&_%UqrEpj!weToTZ^ZzRp*7#?#nT5{rUdmCSzg^(aAjlBnjW9FT${_A3g5nd_T|OtJv%neY4)35 zqV{x8@q}#YM~9m3Ts=O$K>60z;xBJ5@7%QT@sXClfBw`(2_9dT^6%fjvM{kpB_{J5 ztPjkKT|A@Z{MwAh9H&S7%ip|w{N>HPH%BYW6O3-`DD2Hqzq}^l;_CDl&mR8!`}^pD z?VG0r-Q8ZaYgW|W+0m!hWUpJjY(=O0t<4#Ke|PSe*X>sIypb`_fM+P3!R^(nQ^w~BgaOv!Z z!;2D6>|6il)$`&Qy*<;TdkQSy-QRU!Zp`;Lmk-Vf`1SSG&yNp!YNBexxnAE~{pi8H zWCxZVQ$mi<_ixNFyuZ8d*}lU2M;gA~?7evU(5A^D>n8`izOv~0rt+(+Q?Bo;**_=d z=CNfzzr0=5?Rjo>{LlL{*351H_v_Q|FOM#6E?C{|)n8({Y<6>Bf$`KbSJjgj)e6qn)g9xQ#&4Si@Vhm2~-kyJtk&(d~05x@$2LJ#7 diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/package.png b/src/Umbraco.Web.UI/umbraco/images/umbraco/package.png deleted file mode 100644 index 70eb21db5f7df78ecde24f950964de677b235f5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 810 zcmV+_1J(SAP)dnTEgWG0tM zlQg9yKmn`P76xeW673$IiLUY+lMKo@IOu88fNcYfHkrgL8k$YR&+C^DZewWME|-HK`W9i zKIDm1MO4eOS*yZ0dKT-WhjH%Gl|O*LjZsfPVU%m^q?S(J)N}bWs@L5Vt=1_7{YOyp z4ehUhu@0S80|k(39}P!L+q!I;{Hn93kTEiObekUZRN9J^MyEQ9pQk+4ty-q>7=G`G zl;I1C>}~^8r+(K~$y@n}L(mKhq3=U1%!7aU45e#O`^Vr8Ws%u6n(Nofx62==Z%C(b zkwT}p0b!?+b~EEJ!zRK?8O(?wnvTGkC;^rQY1@!q4b0ns&1Ah3t-v$`oYQ990g5Mps6}2agE=Wu z;-KC3#6Jq6GKjgPVi>XuFTmSEKprEE>Po&>imW0Q7`=Z}n?sq5b&TnMB?_U&!0KGh8Ac0>Ggle*^Wo z=O`~!u)5qpJuE3VSNJ&qFs{#ph|c_Y|K+}zXOl~NN~d#ibuAqaOMz@)d8vwKFVSMj z;+I8cgvG@3#savl!(=4Zk ohE~?=HOf%_DWLx?=idSh0Ow~g#)&c=5&!@I07*qoM6N<$f;SpxlmGw# diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/repository.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/repository.gif deleted file mode 100644 index 35fd5a8f815ca2151ea999ec269cb9738d5014d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1062 zcmdUu?N1s90EbUU9lDp&+I25o*Ezp+W@$RJxb%z7YNt(>C5tt+OAR)3?Up9hwOdT9 zs|e*q6QeDglNS{Pjyvw)pq@bC0C|(+*oIbPw*nq1b1>#yGFOUnbnX6&J>P%H^ZW38 z*mAt#{Z0Ca?yK7Y`JVIeXSCc_UO<{{&gsY$L-Ac$EkmL`+QOeQ<2(Qwc5Btt`YoP<<5 zct4RyrGB+LT?|v~z1pYKgM@ERp_m9qA{Rct7^i8Hq6P#a0|fK=!gxHPoS4Kgi&Cjt zT3WWdh&wLgmh4-lYBHTpU+cTRy}iA$v9Z4X=M9l$@$RxvEKbri!!UPai;Ih~OWl|I zuM0rfn26IL1arA(=OfWh-g&#j+1-8F7YO)*p_{k9N6cm&2uGuo2{YYK(h{)*v(A)? z%*d$BfEcHyrkyUrX0uIewPuU8>&q)<3l>i#l1W;wP-QYXolcLMEI-C#KgAZ3tE<^; zHlNR*Jk<^wVAzNnO_<&Qha(hIVmxy`i`A-9sY|7jKS=UEyJ$9HgvTu%9QK6*#bVLs za4ak=P!Y->@H0gQLeRq2Bi!LySy|x=gmT4{**b&I%;5N}#~%a@kj-JI=@e?lA{2ES z$GM&7W^Fc&Ru4kPT<-UiZS4d>xCpmwO#UGKAP}54K*(G$G+KtWQPXdk3}&&0!=w%}kdbI_f4_K8A|F@GlcZX&^Y{ag z{{#5{Q&V7-0e~B*dwS(*69D@$&{w(nm3LlW0mX|w?{;zd?^!uJthHRKtiHCcp^jIv zG;;XFY3?}B+bDasoXJ>m&{y^HM+cYHO&5Nse()?bcga7++4$6CR?DlPqQY-ms(o))^K|0-Mh|v_v|k}Dty#Mil8@} z-U>JPDx=Twj{x>60gH`%x(`rRaXuLJcI=<}OWo*W@2hAF9ZJQFW9GGE9skUYh(}** Y6qbk8;RMzx4_<(UJoaDXHK0`8p$X#fBK diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingAgent.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingAgent.gif deleted file mode 100644 index 8d726fcdf47c6816f65f58aacc3cf280ca84f0a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 145 zcmZ?wbhEHb6krfw*vtR||Ns9tPMi7k!!P5RXTEC~~N?6j*31A{dH73wmh diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingCss.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingCss.gif deleted file mode 100644 index c2c7af9670addf7168f5a0263d154a496e8c424b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 607 zcmZ?wbhEHb6krfwc;?EmcJ10vpFaKm{rmg(?;k#V`1bAF%a6H{kr2Q^jIb?et}+qSK(tt~S%3kc@V zpKoerDkvx{C@7?=sj^_f0tW{NA8+5p#Kh~@ub)4EZr_3Z7cX9{s;ato@80w0&)>Xx z6A>Q9!^dxLZ@+5QDrX1h8#iwB_xDYmIyERT(ALKK>eZ{x&dzahaX)_i*tBWW&!0cn ztXcE#-@kS1*8Tna_t&pq8#ZiMzkdD3jT`@i0mG02ia%Kx85mp{bU>~K#R&uZo(5N^ z<`yT%c4ucRD_2)LmkARmP3Y-!v36j$x3`_Z&hFye%iv-;gNbR*q(!bvm@Neq9GKYW zPFS;qm08DR`JB0HY+U%X6%80?ILvl&>^;O|tR<*#X(p+xa^{>Zqn?1MFte(flKCxH z8+$`3PH|mcbyG1BhqEs#ttD>fz=2^5x4fU%tG3`}W?wd;R@=H*VZ`{ra`3nQ2g9 z;PdCtKY#xG=FJ-)Z{Jm`R{i?*Yr%pARaI4SadC->i6$ndf`Y=CnOQu1{O8Y~v$wYw z6cn02fBw{|Q@3r~77-qH{rdI)|Nk=(2NZv@FfuS0G3bDlgZ#w6=HW22z(Yr>|HP6a zO%VncpGbyni9t4f?3*7y(s5|wSS4bt7RJz|xj?E$#LZ{nC$Cf~HV$Q%EgmMq$~r3I R>>TR4fnD7_z3z?-)&MEOaj5_R diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingDataTypeChild.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingDataTypeChild.gif deleted file mode 100644 index cc557f784bfb2e4908d821fc9602fe1f7a94dfb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 144 zcmZ?wbhEHb6krfw*vtR||Ns9tPMi7k!!L$ipVnQp`2O|yub&V9&jg8s+11~>e8r0Hp8ln)R?eHhFgv$s`N~z#odfg`bLSm7di21dLzk{zU9@P) z%a<>6^NSWN1TvR4H?`I^HXl2Aa@&sW6DLoNiBAj;k2-z!{Nck#jvYU~Z26LuwCp8I zftIh!&o4ZE=JeV%YmT2hed_e-^o;BkE0;fi{(RQ#`91x8=gyt)?wzoHsJo$N>(ygOd(;{t**^M;SUHqSpCx-7IiTzYDX=Z JBgw&F4FJ<48@&Jk diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingDomain.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingDomain.gif deleted file mode 100644 index 8612effcad6902c532ef5e3dd893bfb865283e90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1034 zcmZ?wbhEHb6krfw_&%56@87>wRaJ3uakFO5xcA_}ym|9}{Qf&_+QfpqTw7|YMOAuQin2L#a&p@Bs}~(8IH-}H`{QC8K zXM4+!_x~O~eBu>qoSm7re(k!=dlzJVhChRLRE76%xGJUUc)+_*Xt7A3eaGBQbAFt~E^e4}W?xeSek%uY)BLDQB9 zaA>+QInIu&@pzmTtSUOAjN^}Rx>ujF>;Z*EE{SKPjV7^NYuvcfSKe2MOYoM<61Ryw zbFawknxN@FSwLmij|t69=LFy)F(}1XJiUV*qG|fZ{5T4>B*7e$=rT(#B5)R I2ryU!0NJ*LTL1t6 diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingLanguage.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingLanguage.gif deleted file mode 100644 index cce48e2e6e38e0f146fb9b51bcf4bec2e03d375c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 387 zcmZ?wbhEHb6krfwxN6Ms`Saz=m+#-d{QUX(?_Z$6hj;JZ{rK_m`?t?;-@g6+{llL> z-@bhL@bl-JUq9dc{Q2$2kN4lczW@65-Ip)#K7ao3F{ra_T-MYlY#0?uZY}~lf*4B3Y`t|?8fPo00_>+Z^fx(zT2c#C{CkD0!2O@Jk zdUp2di)0;3IXUmbikzEMmWJ^+DtxRt5qBoifo+EnN3_Na%eX1w8xAHj{cbxxo#W)? z&ckYDK3Ybsx@|H-g;GX(iOsqSl7jLEQ<;1EIF)r-xkSVagw$pq86-}z?LWk|+mA0_J0m7IQRm@9 L7Y{EVM+R#E3x~%i diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingMasterDatatype.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingMasterDatatype.gif deleted file mode 100644 index 2ac4ddbc683a2847f940c4d967320f87913f0e9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 587 zcmZ?wbhEHb6krfwc$UNP|9Jk58#m_6n}7Vo$=P$~9X)#V)alc+=FYo(_1dMYS0_%M zdhhPTt2b^mx3;a{xT&e7<@W76ihFE<;K9t9bCxY%vUJtT=g*%XKY9A@-Mg`|@%QfCJ8*(q4n?8N! znzd_>9zA~g?D-ovZr0T|-MMowB`y2fwHw!N+`M$@@}@0YyL%_hoxkApnbW7voSrv- zVO)ID{sV_|^NZ3mvR}M>v0=le!-tPNd-iP7i^NS|NsA2Ro7j;dNnV<;M}?Mn>TNbiBCLs{P@njySw}P=FFWpea5Wp+@jyVe|Puv zuUxh6=&|E#*Q_~m^ytf%FKer6XU(3!eC4WbJGO7zw!N;g`RUWA`T2z_RxV$%Wa)`} zuNN#>2sEF8T%h=qg^__Fhd~Eq04Pov*jF^<-6|9&nIC+dYz9_z7%nELsV|-3;=I?uBs)IE&QK!2-rvjg vmXN7{vZMD*EzzD7gClMp!aCeh1s29mEY4=ECNnrTC;5n}i953~F<1ite~cB* diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingMasterTemplate.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingMasterTemplate.gif deleted file mode 100644 index 6c79a948fef3ad6b4dc922ccf7c72f9e6e78e8e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 235 zcmZ?wbhEHb6krfwI3mjM>({UK>(~GI@nijr8NYx3Ub}Yf&!0bk-@g6p)~&yP|Ni^; z@7uR;>(;H?K7YZ-OP989-|_$6y*1OOty#0?zO|oltOOlFME@`<% z#1jw&4~`xbA-3fZxkQ1Ah>9phY3Wu-lL)m3ZG0PP@rU)F^J913yl=kwX5Z|amXsK) z!XrQgs3Aa6QBiYqv&;1Wa2DXJ2kx}Yz4sd%8-dT;+S&@Z>bABvP+JGQKER!8X=wpf zesI?hJpS2C1*r80Tn`EyK2San)cXTD_a4$$91dT=;RV(HrluyiTbOYiT=xU+4&zSq z@>OmGAOJeG9#B#So$Z{Z*nfoD_YoMWf(}W`n0zw=k zMq(sFiA=6kDx(ppSRxjQr6P$`AW^DiQngr&JTeqkvzr#HleLMDYQdET&0<+vEfP5# z4xvLPr0h#X5|v8Db4aBEh!8L?8;d&yHfD4?gO+3nyP0Oqlnvn-aU+$(YEY>3?-s0d z2d#|}2HO!5lvU)!X^})I=38n9>U94NwOTvS44X_o*88u-Osb0}Mad*X<=6=l!bkI^ zXfw%~!CAM% zq3#lBYkk<#+|>AI!-M;O_g^@2-A( z<(taOmo9$&)rBw5pF4Zz^cSZ-KY8N#v5NAt&)i3ke0sR_P)Tu7;lTs@_kHs5M<4Fp z^TGSO-`llw$Gh*m{nndrY=3>*Yg=F4vU$_S4eQqxT53x2+&Qyn>1NJ&Hfj2_#Hm^= zA%4o_Nt(EcYE`T9WdmPcf+TD=Beixb2mgN4g(gwRt8LZbWj&UZ=C?%VVm@*=$v$jMcq TVUbDxQ(MU>asGgAAz diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingTemplate.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingTemplate.gif deleted file mode 100644 index d84b0243c1b62c2627436f93ed8865552d2cdda3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 252 zcmZ?wbhEHb6krfwI3mHYcJ10$zkU05e2gXTDfvN( njQ;L9c@54iX_NdW2kY~&a56HovN0Q)@-JGXqM@au$Y2csmLhXu diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingView.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingView.gif deleted file mode 100644 index 74f64b87ce1b8b8dd729545eaaac8aad66fa0f1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1644 zcmZ?wbhEHb6krfwc;?5jcJ12VfByXY_wUD#AK!oe`2G8LM)j1xfB&XcPs?wfpHVwC zziCER-PFH-|NQ^|IliR-`_J$F^S1o||1G(E;{X45z5&7aSGVq;-L-4i{^OSqp1$$r z+n4mJ$?raX`TzfILG$dc8LOr*-BHjqJEdYuYQ>bK@`;(X)AE{T?>%;A*RI{UjWfS| z`<_}g^~bN@x9(s2|Nq70g&Wgrre@U4xPSlt`D=HR$|nB${rms_55Io>e)Qv4q}24xJX@vryZ0+8WTx0E zg`4^s_!c;)W@LI)6{QAO`Gq7`WhYyvDB0U7*i=|m)9WTXpJp<7&;SCUwvn^&w1Gr=XbIJqdZpd>RtPXT0NVp4u-iLDaQr4TRV7Ql_o zE7k*hM=v=)SHB{$K;KZ$0OTc@LSJ9}N^^7Js*6j4QW5UOYH)E#WkITbP-=00X;E@2 zP`NV5ssbzLqSVBa{GyQj{2W*)24v)yt$x9Sh)g$qp7K}fu*U7p`ojhqlLMFrK_uvi?OMhvy-tSOs`9Ra%paA zUI|QZ3PP_5PQ9RnkXrz>*(J3ovn(~mttdZN0qkL`Ox$iU#AzN>ZwhX=7~#~b4|I$^ zC|Z%C872fwJ0K=J(E>T}WS^P`OzTC!M9ujB-(O%h|M}zlx36D5fBN|0{kyksUcY+z z;`y_uPaZ#d_~8D%yLWEix_RUJwX0VyU%GhV{JFDdPMZ`!zF{kpYlR+RD-nPikb&{kYr=x+;1h3v*zS4?}jAkrS3Wts``Kq&+$ZM&Jshr=*?yJLRWNxb_ z$8p=0)tAFYSwPm5*KooPR~KI;U29=}4Lh07N8bDm*wDlwD5I~zu*7shr+_#&k3>R& fqR={bQMMNb5f2FjxZsLGEb) diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingXML.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingXML.gif deleted file mode 100644 index b7b1e5ca967c6a451ac72c7ed12b20f0615ec26e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 268 zcmZ?wbhEHb6krfwI3mrkX3d(QprB2gHmzN|_S?5_U%!6+@ZrPz_wWDw`Sb4GySH!O ze*F0H-@kvKK7IQ8_wV=b-+%x9y>8vQ&!0bk`SRuG&z~DMY}mMQ({UU zV8DO`6o0ZXGB8Lp=zzpPb~3PbK4|Mz37b1jAtz_a-hwr2*Se*4JT}l@6uIWCL-9^W z1#5{3rZpnQk{nInx%oK#x(s*TVKA9|NV$O1MRu0g2kn_Ga(>1td(%vAH8TBlOJ|hf zE*DN@ajlILU~3f0O%e~SlVtDWZHk!SB*HnFyIYuL2D3QVY_=&vtTXgl&C^3nqy;x^ MQqj=TQDm?N01bnD$p8QV diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/settingsScript.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/settingsScript.gif deleted file mode 100644 index 556ac66c30335929e5d98055946a4ff77f1ca191..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 553 zcmdVX`AWiY0KoB|T4{%6_kAC9-}l{bQ1lqRNtgugzy1s&qo5ujLAR!viyYQ469N$= zGmF}En=G47SJYedd4ex|H#XO2=e7*M0A3N0%jINQmLy406q=^}e!nPwW-=MC*Bc6j z7=|H9G9HhMqWHnEhn6Fr=bcWc-ENO35{mL23PfrobdrJ@w z=jD~#?anC*&kJ{VbmTdT<9Hwtcz^%EFf5zRQWO=9MqgrYHkDforGh;)AqOqmyQ3e$URFw`a`~V^z)>i-k diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/sprites.png b/src/Umbraco.Web.UI/umbraco/images/umbraco/sprites.png deleted file mode 100644 index 16aa533cf7c179864a658b9593023fe4a1bbdb86..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14538 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 EKd1buAOHXW diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/statistik.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/statistik.gif deleted file mode 100644 index d526390307f6d019fefd712ab61ea5bfc0e0f976..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 305 zcmZ?wbhEHb6krfwxXR7|1poj4H%^;rJoC)=ug8D=eE5GRNN}hC#h)yU3=HfHIv@vw z{KUZKcc8hzLr0F~#1bJ-5s`TT8jcrwqLyDhcp&Nk-`ra(u4%L6FHC;5UBFD@2JjG|Q-Zcl34HPN!HZ`P4=qaGJ3Hp;#)2ywi*{|_@aA&o z#nXqD&TinzKE;r5`tgyLXZs4zuF1A`X*<6*uQ+cEA4n=g;pikLEP{y}vl|(Sv(8k1d(dloVJiMv*=Kt55y*G9g zPRN#i{O$j%AOD1AJZDTjdvAC7={4DVrboYd`FQW_=v$jJ%<|VBpYMNjcZKx6AFr=0 zdbGd%-ThshMQ1f@E(@Rk|MTMmbEl?ttCw*%-7Ki9L8 z=jw60{x9O|KUi)3e0@v7&P@xyyt$XV;jHM@uch4+vL?*!z4q(+z8Vw#g4LZq`{%^0 zUD(r>YqMryx5ba2b`y^C=byQ}sc`R{*hhz&md$P!So-zJR_0E{{H6jmCc#Aw&&d4 zUbJ@W=1*rDFK#aQ`Q`2Fo2&o*`qWaJet&n}hN~Y$mwjH>=gV1o^}xK?bF1TJHhj6Y zwfM`6(;ZcAHT_+-X?uEe)c^hamtS2S8Ev?EN|3;W7Z7Z#$wAQLnNQS`@>> z3SAQB*v`IXQ^2a^!zjt{%RxY?zXh3DXtDv^_4|V3f)Nc?a>xgQ&4!g@|zI0LU?$euq2jAQBeRMjtQh>>MZp5Ot?%ATQ oZka_B+8Yk?8M8V3WIo>6&&9^O$>MWX*oP+FH3bVy3=|lw0a;tV`Tzg` diff --git a/src/Umbraco.Web.UI/umbraco/images/umbraco/user.gif b/src/Umbraco.Web.UI/umbraco/images/umbraco/user.gif deleted file mode 100644 index 5b67adae9037eda67e1154987f0f2b8202f5f81d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 998 zcmZ?wbhEHb6krfw_}55HNTT{+$L+iPC~6kJ@l%3>lbc; z6z#yhl4^~;Q}&%aeNRZbK-v4GR?zOMh7F#@kKKZ2iR(=G`}gm%l}CR3{4MXW&7$ay zw8ir4H=esSe@dx8tQvG#TyL6H@yq;{{qj0>rdbcP1CND7&2H)3a`*02ZKJwLGY>>h zdn;i$-7jc@n%90A>owjTuRTMiyG1Rl?>*2vd9RH5(!)n?8m2v2u;jRme(&aO=f%|8 z-oE{;;C|@$?>|j_yEVeED40yqi#)0C&>NVsOW$pox${(Mlll6wXL$uu)IzWD3a0vW zzBYB8I&aY-*Oqs3Ce!sI4@XS5>i+!Y_ul>2bRsWSH*C@~X>zQ6Yvnf8%yW^T zWRa*`v5Mzb&$f5&VRMvy&q?S_Pi;DU>-GzuqEp9?U;qC7*TY9|T$|rXsgy;=&S>b^ z=H@p+#dEKUN$d6NkNmpcD7frRs@eDK*?U=wB??|A^;4b(_rFtjS>Lky^Z)<<8AbsO zLO}5+3nK$VFM|%qY*3zH;5f`6%_$wvRLZ`#QB@QAr6#`yIPNXX9yDU<;pXS*J)vEB_&@j zTgDKoZK-DWt+Z&ke-lhH8qBFWUs!u z#GcQ<6@FpnqtdSF=SXJ9D0=&$T?R6Ovo)C`7CH*TB`4pyuPwVd)fbiYOA zp1iEn9vR2H!_znGd9O6Bd%YnnEakBGS~0w=+Q{zLuO&nLEW3JzMo3}T1o0f)f55u|E)|MN|-@ku<^z^Gh zw!i^9dpX+$np4C$L(c5!YHU1KwDw=Ms8i;Qk{L3tduN!LIjz-6J!~@bR?92~hMQSg z8K+&0vh?nim+v;WykB1Wq`m#(omY;9ryVU@`+^I8efp%>&i~`_V@{KmXS}?2D9Q0V z&F3;ZoOs-!_F~@io{87z%&Fg2WxYw>p!>Dk4h_$qAI4MFpHH9eRdx3M=Ubwa8TcBR zYiCD2?CJjh|3AYhphgHN{$ycfV3@?912PPhCm1-6GN^LOcx+hU=o!|;V3{#tqAZ_* z1mBxO%bgfagxKXGlad=$^w=0(gbdX>g%wK`UMey-BuEL1SS45lEOwOSS9u^Hla%N- z+ggudX5oPau3Sx(Q|1UTxb^hdbL2@#I0~>&)34xboFU|Nyo*PN&8_h9@)I-pt%YJ8 zK5B6H%T3y{;zmHKACskkQcL7#=8227TRAigoSvU#R$*GXr_%ATfQY^n)0fW89;Xx< hn`&}6MVdW1orH>fA~v)qD4V%l(a4%~F~Nbs8UUvE5x|I6Ez5xwY4I5;w*X@;8w+>$L<=Sz#=1-=sQ+&7MVv#& z8VNN8uai}sJ3WgZPn|mZ`t?WRdhzbfZzjz=VCt}_xqacYXYch>o`y$FxqJ6%%j(b3 zQ{Uvb?B^Cp5tJ<2tF2Y<8qzy?udKzAU%&oHsx_X_)O8D<_2cJnt)Sh@Rvw9%_T}@J z-wG~!-NWWsxlJufnRHx3MTM%JokpM3us52pHfh8_{hzahNH8#JiK(_;z~oa z>({QiH$8B(tuf7d;MeuWGE{&Kg?}C{53w`sq}_T^UBeaZc)olo;<1T+v=wuyLbP!-Et}_CaqudtG934uNHbm zTxY`d8_(s;XEbzd)AHXbBwg?z*i|p`@UaO^|G|J^6p$DKia%Kx85m|V=z#Qr@&p6N zMFwL|k%-h&;v9-eJTVpl2c_(%hv$ zGu(XBSJk--`Orqw>d diff --git a/src/Umbraco.Web.UI/umbraco/images/umbracoSplash.png b/src/Umbraco.Web.UI/umbraco/images/umbracoSplash.png deleted file mode 100644 index 0d03a6e1dc6e41f4934d708c230a2e2ce47bde87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3134 zcma)8XH*kNyAHjJ5GjdZG)QRbCRA5JdR;>ah@pstfCvNwNL@k+k}RPJfha+m5p*e{ zpb{ViDUqTeSO6nUq=lwI6p#h|(?OYpDnaud4vS zKF+T$)QhhQ6l{W{ZPC&0_)F1on5ZCtD<*u}p-=2&i|qFHr4LLp>n?m5fLaPs6AJxiEoA{J%|TX8d4TMB7&W*GA;^ zq?XN^(<5&TMXxJ7z>ZI7$tUczE2GF13>OB?{d>oqS8ThU1p zg2@y!!MT)d*B7Vj-s`ol9KYn+mB~_^@>yJ$u>zvRzT7<%f9Q{@8*$C;mlZgOvjZK* z3PS9?i|^xtmO{_0rcP!=f?&q}#j1K`_%r9qk>|^pHt-TTpYBCjph!J3TWZKeJ=BYZ zqJh6eJayAv@TY$vheHYM*xG-1VlPH%+N8(HS#%1fM~O*Ui(DP_luiMSsD>t4pY4Iq zN{rjko2MS2n7hz1#+k2S_z#5vPGpc47uF?ow^n)ReA%t53SABOHu?=rj}(&<5yAF^ zqu0J>DSsX^R|Xmk#0${s`?bcbJuerN-O&sC)atpC-9EiHV8vaXnPBbLlD4E(NINd!o=`o4^|^wN?b-AlD)0e%{{R zXfzsyTJMyAL$Yk+8Z5M}Px@kgGa4@d{+nbr0dHdN(RnH8v+c~B4wprH& zt)@m7yg5*0aMbmHSlaS(=!hfbq@8S!yuIOp!npEi%niA17^#J)Vh@W{v! zjWPOuOtGX~-1zIx91~I5tq`|^gFPzI|Dvzhl)3;UJ=%sb;DLMpY*hKw$MOBB>*hA} z({U&An?LnG543|cSV}xnmG95Ek4u6GPDMBFf zhz=-8^l|Wz-Ju^*^Eb{z<%M(*xUew25R16DCgzVLtuvW=Ct6qa)%tU@Tjqjp8|mms zrOa2y8p0g7Fe{GR*XB?12Et@l|Jk)Bdx7^NJ7H5}_rmgz7>VEadQ4un3OjH-NNh~&{ag$7U*Em0f zW?ajo_!{9*1VZ8T_07%L45DwOho@%%AK>NUBEPP^@Z^c%@}0(mYVL|Z#FBh`4l?1~ zNDmK>X}Y1_`3Fbk&Zu}d2zNzCMbd8Eu+@jf3lLjRCg+ClZ3tOw%NkXXb{@-DKdjwO zec(tQ{?7J`gr}|HtRC!kQZ?+gTS7;4sq5P;7V9zt5j?2o#eD;d`1;OFnz3Yo#cBjq zB5(WZV5QGyvi!@TC_lWDleA_NUPV4T23_a*=g%~;?xnYX+m8OUNWZCy&dxXhG{gZJ zjHh`aKBtfSOOhCuo2m9PQ8*$x;Y(U8`vGAsXeW zPmj&r6;{J0rJHW*K)Xfd#%(TF4F!*l?rQ(f)xW7k-2N3Ow?>NfU;Esmjv2e5(TkAT zuH?>PSZHYIJ(qH4u)5&QU+6NR1MEB6v|(bK{Kg~nF;>cfn^t|;t)qiQw|~`8%=jd8 zqFUfm!kNSkYZmNnM2haQj*XACEEoudwaDh$I66{NvQK^&3(5|kDUF+A^yVElGE&+p z-Q6Xu8fhE6fvrWtPiw|JVKPw<1u><9$+&>7bg7vVDKL}rD&ncvmHw6!|Al;Q*M(MO zdC}#aRwcq>EPa!-*xe+x?Z>=xCr!V{Sm@$yPn^k|2C8L`YD~NDcQ{|)~Oe&;&Ek&9RtCdzq2U9g>2IKxGp$w@Ta0E!>oj_v zJLe{T4I5En+6 z82;xKjmq%c*WScQn=!YyD_9DMSPA#DCE%qcSe~Hkd&Q2}vq?#2N+KOFa}5pb6g%PD z9K_~1Ow#am^vrInhU&PDmlvpU28D0mi;Y+NlrLF_>;OOca6^zGXvzQ`A>u>1bqoAv zcyu%$V3GQI?csjjEwOz-QylO}#Z&f`=}w5K>~@CGH1|Ea4tKF`SCg~ZY^!H2PK@;B zU;N%B95GxN5;T^|m!uxizCbI|mXxc)usCtCvC`EGjq`)0r){0EhW$5>19jSwu8LyS zjZ)r~{t=-gX{XHNoC(L33{6bj51oOc!1q0C341z4h9Qb=UA_$gq*vSToA3~`zF{KU z^*f>1I9%~P|54DEJG>&T5CyN#Px;6NFk{aC+truK0=}h?%2&L|WJb9~7iH zAWX_k42g~1?7Sx7HjWC_ zfK4a#&a}l2R!-PQHk(X=Ul6d&<}m?jvhb+sxASvz)A^Sze5bz5_Szl*&)>LO$Yoc+4z33Cu=Ix`~6F^6&g+se! z8{4+(ye5kF6TVF*wz1lWPji1G`_omgg+q1YSwp9$;$M5$KZ$$4Wta8zK1BZMT4L~9 z666@-Ul|!QQL5GOEH$jkXz1{?%t=yi#w9W(;s2|0|F Date: Thu, 24 Mar 2016 12:43:51 +0100 Subject: [PATCH 025/101] Removing unused member usercontrols and master layouts. --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 3 - .../umbraco/members/MemberSearch.ascx | 40 ------- .../umbraco/members/ViewMembers.aspx | 41 ------- .../umbraco/members/search.aspx | 13 --- src/Umbraco.Web/Umbraco.Web.csproj | 26 ----- .../umbraco/members/MemberSearch.ascx | 40 ------- .../umbraco/members/MemberSearch.ascx.cs | 100 ----------------- .../members/MemberSearch.ascx.designer.cs | 51 --------- .../umbraco/members/ViewMembers.aspx | 41 ------- .../umbraco/members/ViewMembers.aspx.cs | 103 ------------------ .../members/ViewMembers.aspx.designer.cs | 42 ------- .../umbraco/members/search.aspx | 13 --- .../umbraco/members/search.aspx.cs | 25 ----- .../umbraco/members/search.aspx.designer.cs | 24 ---- 14 files changed, 562 deletions(-) delete mode 100644 src/Umbraco.Web.UI/umbraco/members/MemberSearch.ascx delete mode 100644 src/Umbraco.Web.UI/umbraco/members/ViewMembers.aspx delete mode 100644 src/Umbraco.Web.UI/umbraco/members/search.aspx delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/members/MemberSearch.ascx delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/members/MemberSearch.ascx.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/members/MemberSearch.ascx.designer.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/members/ViewMembers.aspx delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/members/ViewMembers.aspx.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/members/ViewMembers.aspx.designer.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/members/search.aspx delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/members/search.aspx.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/members/search.aspx.designer.cs diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 705282fd97..7cfeb4de7c 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -668,7 +668,6 @@ - @@ -727,7 +726,6 @@ - @@ -758,7 +756,6 @@ Designer - Designer diff --git a/src/Umbraco.Web.UI/umbraco/members/MemberSearch.ascx b/src/Umbraco.Web.UI/umbraco/members/MemberSearch.ascx deleted file mode 100644 index e1c6f6e664..0000000000 --- a/src/Umbraco.Web.UI/umbraco/members/MemberSearch.ascx +++ /dev/null @@ -1,40 +0,0 @@ -<%@ Control Language="C#" AutoEventWireup="True" CodeBehind="MemberSearch.ascx.cs" Inherits="umbraco.presentation.umbraco.members.MemberSearch" %> -<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> -

diff --git a/src/Umbraco.Web.UI/umbraco/members/ViewMembers.aspx b/src/Umbraco.Web.UI/umbraco/members/ViewMembers.aspx deleted file mode 100644 index 3dd8012118..0000000000 --- a/src/Umbraco.Web.UI/umbraco/members/ViewMembers.aspx +++ /dev/null @@ -1,41 +0,0 @@ -<%@ Page Title="" Language="C#" MasterPageFile="../masterpages/umbracoPage.Master" AutoEventWireup="true" CodeBehind="ViewMembers.aspx.cs" Inherits="umbraco.presentation.members.ViewMembers" %> - -<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
<%= Services.TextService.Localize("name") %><%= Services.TextService.Localize("email") %><%= Services.TextService.Localize("login") %>
- -
-
-
-
- - diff --git a/src/Umbraco.Web.UI/umbraco/members/search.aspx b/src/Umbraco.Web.UI/umbraco/members/search.aspx deleted file mode 100644 index 2bb21660f0..0000000000 --- a/src/Umbraco.Web.UI/umbraco/members/search.aspx +++ /dev/null @@ -1,13 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="../masterpages/umbracoPage.Master" CodeBehind="search.aspx.cs" Inherits="umbraco.presentation.members.search" %> -<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> -<%@ Register Src="~/umbraco/members/MemberSearch.ascx" TagName="MemberSearch" TagPrefix="umb" %> - - - - - - - - - - diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7dd0f6b41d..24c9350041 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1289,13 +1289,6 @@ Preview.aspx - - MemberSearch.ascx - ASPXCodeBehind - - - MemberSearch.ascx - xsltVisualize.aspx @@ -1460,20 +1453,6 @@ EditMemberGroup.aspx - - search.aspx - ASPXCodeBehind - - - search.aspx - - - ViewMembers.aspx - ASPXCodeBehind - - - ViewMembers.aspx - DictionaryItemList.aspx ASPXCodeBehind @@ -1676,9 +1655,6 @@ ASPXCodeBehind - - ASPXCodeBehind - ASPXCodeBehind @@ -1702,7 +1678,6 @@ - @@ -1728,7 +1703,6 @@ ASPXCodeBehind - Designer diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/members/MemberSearch.ascx b/src/Umbraco.Web/umbraco.presentation/umbraco/members/MemberSearch.ascx deleted file mode 100644 index e1c6f6e664..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/members/MemberSearch.ascx +++ /dev/null @@ -1,40 +0,0 @@ -<%@ Control Language="C#" AutoEventWireup="True" CodeBehind="MemberSearch.ascx.cs" Inherits="umbraco.presentation.umbraco.members.MemberSearch" %> -<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> -
-

Member Search

- " alt="Member Search" class="dashboardIcon" /> -

- -

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
<%= Services.TextService.Localize("name") %><%= Services.TextService.Localize("email") %><%= Services.TextService.Localize("login") %>
<%# Eval("Name") %><%# Eval("Email") %><%# Eval("LoginName") %>
<%# Eval("Name") %><%# Eval("Email") %><%# Eval("LoginName") %>
- -
-
-
diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/members/MemberSearch.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/members/MemberSearch.ascx.cs deleted file mode 100644 index 167311b944..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/members/MemberSearch.ascx.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web.UI; -using Examine; -using Examine.LuceneEngine.SearchCriteria; -using Examine.SearchCriteria; -using umbraco.cms.businesslogic.member; -using System.Web.Security; -using Umbraco.Core.Security; -using Umbraco.Core.Services; -using Umbraco.Web.UI.Controls; - -namespace umbraco.presentation.umbraco.members -{ - public partial class MemberSearch : UmbracoUserControl - { - protected void Page_Load(object sender, EventArgs e) - { - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - - if (provider.IsUmbracoMembershipProvider()) - - ButtonSearch.Text = Services.TextService.Localize("search"); - } - - protected void ButtonSearch_Click(object sender, EventArgs e) - { - resultsPane.Visible = true; - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - if (provider.IsUmbracoMembershipProvider()) - { - var query = searchQuery.Text.ToLower(); - var internalSearcher = ExamineManager.Instance.SearchProviderCollection["InternalMemberSearcher"]; - - if (String.IsNullOrEmpty(query) == false) - { - var criteria = internalSearcher.CreateSearchCriteria("member", BooleanOperation.Or); - var fields = new[] {"id", "__nodeName", "email"}; - var term = new[] {query.ToLower().Escape()}; - var operation = criteria.GroupedOr(fields, term).Compile(); - - var results = internalSearcher.Search(operation) - .Select(x => new MemberSearchResult - { - Id = x["id"], - Name = x["nodeName"], - Email = x["email"], - LoginName = x["loginName"] - }); - rp_members.DataSource = results; - rp_members.DataBind(); - } - else - { - resultsPane.Visible = false; - } - } - else - { - IEnumerable results; - if (searchQuery.Text.Contains("@")) - { - results = from MembershipUser x in provider.FindUsersByEmail(searchQuery.Text) - select - new MemberSearchResult() - { - Id = x.UserName, - Email = x.Email, - LoginName = x.UserName, - Name = x.UserName - }; - } - else - { - results = from MembershipUser x in provider.FindUsersByName(searchQuery.Text + "%") - select - new MemberSearchResult() - { - Id = x.UserName, - Email = x.Email, - LoginName = x.UserName, - Name = x.UserName - }; - } - - rp_members.DataSource = results; - rp_members.DataBind(); - } - } - - public class MemberSearchResult - { - public string Id { get; set; } - public string LoginName { get; set; } - public string Name { get; set; } - public string Email { get; set; } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/members/MemberSearch.ascx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/members/MemberSearch.ascx.designer.cs deleted file mode 100644 index 5a80e4c929..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/members/MemberSearch.ascx.designer.cs +++ /dev/null @@ -1,51 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace umbraco.presentation.umbraco.members { - - - public partial class MemberSearch { - - /// - /// searchQuery control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.TextBox searchQuery; - - /// - /// ButtonSearch control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Button ButtonSearch; - - /// - /// resultsPane control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.Pane resultsPane; - - /// - /// rp_members control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Repeater rp_members; - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/members/ViewMembers.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/members/ViewMembers.aspx deleted file mode 100644 index 3dd8012118..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/members/ViewMembers.aspx +++ /dev/null @@ -1,41 +0,0 @@ -<%@ Page Title="" Language="C#" MasterPageFile="../masterpages/umbracoPage.Master" AutoEventWireup="true" CodeBehind="ViewMembers.aspx.cs" Inherits="umbraco.presentation.members.ViewMembers" %> - -<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
<%= Services.TextService.Localize("name") %><%= Services.TextService.Localize("email") %><%= Services.TextService.Localize("login") %>
- -
-
-
-
- - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/members/ViewMembers.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/members/ViewMembers.aspx.cs deleted file mode 100644 index a147c12295..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/members/ViewMembers.aspx.cs +++ /dev/null @@ -1,103 +0,0 @@ -using Umbraco.Core.Services; -using System; -using System.Web.Security; -using System.Web.UI.WebControls; -using Umbraco.Core; -using Umbraco.Core.Security; - -namespace umbraco.presentation.members -{ - public partial class ViewMembers : Umbraco.Web.UI.Pages.UmbracoEnsuredPage - { - - public ViewMembers() - { - CurrentApp = Constants.Applications.Members.ToString(); - } - - protected void Page_Load(object sender, EventArgs e) - { - panel1.Text = Services.TextService.Localize("member"); - BindRp(); - } - - private void BindRp() - { - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - string letter = Request.QueryString["letter"]; - if (string.IsNullOrEmpty(letter) == false) - { - if (provider.IsUmbracoMembershipProvider()) - { - if (letter == "#") - { - rp_members.DataSource = cms.businesslogic.member.Member.getAllOtherMembers(); - } - else - { - rp_members.DataSource = cms.businesslogic.member.Member.getMemberFromFirstLetter(letter.ToCharArray()[0]); - } - } - else - { - rp_members.DataSource = provider.FindUsersByName(letter + "%"); - } - rp_members.DataBind(); - } - } - - public void bindMember(object sender, RepeaterItemEventArgs e) - { - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem) - { - if (provider.IsUmbracoMembershipProvider()) - { - cms.businesslogic.member.Member mem = (cms.businesslogic.member.Member)e.Item.DataItem; - Literal _name = (Literal)e.Item.FindControl("lt_name"); - Literal _email = (Literal)e.Item.FindControl("lt_email"); - Literal _login = (Literal)e.Item.FindControl("lt_login"); - Button _button = (Button)e.Item.FindControl("bt_delete"); - - _name.Text = "
" + mem.Text + ""; - _login.Text = mem.LoginName; - _email.Text = mem.Email; - - _button.CommandArgument = mem.Id.ToString(); - _button.OnClientClick = "return confirm(\"" + Services.TextService.Localize("confirmdelete") + "'" + mem.Text + "' ?\")"; - _button.Text = Services.TextService.Localize("delete"); - } - else - { - var mem = (MembershipUser)e.Item.DataItem; - Literal _name = (Literal)e.Item.FindControl("lt_name"); - Literal _email = (Literal)e.Item.FindControl("lt_email"); - Literal _login = (Literal)e.Item.FindControl("lt_login"); - Button _button = (Button)e.Item.FindControl("bt_delete"); - - _name.Text = "" + mem.UserName + ""; - _login.Text = mem.UserName; - _email.Text = mem.Email; - _button.Visible = false; - - } - } - } - - public void deleteMember(object sender, CommandEventArgs e) - { - int memid = 0; - - if (int.TryParse(e.CommandArgument.ToString(), out memid)) - { - cms.businesslogic.member.Member mem = new global::umbraco.cms.businesslogic.member.Member(memid); - - if (mem != null) - mem.delete(); - - - BindRp(); - } - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/members/ViewMembers.aspx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/members/ViewMembers.aspx.designer.cs deleted file mode 100644 index 568431385a..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/members/ViewMembers.aspx.designer.cs +++ /dev/null @@ -1,42 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace umbraco.presentation.members { - - - public partial class ViewMembers { - - /// - /// panel1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.UmbracoPanel panel1; - - /// - /// pane1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.Pane pane1; - - /// - /// rp_members control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Repeater rp_members; - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/members/search.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/members/search.aspx deleted file mode 100644 index 2bb21660f0..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/members/search.aspx +++ /dev/null @@ -1,13 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="../masterpages/umbracoPage.Master" CodeBehind="search.aspx.cs" Inherits="umbraco.presentation.members.search" %> -<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> -<%@ Register Src="~/umbraco/members/MemberSearch.ascx" TagName="MemberSearch" TagPrefix="umb" %> - - - - - - - - - - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/members/search.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/members/search.aspx.cs deleted file mode 100644 index ccd096aea3..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/members/search.aspx.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Web; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Linq; -using System.Web.Security; -using umbraco.cms.businesslogic.member; -using umbraco.DataLayer.SqlHelpers; -using umbraco.BusinessLogic; -using Examine; -using Umbraco.Core; - -namespace umbraco.presentation.members -{ - - - public partial class search : Umbraco.Web.UI.Pages.UmbracoEnsuredPage - { - public search() - { - CurrentApp = Constants.Applications.Members.ToString(); - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/members/search.aspx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/members/search.aspx.designer.cs deleted file mode 100644 index 115302af0d..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/members/search.aspx.designer.cs +++ /dev/null @@ -1,24 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace umbraco.presentation.members { - - - public partial class search { - - /// - /// Panel2 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::umbraco.uicontrols.UmbracoPanel Panel2; - } -} From 69dc8bc57fba36b84c4fc0b6136cfc6cdb16b2e5 Mon Sep 17 00:00:00 2001 From: Yannis Guedel Date: Thu, 24 Mar 2016 19:49:01 +0100 Subject: [PATCH 026/101] fix newtonsoft.json 8.0.0 when running in debug mode --- src/Umbraco.Web.UI/web.Template.Debug.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/web.Template.Debug.config b/src/Umbraco.Web.UI/web.Template.Debug.config index 7816568561..93ed17e14d 100644 --- a/src/Umbraco.Web.UI/web.Template.Debug.config +++ b/src/Umbraco.Web.UI/web.Template.Debug.config @@ -378,7 +378,7 @@ xdt:Locator="Condition(_defaultNamespace:assemblyIdentity[@name='Newtonsoft.Json']])"/> - + Date: Thu, 24 Mar 2016 20:30:31 +0000 Subject: [PATCH 027/101] Updated config transforms --- build/NuSpecs/tools/trees.config.install.xdt | 2 +- src/Umbraco.Web.UI/config/trees.Release.config | 2 +- src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml | 9 +-------- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/build/NuSpecs/tools/trees.config.install.xdt b/build/NuSpecs/tools/trees.config.install.xdt index 580c619547..1a56c81271 100644 --- a/build/NuSpecs/tools/trees.config.install.xdt +++ b/build/NuSpecs/tools/trees.config.install.xdt @@ -41,7 +41,7 @@ - - + diff --git a/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml b/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml index 9fca6d082a..fdb8697398 100644 --- a/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml +++ b/src/Umbraco.Web.UI/umbraco/config/create/UI.Release.xml @@ -115,14 +115,7 @@ - -
Dictionary editor egenskab
- /create/simple.ascx - - - -
- +
Dictionary editor egenskab
/create/simple.ascx From 4f141e8b27d124d36177da6a848f1cfdecd539b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ferreira?= Date: Fri, 26 Feb 2016 14:30:32 +0000 Subject: [PATCH 028/101] Fixing column sort on columns with custom headers --- .../Repositories/ContentRepository.cs | 1905 +++---- .../Interfaces/IContentRepository.cs | 143 +- .../Interfaces/IMediaRepository.cs | 59 +- .../Interfaces/IMemberRepository.cs | 115 +- .../Repositories/MediaRepository.cs | 1141 ++-- .../Repositories/MemberRepository.cs | 1487 +++--- .../Repositories/VersionableRepositoryBase.cs | 992 ++-- .../SqlSyntax/ISqlSyntaxProvider.cs | 136 +- .../SqlSyntax/MySqlSyntaxProvider.cs | 651 +-- .../SqlSyntax/SqlSyntaxProviderBase.cs | 1058 ++-- src/Umbraco.Core/Services/ContentService.cs | 4632 +++++++++-------- src/Umbraco.Core/Services/IContentService.cs | 1148 ++-- src/Umbraco.Core/Services/IMediaService.cs | 646 +-- src/Umbraco.Core/Services/IMemberService.cs | 383 +- src/Umbraco.Core/Services/MediaService.cs | 2626 +++++----- src/Umbraco.Core/Services/MemberService.cs | 2584 ++++----- .../Repositories/ContentRepositoryTest.cs | 1582 +++--- .../Repositories/MediaRepositoryTest.cs | 1086 ++-- .../UmbracoExamine/IndexInitializer.cs | 226 +- .../src/common/resources/content.resource.js | 1194 ++--- .../src/common/resources/media.resource.js | 894 ++-- .../src/common/resources/member.resource.js | 430 +- .../list/list.listviewlayout.controller.js | 3 +- .../listview/listview.controller.js | 967 ++-- src/Umbraco.Web/Editors/ContentController.cs | 1353 ++--- src/Umbraco.Web/Editors/MediaController.cs | 1339 ++--- src/Umbraco.Web/Editors/MemberController.cs | 1289 ++--- src/UmbracoExamine/UmbracoMemberIndexer.cs | 353 +- 28 files changed, 15251 insertions(+), 15171 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 7d3557a81d..30887281f1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -26,956 +26,957 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - /// - /// Represents a repository for doing CRUD operations for - /// - internal class ContentRepository : RecycleBinRepository, IContentRepository - { - private readonly IContentTypeRepository _contentTypeRepository; - private readonly ITemplateRepository _templateRepository; - private readonly ITagRepository _tagRepository; - private readonly CacheHelper _cacheHelper; - private readonly ContentPreviewRepository _contentPreviewRepository; - private readonly ContentXmlRepository _contentXmlRepository; - - public ContentRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider syntaxProvider, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection) - : base(work, cacheHelper, logger, syntaxProvider, contentSection) - { - if (contentTypeRepository == null) throw new ArgumentNullException("contentTypeRepository"); - if (templateRepository == null) throw new ArgumentNullException("templateRepository"); - if (tagRepository == null) throw new ArgumentNullException("tagRepository"); - _contentTypeRepository = contentTypeRepository; - _templateRepository = templateRepository; - _tagRepository = tagRepository; - _cacheHelper = cacheHelper; - _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, syntaxProvider); - _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, syntaxProvider); - - EnsureUniqueNaming = true; - } - - public bool EnsureUniqueNaming { get; set; } - - #region Overrides of RepositoryBase - - protected override IContent PerformGet(int id) - { - var sql = GetBaseQuery(false) - .Where(GetBaseWhereClause(), new { Id = id }) - .Where(x => x.Newest) - .OrderByDescending(x => x.VersionDate); - - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - return null; - - var content = CreateContentFromDto(dto, dto.ContentVersionDto.VersionId, sql); - - return content; - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - var sql = GetBaseQuery(false); - if (ids.Any()) - { - sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); - } - - //we only want the newest ones with this method - sql.Where(x => x.Newest); - - return ProcessQuery(sql); - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate() - .Where(x => x.Newest) - .OrderByDescending(x => x.VersionDate) - .OrderBy(x => x.SortOrder); - - return ProcessQuery(sql); - } - - #endregion - - #region Overrides of PetaPocoRepositoryBase - - protected override Sql GetBaseQuery(bool isCount) - { - var sqlx = string.Format("LEFT OUTER JOIN {0} {1} ON ({1}.{2}={0}.{2} AND {1}.{3}=1)", - SqlSyntax.GetQuotedTableName("cmsDocument"), - SqlSyntax.GetQuotedTableName("cmsDocument2"), - SqlSyntax.GetQuotedColumnName("nodeId"), - SqlSyntax.GetQuotedColumnName("published")); - - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From() - .InnerJoin() - .On(left => left.VersionId, right => right.VersionId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - - // cannot do this because PetaPoco does not know how to alias the table - //.LeftOuterJoin() - //.On(left => left.NodeId, right => right.NodeId) - // so have to rely on writing our own SQL - .Append(sqlx/*, new { @published = true }*/) - - .Where(x => x.NodeObjectType == NodeObjectTypeId); - return sql; - } - - protected override string GetBaseWhereClause() - { - return "umbracoNode.id = @Id"; - } - - protected override IEnumerable GetDeleteClauses() - { - var list = new List - { - "DELETE FROM cmsTask WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", - "DELETE FROM umbracoRelation WHERE parentId = @Id", - "DELETE FROM umbracoRelation WHERE childId = @Id", - "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", - "DELETE FROM umbracoDomains WHERE domainRootStructureID = @Id", - "DELETE FROM cmsDocument WHERE nodeId = @Id", - "DELETE FROM cmsPropertyData WHERE contentNodeId = @Id", - "DELETE FROM cmsPreviewXml WHERE nodeId = @Id", - "DELETE FROM cmsContentVersion WHERE ContentId = @Id", - "DELETE FROM cmsContentXml WHERE nodeId = @Id", - "DELETE FROM cmsContent WHERE nodeId = @Id", - "DELETE FROM umbracoAccess WHERE nodeId = @Id", - "DELETE FROM umbracoNode WHERE id = @Id" - }; - return list; - } - - protected override Guid NodeObjectTypeId - { - get { return 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 = new 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 = new 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.Builder.Where(x => x.Published == true); - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); - } - else - { - foreach (var contentTypeId in contentTypeIds) - { - //copy local - var id = contentTypeId; - var query = Query.Builder.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, Transaction 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, - new Tuple("cmsDocument", "nodeId"), - ProcessQuery, "Path", Direction.Ascending); - - 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(xmlItems, tr); - - processed += xmlItems.Length; - - pageIndex++; - } while (processed < total); - } - - public override IContent GetByVersion(Guid versionId) - { - var sql = GetBaseQuery(false); - sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(x => x.VersionDate); - - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - return null; - - var content = CreateContentFromDto(dto, versionId, sql); - - return content; - } - - public override void DeleteVersion(Guid versionId) - { - var sql = new Sql() - .Select("*") - .From() - .InnerJoin().On(left => left.VersionId, right => right.VersionId) - .Where(x => x.VersionId == versionId) - .Where(x => x.Newest != true); - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) return; - - using (var transaction = Database.GetTransaction()) - { - PerformDeleteVersion(dto.NodeId, versionId); - - transaction.Complete(); - } - } - - public override void DeleteVersions(int id, DateTime versionDate) - { - var sql = new Sql() - .Select("*") - .From() - .InnerJoin().On(left => left.VersionId, right => right.VersionId) - .Where(x => x.NodeId == id) - .Where(x => x.VersionDate < versionDate) - .Where(x => x.Newest != true); - var list = Database.Fetch(sql); - if (list.Any() == false) return; - - using (var transaction = Database.GetTransaction()) - { - foreach (var dto in list) - { - PerformDeleteVersion(id, dto.VersionId); - } - - transaction.Complete(); - } - } - - protected override void PerformDeleteVersion(int id, Guid versionId) - { - Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - } - - #endregion - - #region Unit of Work Implementation - - protected override void PersistDeletedItem(IContent entity) - { - //We need to clear out all access rules but we need to do this in a manual way since - // nothing in that table is joined to a content id - var subQuery = new Sql() - .Select("umbracoAccessRule.accessId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.AccessId, right => right.Id) - .Where(dto => dto.NodeId == entity.Id); - Database.Execute(SqlSyntax.GetDeleteSubquery("umbracoAccessRule", "accessId", subQuery)); - - //now let the normal delete clauses take care of everything else - base.PersistDeletedItem(entity); - } - - protected override void PersistNewItem(IContent entity) - { - ((Content)entity).AddingEntity(); - - //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); - - //Ensure that strings don't contain characters that are invalid in XML - entity.SanitizeEntityPropertiesForXmlStorage(); - - var factory = new ContentFactory(NodeObjectTypeId, entity.Id); - var dto = factory.BuildDto(entity); - - //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 level = parent.Level + 1; - var maxSortOrder = Database.ExecuteScalar( - "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", - new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); - var sortOrder = maxSortOrder + 1; - - //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); - - //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; - - //Assign the same permissions to it as the parent node - // http://issues.umbraco.org/issue/U4-2161 - var permissionsRepo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - var parentPermissions = permissionsRepo.GetPermissionsForEntity(entity.ParentId).ToArray(); - //if there are parent permissions then assign them, otherwise leave null and permissions will become the - // user's default permissions. - if (parentPermissions.Any()) - { - var userPermissions = ( - from perm in parentPermissions - from p in perm.AssignedPermissions - select new EntityPermissionSet.UserPermission(perm.UserId, p)).ToList(); - - permissionsRepo.ReplaceEntityPermissions(new EntityPermissionSet(entity.Id, userPermissions)); - //flag the entity's permissions changed flag so we can track those changes. - //Currently only used for the cache refreshers to detect if we should refresh all user permissions cache. - ((Content)entity).PermissionsChanged = true; - } - - //Create the Content specific data - cmsContent - var contentDto = dto.ContentVersionDto.ContentDto; - contentDto.NodeId = nodeDto.NodeId; - Database.Insert(contentDto); - - //Create the first version - cmsContentVersion - //Assumes a new Version guid and Version date (modified date) has been set - var contentVersionDto = dto.ContentVersionDto; - contentVersionDto.NodeId = nodeDto.NodeId; - Database.Insert(contentVersionDto); - - //Create the Document specific data for this version - cmsDocument - //Assumes a new Version guid has been generated - dto.NodeId = nodeDto.NodeId; - Database.Insert(dto); - - //Create the PropertyData for this version - cmsPropertyData - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); - var keyDictionary = new Dictionary(); - - //Add Properties - foreach (var propertyDataDto in propertyDataDtos) - { - var primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } - - //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); - } - - // published => update published version infos, else leave it blank - if (entity.Published) - { - dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto - { - VersionId = dto.VersionId, - Newest = true, - NodeId = dto.NodeId, - Published = true - }; - ((Content)entity).PublishedVersionGuid = dto.VersionId; - } - - entity.ResetDirtyProperties(); - } - - protected override void PersistUpdatedItem(IContent entity) - { - var publishedState = ((Content)entity).PublishedState; - - //check if we need to make any database changes at all - if (entity.RequiresSaving(publishedState) == false) - { - entity.ResetDirtyProperties(); - return; - } - - //check if we need to create a new version - bool shouldCreateNewVersion = entity.ShouldCreateNewVersion(publishedState); - if (shouldCreateNewVersion) - { - //Updates Modified date and Version Guid - ((Content)entity).UpdatingEntity(); - } - else - { - entity.UpdateDate = DateTime.Now; - } - - //Ensure unique name on the same level - entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id); - - //Ensure that strings don't contain characters that are invalid in XML - entity.SanitizeEntityPropertiesForXmlStorage(); - - //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 }); - 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; - - //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. - // Gonna just leave it as is for now, and not re-propogate permissions. - } - - 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 }); - factory.SetPrimaryKey(contentDto.PrimaryKey); - var dto = factory.BuildDto(entity); - - //Updates the (base) node data - umbracoNode - var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; - var o = Database.Update(nodeDto); - - //Only update this DTO if the contentType has actually changed - if (contentDto.ContentTypeId != entity.ContentTypeId) - { - //Create the Content specific data - cmsContent - var newContentDto = dto.ContentVersionDto.ContentDto; - 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; - } - - //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); - } - - var contentVersionDto = dto.ContentVersionDto; - if (shouldCreateNewVersion) - { - //Create a new version - cmsContentVersion - //Assumes a new Version guid and Version date (modified date) has been set - Database.Insert(contentVersionDto); - //Create the Document specific data for this version - cmsDocument - //Assumes a new Version guid has been generated - Database.Insert(dto); - } - 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 }); - contentVersionDto.Id = contentVerDto.Id; - - Database.Update(contentVersionDto); - Database.Update(dto); - } - - //Create the PropertyData for this version - cmsPropertyData - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); - var keyDictionary = new Dictionary(); - - //Add Properties - foreach (var propertyDataDto in propertyDataDtos) - { - if (shouldCreateNewVersion == false && propertyDataDto.Id > 0) - { - Database.Update(propertyDataDto); - } - else - { - int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } - } - - //Update Properties with its newly set Id - if (keyDictionary.Any()) - { - foreach (var property in entity.Properties) - { - if (keyDictionary.ContainsKey(property.PropertyTypeId) == false) continue; - - property.Id = keyDictionary[property.PropertyTypeId]; - } - } - - //lastly, check if we are a newly published version and then update the tags table - if (publishedStateChanged && entity.Published) - { - 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); - } - - // published => update published version infos, - // else if unpublished then clear published version infos - if (entity.Published) - { - dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto - { - VersionId = dto.VersionId, - Newest = true, - NodeId = dto.NodeId, - Published = true - }; - ((Content)entity).PublishedVersionGuid = dto.VersionId; - } - else if (publishedStateChanged) - { - dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto - { - VersionId = default(Guid), - Newest = false, - NodeId = dto.NodeId, - Published = false - }; - ((Content)entity).PublishedVersionGuid = default(Guid); - } - - entity.ResetDirtyProperties(); - } - - - #endregion - - #region Implementation of IContentRepository - - 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 - - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate() - .Where(x => x.Published) - .OrderBy(x => x.Level, SqlSyntax) - .OrderBy(x => x.SortOrder, SqlSyntax); - - //NOTE: This doesn't allow properties to be part of the query - var dtos = Database.Fetch(sql); - - 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) - 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); - } - } - } - - public int CountPublished() - { - var sql = GetBaseQuery(true).Where(x => x.Trashed == false) - .Where(x => x.Published == true); - return Database.ExecuteScalar(sql); - } - - public void ReplaceContentPermissions(EntityPermissionSet permissionSet) - { - var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - repo.ReplaceEntityPermissions(permissionSet); - } - - public void ClearPublished(IContent content) - { - // race cond! - var documentDtos = Database.Fetch("WHERE nodeId=@id AND published=@published", new { id = content.Id, published = true }); - foreach (var documentDto in documentDtos) - { - documentDto.Published = false; - Database.Update(documentDto); - } - } - - /// - /// Assigns a single permission to the current content item for the specified user ids - /// - /// - /// - /// - public void AssignEntityPermission(IContent entity, char permission, IEnumerable userIds) - { - var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - repo.AssignEntityPermission(entity, permission, userIds); - } - - public IEnumerable GetPermissionsForEntity(int entityId) - { - var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - 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 - /// - /// Query to excute - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects - public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string filter = "") - { - - //NOTE: This uses the GetBaseQuery method but that does not take into account the required 'newest' field which is - // what we always require for a paged result, so we'll ensure it's included in the filter - - var args = new List(); - var sbWhere = new StringBuilder("AND (cmsDocument.newest = 1)"); - - if (filter.IsNullOrWhiteSpace() == false) - { - sbWhere.Append(" AND (cmsDocument." + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ")"); - args.Add("%" + filter + "%"); - } - - Func> filterCallback = () => new Tuple(sbWhere.ToString(), args.ToArray()); - - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - new Tuple("cmsDocument", "nodeId"), - ProcessQuery, orderBy, orderDirection, - filterCallback); - - } - - #endregion - - #region IRecycleBinRepository members - - protected override int RecycleBinId - { - get { return Constants.System.RecycleBinContent; } - } - - #endregion - - protected override string GetDatabaseFieldNameForOrderBy(string orderBy) - { - //Some custom ones - switch (orderBy.ToUpperInvariant()) - { - case "NAME": - return "cmsDocument.text"; - case "UPDATER": - //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter - return "cmsDocument.documentUser"; - } - - return base.GetDatabaseFieldNameForOrderBy(orderBy); - } - - private IEnumerable ProcessQuery(Sql sql) - { - //NOTE: This doesn't allow properties to be part of the query - var dtos = Database.Fetch(sql); - - //nothing found - if (dtos.Any() == false) return Enumerable.Empty(); - - //content types - //NOTE: This should be ok for an SQL 'IN' statement, there shouldn't be an insane amount of content types - var contentTypes = _contentTypeRepository.GetAll(dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray()) - .ToArray(); - - - var ids = dtos - .Where(dto => dto.TemplateId.HasValue && dto.TemplateId.Value > 0) - .Select(x => x.TemplateId.Value).ToArray(); - - //NOTE: This should be ok for an SQL 'IN' statement, there shouldn't be an insane amount of content types - var templates = ids.Length == 0 ? Enumerable.Empty() : _templateRepository.GetAll(ids).ToArray(); - - var dtosWithContentTypes = dtos - //This select into and null check are required because we don't have a foreign damn key on the contentType column - // http://issues.umbraco.org/issue/U4-5503 - .Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentVersionDto.ContentDto.ContentTypeId) }) - .Where(x => x.contentType != null) - .ToArray(); - - //Go get the property data for each document - var docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition( - d.dto.NodeId, - d.dto.VersionId, - d.dto.ContentVersionDto.VersionDate, - d.dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, - d.contentType)); - - var propertyData = GetPropertyCollection(sql, docDefs); - - 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)), - propertyData[d.dto.NodeId])); - } - - /// - /// Private method to create a content object from a DocumentDto, which is used by Get and GetByVersion. - /// - /// - /// - /// - /// - /// - private IContent CreateContentFromDto(DocumentDto dto, - IContentType contentType, - ITemplate template, - Models.PropertyCollection propCollection) - { - var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId); - var content = factory.BuildEntity(dto); - - //Check if template id is set on DocumentDto, and get ITemplate if it is. - if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) - { - content.Template = template ?? _templateRepository.Get(dto.TemplateId.Value); - } - else - { - //ensure there isn't one set. - content.Template = null; - } - - content.Properties = propCollection; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)content).ResetDirtyProperties(false); - return content; - } - - /// - /// Private method to create a content object from a DocumentDto, which is used by Get and GetByVersion. - /// - /// - /// - /// - /// - private IContent CreateContentFromDto(DocumentDto dto, Guid versionId, Sql docSql) - { - var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); - - var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId); - var content = factory.BuildEntity(dto); - - //Check if template id is set on DocumentDto, and get ITemplate if it is. - if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) - { - content.Template = _templateRepository.Get(dto.TemplateId.Value); - } - - var docDef = new DocumentDefinition(dto.NodeId, versionId, content.UpdateDate, content.CreateDate, contentType); - - var properties = GetPropertyCollection(docSql, new[] { docDef }); - - content.Properties = properties[dto.NodeId]; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)content).ResetDirtyProperties(false); - return content; - } - - private string EnsureUniqueNodeName(int parentId, string nodeName, int id = 0) - { - if (EnsureUniqueNaming == false) - return nodeName; - - var sql = new Sql(); - sql.Select("*") - .From() - .Where(x => x.NodeObjectType == NodeObjectTypeId && x.ParentId == parentId && x.Text.StartsWith(nodeName)); - - int uniqueNumber = 1; - var currentName = nodeName; - - var dtos = Database.Fetch(sql); - if (dtos.Any()) - { - var results = dtos.OrderBy(x => x.Text, new SimilarNodeNameComparer()); - foreach (var dto in results) - { - if (id != 0 && id == dto.NodeId) continue; - - if (dto.Text.ToLowerInvariant().Equals(currentName.ToLowerInvariant())) - { - currentName = nodeName + string.Format(" ({0})", uniqueNumber); - uniqueNumber++; - } - } - } - - return currentName; - } - - /// - /// Dispose disposable properties - /// - /// - /// Ensure the unit of work is disposed - /// - protected override void DisposeResources() - { - _contentTypeRepository.Dispose(); - _templateRepository.Dispose(); - _tagRepository.Dispose(); - _contentPreviewRepository.Dispose(); - _contentXmlRepository.Dispose(); - } - } + /// + /// Represents a repository for doing CRUD operations for + /// + internal class ContentRepository : RecycleBinRepository, IContentRepository + { + private readonly IContentTypeRepository _contentTypeRepository; + private readonly ITemplateRepository _templateRepository; + private readonly ITagRepository _tagRepository; + private readonly CacheHelper _cacheHelper; + private readonly ContentPreviewRepository _contentPreviewRepository; + private readonly ContentXmlRepository _contentXmlRepository; + + public ContentRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider syntaxProvider, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection) + : base(work, cacheHelper, logger, syntaxProvider, contentSection) + { + if (contentTypeRepository == null) throw new ArgumentNullException("contentTypeRepository"); + if (templateRepository == null) throw new ArgumentNullException("templateRepository"); + if (tagRepository == null) throw new ArgumentNullException("tagRepository"); + _contentTypeRepository = contentTypeRepository; + _templateRepository = templateRepository; + _tagRepository = tagRepository; + _cacheHelper = cacheHelper; + _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, syntaxProvider); + _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, syntaxProvider); + + EnsureUniqueNaming = true; + } + + public bool EnsureUniqueNaming { get; set; } + + #region Overrides of RepositoryBase + + protected override IContent PerformGet(int id) + { + var sql = GetBaseQuery(false) + .Where(GetBaseWhereClause(), new { Id = id }) + .Where(x => x.Newest) + .OrderByDescending(x => x.VersionDate); + + var dto = Database.Fetch(sql).FirstOrDefault(); + + if (dto == null) + return null; + + var content = CreateContentFromDto(dto, dto.ContentVersionDto.VersionId, sql); + + return content; + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + var sql = GetBaseQuery(false); + if (ids.Any()) + { + sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); + } + + //we only want the newest ones with this method + sql.Where(x => x.Newest); + + return ProcessQuery(sql); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + var sqlClause = GetBaseQuery(false); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate() + .Where(x => x.Newest) + .OrderByDescending(x => x.VersionDate) + .OrderBy(x => x.SortOrder); + + return ProcessQuery(sql); + } + + #endregion + + #region Overrides of PetaPocoRepositoryBase + + protected override Sql GetBaseQuery(bool isCount) + { + var sqlx = string.Format("LEFT OUTER JOIN {0} {1} ON ({1}.{2}={0}.{2} AND {1}.{3}=1)", + SqlSyntax.GetQuotedTableName("cmsDocument"), + SqlSyntax.GetQuotedTableName("cmsDocument2"), + SqlSyntax.GetQuotedColumnName("nodeId"), + SqlSyntax.GetQuotedColumnName("published")); + + var sql = new Sql(); + sql.Select(isCount ? "COUNT(*)" : "*") + .From() + .InnerJoin() + .On(left => left.VersionId, right => right.VersionId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + + // cannot do this because PetaPoco does not know how to alias the table + //.LeftOuterJoin() + //.On(left => left.NodeId, right => right.NodeId) + // so have to rely on writing our own SQL + .Append(sqlx/*, new { @published = true }*/) + + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; + } + + protected override string GetBaseWhereClause() + { + return "umbracoNode.id = @Id"; + } + + protected override IEnumerable GetDeleteClauses() + { + var list = new List + { + "DELETE FROM cmsTask WHERE nodeId = @Id", + "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", + "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", + "DELETE FROM umbracoRelation WHERE parentId = @Id", + "DELETE FROM umbracoRelation WHERE childId = @Id", + "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", + "DELETE FROM umbracoDomains WHERE domainRootStructureID = @Id", + "DELETE FROM cmsDocument WHERE nodeId = @Id", + "DELETE FROM cmsPropertyData WHERE contentNodeId = @Id", + "DELETE FROM cmsPreviewXml WHERE nodeId = @Id", + "DELETE FROM cmsContentVersion WHERE ContentId = @Id", + "DELETE FROM cmsContentXml WHERE nodeId = @Id", + "DELETE FROM cmsContent WHERE nodeId = @Id", + "DELETE FROM umbracoAccess WHERE nodeId = @Id", + "DELETE FROM umbracoNode WHERE id = @Id" + }; + return list; + } + + protected override Guid NodeObjectTypeId + { + get { return 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 = new 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 = new 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.Builder.Where(x => x.Published == true); + RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); + } + else + { + foreach (var contentTypeId in contentTypeIds) + { + //copy local + var id = contentTypeId; + var query = Query.Builder.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, Transaction 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, + new Tuple("cmsDocument", "nodeId"), + ProcessQuery, "Path", Direction.Ascending, orderBySystemField: 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(xmlItems, tr); + + processed += xmlItems.Length; + + pageIndex++; + } while (processed < total); + } + + public override IContent GetByVersion(Guid versionId) + { + var sql = GetBaseQuery(false); + sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); + sql.OrderByDescending(x => x.VersionDate); + + var dto = Database.Fetch(sql).FirstOrDefault(); + + if (dto == null) + return null; + + var content = CreateContentFromDto(dto, versionId, sql); + + return content; + } + + public override void DeleteVersion(Guid versionId) + { + var sql = new Sql() + .Select("*") + .From() + .InnerJoin().On(left => left.VersionId, right => right.VersionId) + .Where(x => x.VersionId == versionId) + .Where(x => x.Newest != true); + var dto = Database.Fetch(sql).FirstOrDefault(); + + if (dto == null) return; + + using (var transaction = Database.GetTransaction()) + { + PerformDeleteVersion(dto.NodeId, versionId); + + transaction.Complete(); + } + } + + public override void DeleteVersions(int id, DateTime versionDate) + { + var sql = new Sql() + .Select("*") + .From() + .InnerJoin().On(left => left.VersionId, right => right.VersionId) + .Where(x => x.NodeId == id) + .Where(x => x.VersionDate < versionDate) + .Where(x => x.Newest != true); + var list = Database.Fetch(sql); + if (list.Any() == false) return; + + using (var transaction = Database.GetTransaction()) + { + foreach (var dto in list) + { + PerformDeleteVersion(id, dto.VersionId); + } + + transaction.Complete(); + } + } + + protected override void PerformDeleteVersion(int id, Guid versionId) + { + Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + } + + #endregion + + #region Unit of Work Implementation + + protected override void PersistDeletedItem(IContent entity) + { + //We need to clear out all access rules but we need to do this in a manual way since + // nothing in that table is joined to a content id + var subQuery = new Sql() + .Select("umbracoAccessRule.accessId") + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.AccessId, right => right.Id) + .Where(dto => dto.NodeId == entity.Id); + Database.Execute(SqlSyntax.GetDeleteSubquery("umbracoAccessRule", "accessId", subQuery)); + + //now let the normal delete clauses take care of everything else + base.PersistDeletedItem(entity); + } + + protected override void PersistNewItem(IContent entity) + { + ((Content)entity).AddingEntity(); + + //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); + + //Ensure that strings don't contain characters that are invalid in XML + entity.SanitizeEntityPropertiesForXmlStorage(); + + var factory = new ContentFactory(NodeObjectTypeId, entity.Id); + var dto = factory.BuildDto(entity); + + //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 level = parent.Level + 1; + var maxSortOrder = Database.ExecuteScalar( + "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", + new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); + var sortOrder = maxSortOrder + 1; + + //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); + + //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; + + //Assign the same permissions to it as the parent node + // http://issues.umbraco.org/issue/U4-2161 + var permissionsRepo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); + var parentPermissions = permissionsRepo.GetPermissionsForEntity(entity.ParentId).ToArray(); + //if there are parent permissions then assign them, otherwise leave null and permissions will become the + // user's default permissions. + if (parentPermissions.Any()) + { + var userPermissions = ( + from perm in parentPermissions + from p in perm.AssignedPermissions + select new EntityPermissionSet.UserPermission(perm.UserId, p)).ToList(); + + permissionsRepo.ReplaceEntityPermissions(new EntityPermissionSet(entity.Id, userPermissions)); + //flag the entity's permissions changed flag so we can track those changes. + //Currently only used for the cache refreshers to detect if we should refresh all user permissions cache. + ((Content)entity).PermissionsChanged = true; + } + + //Create the Content specific data - cmsContent + var contentDto = dto.ContentVersionDto.ContentDto; + contentDto.NodeId = nodeDto.NodeId; + Database.Insert(contentDto); + + //Create the first version - cmsContentVersion + //Assumes a new Version guid and Version date (modified date) has been set + var contentVersionDto = dto.ContentVersionDto; + contentVersionDto.NodeId = nodeDto.NodeId; + Database.Insert(contentVersionDto); + + //Create the Document specific data for this version - cmsDocument + //Assumes a new Version guid has been generated + dto.NodeId = nodeDto.NodeId; + Database.Insert(dto); + + //Create the PropertyData for this version - cmsPropertyData + var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); + var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); + var keyDictionary = new Dictionary(); + + //Add Properties + foreach (var propertyDataDto in propertyDataDtos) + { + var primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); + keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); + } + + //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); + } + + // published => update published version infos, else leave it blank + if (entity.Published) + { + dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto + { + VersionId = dto.VersionId, + Newest = true, + NodeId = dto.NodeId, + Published = true + }; + ((Content)entity).PublishedVersionGuid = dto.VersionId; + } + + entity.ResetDirtyProperties(); + } + + protected override void PersistUpdatedItem(IContent entity) + { + var publishedState = ((Content)entity).PublishedState; + + //check if we need to make any database changes at all + if (entity.RequiresSaving(publishedState) == false) + { + entity.ResetDirtyProperties(); + return; + } + + //check if we need to create a new version + bool shouldCreateNewVersion = entity.ShouldCreateNewVersion(publishedState); + if (shouldCreateNewVersion) + { + //Updates Modified date and Version Guid + ((Content)entity).UpdatingEntity(); + } + else + { + entity.UpdateDate = DateTime.Now; + } + + //Ensure unique name on the same level + entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id); + + //Ensure that strings don't contain characters that are invalid in XML + entity.SanitizeEntityPropertiesForXmlStorage(); + + //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 }); + 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; + + //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. + // Gonna just leave it as is for now, and not re-propogate permissions. + } + + 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 }); + factory.SetPrimaryKey(contentDto.PrimaryKey); + var dto = factory.BuildDto(entity); + + //Updates the (base) node data - umbracoNode + var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; + var o = Database.Update(nodeDto); + + //Only update this DTO if the contentType has actually changed + if (contentDto.ContentTypeId != entity.ContentTypeId) + { + //Create the Content specific data - cmsContent + var newContentDto = dto.ContentVersionDto.ContentDto; + 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; + } + + //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); + } + + var contentVersionDto = dto.ContentVersionDto; + if (shouldCreateNewVersion) + { + //Create a new version - cmsContentVersion + //Assumes a new Version guid and Version date (modified date) has been set + Database.Insert(contentVersionDto); + //Create the Document specific data for this version - cmsDocument + //Assumes a new Version guid has been generated + Database.Insert(dto); + } + 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 }); + contentVersionDto.Id = contentVerDto.Id; + + Database.Update(contentVersionDto); + Database.Update(dto); + } + + //Create the PropertyData for this version - cmsPropertyData + var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); + var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); + var keyDictionary = new Dictionary(); + + //Add Properties + foreach (var propertyDataDto in propertyDataDtos) + { + if (shouldCreateNewVersion == false && propertyDataDto.Id > 0) + { + Database.Update(propertyDataDto); + } + else + { + int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); + keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); + } + } + + //Update Properties with its newly set Id + if (keyDictionary.Any()) + { + foreach (var property in entity.Properties) + { + if (keyDictionary.ContainsKey(property.PropertyTypeId) == false) continue; + + property.Id = keyDictionary[property.PropertyTypeId]; + } + } + + //lastly, check if we are a newly published version and then update the tags table + if (publishedStateChanged && entity.Published) + { + 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); + } + + // published => update published version infos, + // else if unpublished then clear published version infos + if (entity.Published) + { + dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto + { + VersionId = dto.VersionId, + Newest = true, + NodeId = dto.NodeId, + Published = true + }; + ((Content)entity).PublishedVersionGuid = dto.VersionId; + } + else if (publishedStateChanged) + { + dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto + { + VersionId = default(Guid), + Newest = false, + NodeId = dto.NodeId, + Published = false + }; + ((Content)entity).PublishedVersionGuid = default(Guid); + } + + entity.ResetDirtyProperties(); + } + + + #endregion + + #region Implementation of IContentRepository + + 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 + + var sqlClause = GetBaseQuery(false); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate() + .Where(x => x.Published) + .OrderBy(x => x.Level, SqlSyntax) + .OrderBy(x => x.SortOrder, SqlSyntax); + + //NOTE: This doesn't allow properties to be part of the query + var dtos = Database.Fetch(sql); + + 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) + 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); + } + } + } + + public int CountPublished() + { + var sql = GetBaseQuery(true).Where(x => x.Trashed == false) + .Where(x => x.Published == true); + return Database.ExecuteScalar(sql); + } + + public void ReplaceContentPermissions(EntityPermissionSet permissionSet) + { + var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); + repo.ReplaceEntityPermissions(permissionSet); + } + + public void ClearPublished(IContent content) + { + // race cond! + var documentDtos = Database.Fetch("WHERE nodeId=@id AND published=@published", new { id = content.Id, published = true }); + foreach (var documentDto in documentDtos) + { + documentDto.Published = false; + Database.Update(documentDto); + } + } + + /// + /// Assigns a single permission to the current content item for the specified user ids + /// + /// + /// + /// + public void AssignEntityPermission(IContent entity, char permission, IEnumerable userIds) + { + var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); + repo.AssignEntityPermission(entity, permission, userIds); + } + + public IEnumerable GetPermissionsForEntity(int entityId) + { + var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); + 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 + /// + /// Query to excute + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") + { + + //NOTE: This uses the GetBaseQuery method but that does not take into account the required 'newest' field which is + // what we always require for a paged result, so we'll ensure it's included in the filter + + var args = new List(); + var sbWhere = new StringBuilder("AND (cmsDocument.newest = 1)"); + + if (filter.IsNullOrWhiteSpace() == false) + { + sbWhere.Append(" AND (cmsDocument." + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ")"); + args.Add("%" + filter + "%"); + } + + Func> filterCallback = () => new Tuple(sbWhere.ToString(), args.ToArray()); + + return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, + new Tuple("cmsDocument", "nodeId"), + ProcessQuery, orderBy, orderDirection, orderBySystemField, + filterCallback); + + } + + #endregion + + #region IRecycleBinRepository members + + protected override int RecycleBinId + { + get { return Constants.System.RecycleBinContent; } + } + + #endregion + + protected override string GetDatabaseFieldNameForOrderBy(string orderBy) + { + //Some custom ones + switch (orderBy.ToUpperInvariant()) + { + case "NAME": + return "cmsDocument.text"; + case "UPDATER": + //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter + return "cmsDocument.documentUser"; + } + + return base.GetDatabaseFieldNameForOrderBy(orderBy); + } + + private IEnumerable ProcessQuery(Sql sql) + { + //NOTE: This doesn't allow properties to be part of the query + var dtos = Database.Fetch(sql); + + //nothing found + if (dtos.Any() == false) return Enumerable.Empty(); + + //content types + //NOTE: This should be ok for an SQL 'IN' statement, there shouldn't be an insane amount of content types + var contentTypes = _contentTypeRepository.GetAll(dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray()) + .ToArray(); + + + var ids = dtos + .Where(dto => dto.TemplateId.HasValue && dto.TemplateId.Value > 0) + .Select(x => x.TemplateId.Value).ToArray(); + + //NOTE: This should be ok for an SQL 'IN' statement, there shouldn't be an insane amount of content types + var templates = ids.Length == 0 ? Enumerable.Empty() : _templateRepository.GetAll(ids).ToArray(); + + var dtosWithContentTypes = dtos + //This select into and null check are required because we don't have a foreign damn key on the contentType column + // http://issues.umbraco.org/issue/U4-5503 + .Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentVersionDto.ContentDto.ContentTypeId) }) + .Where(x => x.contentType != null) + .ToArray(); + + //Go get the property data for each document + var docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition( + d.dto.NodeId, + d.dto.VersionId, + d.dto.ContentVersionDto.VersionDate, + d.dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, + d.contentType)); + + var propertyData = GetPropertyCollection(sql, docDefs); + + 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)), + propertyData[d.dto.NodeId])); + } + + /// + /// Private method to create a content object from a DocumentDto, which is used by Get and GetByVersion. + /// + /// + /// + /// + /// + /// + private IContent CreateContentFromDto(DocumentDto dto, + IContentType contentType, + ITemplate template, + Models.PropertyCollection propCollection) + { + var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId); + var content = factory.BuildEntity(dto); + + //Check if template id is set on DocumentDto, and get ITemplate if it is. + if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) + { + content.Template = template ?? _templateRepository.Get(dto.TemplateId.Value); + } + else + { + //ensure there isn't one set. + content.Template = null; + } + + content.Properties = propCollection; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)content).ResetDirtyProperties(false); + return content; + } + + /// + /// Private method to create a content object from a DocumentDto, which is used by Get and GetByVersion. + /// + /// + /// + /// + /// + private IContent CreateContentFromDto(DocumentDto dto, Guid versionId, Sql docSql) + { + var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); + + var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId); + var content = factory.BuildEntity(dto); + + //Check if template id is set on DocumentDto, and get ITemplate if it is. + if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) + { + content.Template = _templateRepository.Get(dto.TemplateId.Value); + } + + var docDef = new DocumentDefinition(dto.NodeId, versionId, content.UpdateDate, content.CreateDate, contentType); + + var properties = GetPropertyCollection(docSql, new[] { docDef }); + + content.Properties = properties[dto.NodeId]; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)content).ResetDirtyProperties(false); + return content; + } + + private string EnsureUniqueNodeName(int parentId, string nodeName, int id = 0) + { + if (EnsureUniqueNaming == false) + return nodeName; + + var sql = new Sql(); + sql.Select("*") + .From() + .Where(x => x.NodeObjectType == NodeObjectTypeId && x.ParentId == parentId && x.Text.StartsWith(nodeName)); + + int uniqueNumber = 1; + var currentName = nodeName; + + var dtos = Database.Fetch(sql); + if (dtos.Any()) + { + var results = dtos.OrderBy(x => x.Text, new SimilarNodeNameComparer()); + foreach (var dto in results) + { + if (id != 0 && id == dto.NodeId) continue; + + if (dto.Text.ToLowerInvariant().Equals(currentName.ToLowerInvariant())) + { + currentName = nodeName + string.Format(" ({0})", uniqueNumber); + uniqueNumber++; + } + } + } + + return currentName; + } + + /// + /// Dispose disposable properties + /// + /// + /// Ensure the unit of work is disposed + /// + protected override void DisposeResources() + { + _contentTypeRepository.Dispose(); + _templateRepository.Dispose(); + _tagRepository.Dispose(); + _contentPreviewRepository.Dispose(); + _contentXmlRepository.Dispose(); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index 9d3fcbb40b..0662581b15 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -9,84 +9,85 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IContentRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository - { - /// - /// Get the count of published items - /// - /// - /// - /// We require this on the repo because the IQuery{IContent} cannot supply the 'newest' parameter - /// - int CountPublished(); + public interface IContentRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository + { + /// + /// Get the count of published items + /// + /// + /// + /// We require this on the repo because the IQuery{IContent} cannot supply the 'newest' parameter + /// + int CountPublished(); - /// - /// 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. - /// - /// - void ReplaceContentPermissions(EntityPermissionSet permissionSet); + /// + /// 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. + /// + /// + void ReplaceContentPermissions(EntityPermissionSet permissionSet); - /// - /// Clears the published flag for a content. - /// - /// - void ClearPublished(IContent content); + /// + /// Clears the published flag for a content. + /// + /// + void ClearPublished(IContent content); - /// - /// Gets all published Content by the specified query - /// - /// Query to execute against published versions - /// An enumerable list of - IEnumerable GetByPublishedVersion(IQuery query); + /// + /// Gets all published Content by the specified query + /// + /// Query to execute against published versions + /// An enumerable list of + IEnumerable GetByPublishedVersion(IQuery query); - /// - /// Assigns a single permission to the current content item for the specified user ids - /// - /// - /// - /// - void AssignEntityPermission(IContent entity, char permission, IEnumerable userIds); + /// + /// Assigns a single permission to the current content item for the specified user ids + /// + /// + /// + /// + void AssignEntityPermission(IContent entity, char permission, IEnumerable userIds); - /// - /// Gets the list of permissions for the content item - /// - /// - /// - IEnumerable GetPermissionsForEntity(int entityId); + /// + /// Gets the list of permissions for the content item + /// + /// + /// + IEnumerable GetPermissionsForEntity(int entityId); - /// - /// Used to add/update published xml for the content item - /// - /// - /// - void AddOrUpdateContentXml(IContent content, Func xml); + /// + /// Used to add/update published xml for the content item + /// + /// + /// + void AddOrUpdateContentXml(IContent content, Func xml); - /// - /// Used to remove the content xml for a content item - /// - /// - void DeleteContentXml(IContent content); + /// + /// Used to remove the content xml for a content item + /// + /// + void DeleteContentXml(IContent content); - /// - /// Used to add/update preview xml for the content item - /// - /// - /// - void AddOrUpdatePreviewXml(IContent content, Func xml); + /// + /// Used to add/update preview xml for the content item + /// + /// + /// + void AddOrUpdatePreviewXml(IContent content, Func xml); - /// - /// Gets paged content results - /// - /// Query to excute - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects - IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string filter = ""); - } + /// + /// Gets paged content results + /// + /// Query to excute + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs index 46bce6a03c..358a099ea0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs @@ -7,35 +7,36 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IMediaRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository - { - - /// - /// Used to add/update published xml for the media item - /// - /// - /// - void AddOrUpdateContentXml(IMedia content, Func xml); + public interface IMediaRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository + { - /// - /// Used to add/update preview xml for the content item - /// - /// - /// - void AddOrUpdatePreviewXml(IMedia content, Func xml); + /// + /// Used to add/update published xml for the media item + /// + /// + /// + void AddOrUpdateContentXml(IMedia content, Func xml); - /// - /// Gets paged media results - /// - /// Query to excute - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects - IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string filter = ""); - } + /// + /// Used to add/update preview xml for the content item + /// + /// + /// + void AddOrUpdatePreviewXml(IMedia content, Func xml); + + /// + /// Gets paged media results + /// + /// Query to excute + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs index 9cb74d1806..c59bf5d192 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs @@ -9,69 +9,70 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IMemberRepository : IRepositoryVersionable, IDeleteMediaFilesRepository - { - /// - /// Finds members in a given role - /// - /// - /// - /// - /// - IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + public interface IMemberRepository : IRepositoryVersionable, IDeleteMediaFilesRepository + { + /// + /// Finds members in a given role + /// + /// + /// + /// + /// + IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); - /// - /// Get all members in a specific group - /// - /// - /// - IEnumerable GetByMemberGroup(string groupName); + /// + /// Get all members in a specific group + /// + /// + /// + IEnumerable GetByMemberGroup(string groupName); - /// - /// Checks if a member with the username exists - /// - /// - /// - bool Exists(string username); + /// + /// Checks if a member with the username exists + /// + /// + /// + bool Exists(string username); - /// - /// Gets the count of items based on a complex query - /// - /// - /// - int GetCountByQuery(IQuery query); + /// + /// Gets the count of items based on a complex query + /// + /// + /// + int GetCountByQuery(IQuery query); - /// - /// Gets paged member results - /// - /// - /// - /// - /// - /// - /// - /// - /// - IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string filter = ""); + /// + /// Gets paged member results + /// + /// The query. + /// Index of the page. + /// Size of the page. + /// The total records. + /// The order by column + /// The order direction. + /// Flag to indicate when ordering by system field + /// Search query + /// + IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); - //IEnumerable GetPagedResultsByQuery( - // Sql sql, int pageIndex, int pageSize, out int totalRecords, - // Func, int[]> resolveIds); + //IEnumerable GetPagedResultsByQuery( + // Sql sql, int pageIndex, int pageSize, out int totalRecords, + // Func, int[]> resolveIds); - /// - /// Used to add/update published xml for the media item - /// - /// - /// - void AddOrUpdateContentXml(IMember content, Func xml); + /// + /// Used to add/update published xml for the media item + /// + /// + /// + void AddOrUpdateContentXml(IMember content, Func xml); - /// - /// Used to add/update preview xml for the content item - /// - /// - /// - void AddOrUpdatePreviewXml(IMember content, Func xml); + /// + /// Used to add/update preview xml for the content item + /// + /// + /// + void AddOrUpdatePreviewXml(IMember content, Func xml); - } + } } \ 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 162ad88ca0..d425f0afda 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -22,574 +22,575 @@ using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { - /// - /// Represents a repository for doing CRUD operations for - /// - internal class MediaRepository : RecycleBinRepository, IMediaRepository - { - private readonly IMediaTypeRepository _mediaTypeRepository; - private readonly ITagRepository _tagRepository; - private readonly ContentXmlRepository _contentXmlRepository; - private readonly ContentPreviewRepository _contentPreviewRepository; - - public MediaRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, IContentSection contentSection) - : base(work, cache, logger, sqlSyntax, contentSection) - { - if (mediaTypeRepository == null) throw new ArgumentNullException("mediaTypeRepository"); - if (tagRepository == null) throw new ArgumentNullException("tagRepository"); - _mediaTypeRepository = mediaTypeRepository; - _tagRepository = tagRepository; - _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); - _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); - EnsureUniqueNaming = contentSection.EnsureUniqueNaming; - } - - public bool EnsureUniqueNaming { get; private set; } - - #region Overrides of RepositoryBase - - protected override IMedia PerformGet(int id) - { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(x => x.VersionDate); - - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - return null; - - var content = CreateMediaFromDto(dto, dto.VersionId, sql); - - return content; - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - var sql = GetBaseQuery(false); - if (ids.Any()) - { - sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); - } - - return ProcessQuery(sql); - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate() - .OrderBy(x => x.SortOrder); - - return ProcessQuery(sql); - } - - #endregion - - #region Overrides of PetaPocoRepositoryBase - - protected override Sql GetBaseQuery(bool isCount) - { - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId); - return sql; - } - - protected override string GetBaseWhereClause() - { - return "umbracoNode.id = @Id"; - } - - protected override IEnumerable GetDeleteClauses() - { - var list = new List - { - "DELETE FROM cmsTask WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", - "DELETE FROM umbracoRelation WHERE parentId = @Id", - "DELETE FROM umbracoRelation WHERE childId = @Id", - "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", - "DELETE FROM cmsDocument WHERE nodeId = @Id", - "DELETE FROM cmsPropertyData WHERE contentNodeId = @Id", - "DELETE FROM cmsPreviewXml WHERE nodeId = @Id", - "DELETE FROM cmsContentVersion WHERE ContentId = @Id", - "DELETE FROM cmsContentXml WHERE nodeId = @Id", - "DELETE FROM cmsContent WHERE nodeId = @Id", - "DELETE FROM umbracoNode WHERE id = @Id" - }; - return list; - } - - protected override Guid NodeObjectTypeId - { - get { return new Guid(Constants.ObjectTypes.Media); } - } - - #endregion - - #region Overrides of VersionableRepositoryBase - - public override IMedia GetByVersion(Guid versionId) - { - var sql = GetBaseQuery(false); - sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(x => x.VersionDate); - - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - return null; - - var mediaType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); - - var factory = new MediaFactory(mediaType, NodeObjectTypeId, dto.NodeId); - var media = factory.BuildEntity(dto); - - var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.VersionId, media.UpdateDate, media.CreateDate, mediaType) }); - - media.Properties = properties[dto.NodeId]; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)media).ResetDirtyProperties(false); - 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 = new 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 = new 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) - { - var query = Query.Builder; - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); - } - else - { - foreach (var contentTypeId in contentTypeIds) - { - //copy local - var id = contentTypeId; - var query = Query.Builder.Where(x => x.ContentTypeId == id && x.Trashed == false); - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); - } - } - - tr.Complete(); - } - } - - private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, Transaction 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); - - 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(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)); - } - - protected override void PerformDeleteVersion(int id, Guid versionId) - { - Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); - } - - #endregion - - #region Unit of Work Implementation - - protected override void PersistNewItem(IMedia entity) - { - ((Models.Media)entity).AddingEntity(); - - //Ensure unique name on the same level - entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name); - - //Ensure that strings don't contain characters that are invalid in XML - entity.SanitizeEntityPropertiesForXmlStorage(); - - var factory = new MediaFactory(NodeObjectTypeId, entity.Id); - var dto = factory.BuildDto(entity); - - //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 level = parent.Level + 1; - var maxSortOrder = Database.ExecuteScalar( - "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", - new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); - var sortOrder = maxSortOrder + 1; - - //Create the (base) node data - umbracoNode - var nodeDto = dto.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); - - //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; - - //Create the Content specific data - cmsContent - var contentDto = dto.ContentDto; - contentDto.NodeId = nodeDto.NodeId; - Database.Insert(contentDto); - - //Create the first version - cmsContentVersion - //Assumes a new Version guid and Version date (modified date) has been set - dto.NodeId = nodeDto.NodeId; - Database.Insert(dto); - - //Create the PropertyData for this version - cmsPropertyData - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); - var keyDictionary = new Dictionary(); - - //Add Properties - foreach (var propertyDataDto in propertyDataDtos) - { - var primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } - - //Update Properties with its newly set Id - foreach (var property in entity.Properties) - { - property.Id = keyDictionary[property.PropertyTypeId]; - } - - UpdatePropertyTags(entity, _tagRepository); - - entity.ResetDirtyProperties(); - } - - protected override void PersistUpdatedItem(IMedia entity) - { - //Updates Modified date - ((Models.Media)entity).UpdatingEntity(); - - //Ensure unique name on the same level - entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id); - - //Ensure that strings don't contain characters that are invalid in XML - entity.SanitizeEntityPropertiesForXmlStorage(); - - //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 }); - 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; - } - - 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 }); - factory.SetPrimaryKey(contentDto.PrimaryKey); - var dto = factory.BuildDto(entity); - - //Updates the (base) node data - umbracoNode - var nodeDto = dto.ContentDto.NodeDto; - var o = Database.Update(nodeDto); - - //Only update this DTO if the contentType has actually changed - if (contentDto.ContentTypeId != entity.ContentTypeId) - { - //Create the Content specific data - cmsContent - var newContentDto = dto.ContentDto; - Database.Update(newContentDto); - } - - //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 }); - dto.Id = contentVerDto.Id; - //Updates the current version - cmsContentVersion - //Assumes a Version guid exists and Version date (modified date) has been set/updated - Database.Update(dto); - - //Create the PropertyData for this version - cmsPropertyData - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); - var keyDictionary = new Dictionary(); - - //Add Properties - foreach (var propertyDataDto in propertyDataDtos) - { - if (propertyDataDto.Id > 0) - { - Database.Update(propertyDataDto); - } - else - { - int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } - } - - //Update Properties with its newly set Id - if (keyDictionary.Any()) - { - foreach (var property in entity.Properties) - { - property.Id = keyDictionary[property.PropertyTypeId]; - } - } - - UpdatePropertyTags(entity, _tagRepository); - - entity.ResetDirtyProperties(); - } - - #endregion - - #region IRecycleBinRepository members - - protected override int RecycleBinId - { - get { return Constants.System.RecycleBinMedia; } - } - - #endregion - - /// - /// Gets paged media results - /// - /// Query to excute - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects - public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string filter = "") - { - var args = new List(); - var sbWhere = new StringBuilder(); - Func> filterCallback = null; - if (filter.IsNullOrWhiteSpace() == false) - { - sbWhere.Append("AND (umbracoNode." + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ")"); - args.Add("%" + filter + "%"); - filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray()); - } - - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - new Tuple("cmsContentVersion", "contentId"), - ProcessQuery, orderBy, orderDirection, - filterCallback); - - } - - private IEnumerable ProcessQuery(Sql sql) - { - //NOTE: This doesn't allow properties to be part of the query - var dtos = Database.Fetch(sql); - - var ids = dtos.Select(x => x.ContentDto.ContentTypeId).ToArray(); - - //content types - var contentTypes = ids.Length == 0 ? Enumerable.Empty() : _mediaTypeRepository.GetAll(ids).ToArray(); - - var dtosWithContentTypes = dtos - //This select into and null check are required because we don't have a foreign damn key on the contentType column - // http://issues.umbraco.org/issue/U4-5503 - .Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentDto.ContentTypeId) }) - .Where(x => x.contentType != null) - .ToArray(); - - //Go get the property data for each document - var docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition( - d.dto.NodeId, - d.dto.VersionId, - d.dto.VersionDate, - d.dto.ContentDto.NodeDto.CreateDate, - d.contentType)) - .ToArray(); - - var propertyData = GetPropertyCollection(sql, docDefs); - - return dtosWithContentTypes.Select(d => CreateMediaFromDto( - d.dto, - contentTypes.First(ct => ct.Id == d.dto.ContentDto.ContentTypeId), - propertyData[d.dto.NodeId])); - } - - /// - /// Private method to create a media object from a ContentDto - /// - /// - /// - /// - /// - private IMedia CreateMediaFromDto(ContentVersionDto dto, - IMediaType contentType, - PropertyCollection propCollection) - { - var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId); - var media = factory.BuildEntity(dto); - - media.Properties = propCollection; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)media).ResetDirtyProperties(false); - return media; - } - - /// - /// Private method to create a media object from a ContentDto - /// - /// - /// - /// - /// - private IMedia CreateMediaFromDto(ContentVersionDto dto, Guid versionId, Sql docSql) - { - var contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); - - var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId); - var media = factory.BuildEntity(dto); - - var docDef = new DocumentDefinition(dto.NodeId, versionId, media.UpdateDate, media.CreateDate, contentType); - - var properties = GetPropertyCollection(docSql, new[] { docDef }); - - media.Properties = properties[dto.NodeId]; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)media).ResetDirtyProperties(false); - return media; - } - - private string EnsureUniqueNodeName(int parentId, string nodeName, int id = 0) - { - if (EnsureUniqueNaming == false) - return nodeName; - - var sql = new Sql(); - sql.Select("*") - .From() - .Where(x => x.NodeObjectType == NodeObjectTypeId && x.ParentId == parentId && x.Text.StartsWith(nodeName)); - - int uniqueNumber = 1; - var currentName = nodeName; - - var dtos = Database.Fetch(sql); - if (dtos.Any()) - { - var results = dtos.OrderBy(x => x.Text, new SimilarNodeNameComparer()); - foreach (var dto in results) - { - if (id != 0 && id == dto.NodeId) continue; - - if (dto.Text.ToLowerInvariant().Equals(currentName.ToLowerInvariant())) - { - currentName = nodeName + string.Format(" ({0})", uniqueNumber); - uniqueNumber++; - } - } - } - - return currentName; - } - - /// - /// Dispose disposable properties - /// - /// - /// Ensure the unit of work is disposed - /// - protected override void DisposeResources() - { - _mediaTypeRepository.Dispose(); - _tagRepository.Dispose(); - _contentXmlRepository.Dispose(); - _contentPreviewRepository.Dispose(); - } - } + /// + /// Represents a repository for doing CRUD operations for + /// + internal class MediaRepository : RecycleBinRepository, IMediaRepository + { + private readonly IMediaTypeRepository _mediaTypeRepository; + private readonly ITagRepository _tagRepository; + private readonly ContentXmlRepository _contentXmlRepository; + private readonly ContentPreviewRepository _contentPreviewRepository; + + public MediaRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, IContentSection contentSection) + : base(work, cache, logger, sqlSyntax, contentSection) + { + if (mediaTypeRepository == null) throw new ArgumentNullException("mediaTypeRepository"); + if (tagRepository == null) throw new ArgumentNullException("tagRepository"); + _mediaTypeRepository = mediaTypeRepository; + _tagRepository = tagRepository; + _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); + _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); + EnsureUniqueNaming = contentSection.EnsureUniqueNaming; + } + + public bool EnsureUniqueNaming { get; private set; } + + #region Overrides of RepositoryBase + + protected override IMedia PerformGet(int id) + { + var sql = GetBaseQuery(false); + sql.Where(GetBaseWhereClause(), new { Id = id }); + sql.OrderByDescending(x => x.VersionDate); + + var dto = Database.Fetch(sql).FirstOrDefault(); + + if (dto == null) + return null; + + var content = CreateMediaFromDto(dto, dto.VersionId, sql); + + return content; + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + var sql = GetBaseQuery(false); + if (ids.Any()) + { + sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); + } + + return ProcessQuery(sql); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + var sqlClause = GetBaseQuery(false); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate() + .OrderBy(x => x.SortOrder); + + return ProcessQuery(sql); + } + + #endregion + + #region Overrides of PetaPocoRepositoryBase + + protected override Sql GetBaseQuery(bool isCount) + { + var sql = new Sql(); + sql.Select(isCount ? "COUNT(*)" : "*") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; + } + + protected override string GetBaseWhereClause() + { + return "umbracoNode.id = @Id"; + } + + protected override IEnumerable GetDeleteClauses() + { + var list = new List + { + "DELETE FROM cmsTask WHERE nodeId = @Id", + "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", + "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", + "DELETE FROM umbracoRelation WHERE parentId = @Id", + "DELETE FROM umbracoRelation WHERE childId = @Id", + "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", + "DELETE FROM cmsDocument WHERE nodeId = @Id", + "DELETE FROM cmsPropertyData WHERE contentNodeId = @Id", + "DELETE FROM cmsPreviewXml WHERE nodeId = @Id", + "DELETE FROM cmsContentVersion WHERE ContentId = @Id", + "DELETE FROM cmsContentXml WHERE nodeId = @Id", + "DELETE FROM cmsContent WHERE nodeId = @Id", + "DELETE FROM umbracoNode WHERE id = @Id" + }; + return list; + } + + protected override Guid NodeObjectTypeId + { + get { return new Guid(Constants.ObjectTypes.Media); } + } + + #endregion + + #region Overrides of VersionableRepositoryBase + + public override IMedia GetByVersion(Guid versionId) + { + var sql = GetBaseQuery(false); + sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); + sql.OrderByDescending(x => x.VersionDate); + + var dto = Database.Fetch(sql).FirstOrDefault(); + + if (dto == null) + return null; + + var mediaType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); + + var factory = new MediaFactory(mediaType, NodeObjectTypeId, dto.NodeId); + var media = factory.BuildEntity(dto); + + var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.VersionId, media.UpdateDate, media.CreateDate, mediaType) }); + + media.Properties = properties[dto.NodeId]; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)media).ResetDirtyProperties(false); + 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 = new 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 = new 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) + { + var query = Query.Builder; + RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); + } + else + { + foreach (var contentTypeId in contentTypeIds) + { + //copy local + var id = contentTypeId; + var query = Query.Builder.Where(x => x.ContentTypeId == id && x.Trashed == false); + RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); + } + } + + tr.Complete(); + } + } + + private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, Transaction 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, orderBySystemField: 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(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)); + } + + protected override void PerformDeleteVersion(int id, Guid versionId) + { + Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); + } + + #endregion + + #region Unit of Work Implementation + + protected override void PersistNewItem(IMedia entity) + { + ((Models.Media)entity).AddingEntity(); + + //Ensure unique name on the same level + entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name); + + //Ensure that strings don't contain characters that are invalid in XML + entity.SanitizeEntityPropertiesForXmlStorage(); + + var factory = new MediaFactory(NodeObjectTypeId, entity.Id); + var dto = factory.BuildDto(entity); + + //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 level = parent.Level + 1; + var maxSortOrder = Database.ExecuteScalar( + "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", + new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); + var sortOrder = maxSortOrder + 1; + + //Create the (base) node data - umbracoNode + var nodeDto = dto.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); + + //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; + + //Create the Content specific data - cmsContent + var contentDto = dto.ContentDto; + contentDto.NodeId = nodeDto.NodeId; + Database.Insert(contentDto); + + //Create the first version - cmsContentVersion + //Assumes a new Version guid and Version date (modified date) has been set + dto.NodeId = nodeDto.NodeId; + Database.Insert(dto); + + //Create the PropertyData for this version - cmsPropertyData + var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); + var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); + var keyDictionary = new Dictionary(); + + //Add Properties + foreach (var propertyDataDto in propertyDataDtos) + { + var primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); + keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); + } + + //Update Properties with its newly set Id + foreach (var property in entity.Properties) + { + property.Id = keyDictionary[property.PropertyTypeId]; + } + + UpdatePropertyTags(entity, _tagRepository); + + entity.ResetDirtyProperties(); + } + + protected override void PersistUpdatedItem(IMedia entity) + { + //Updates Modified date + ((Models.Media)entity).UpdatingEntity(); + + //Ensure unique name on the same level + entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id); + + //Ensure that strings don't contain characters that are invalid in XML + entity.SanitizeEntityPropertiesForXmlStorage(); + + //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 }); + 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; + } + + 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 }); + factory.SetPrimaryKey(contentDto.PrimaryKey); + var dto = factory.BuildDto(entity); + + //Updates the (base) node data - umbracoNode + var nodeDto = dto.ContentDto.NodeDto; + var o = Database.Update(nodeDto); + + //Only update this DTO if the contentType has actually changed + if (contentDto.ContentTypeId != entity.ContentTypeId) + { + //Create the Content specific data - cmsContent + var newContentDto = dto.ContentDto; + Database.Update(newContentDto); + } + + //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 }); + dto.Id = contentVerDto.Id; + //Updates the current version - cmsContentVersion + //Assumes a Version guid exists and Version date (modified date) has been set/updated + Database.Update(dto); + + //Create the PropertyData for this version - cmsPropertyData + var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); + var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); + var keyDictionary = new Dictionary(); + + //Add Properties + foreach (var propertyDataDto in propertyDataDtos) + { + if (propertyDataDto.Id > 0) + { + Database.Update(propertyDataDto); + } + else + { + int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); + keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); + } + } + + //Update Properties with its newly set Id + if (keyDictionary.Any()) + { + foreach (var property in entity.Properties) + { + property.Id = keyDictionary[property.PropertyTypeId]; + } + } + + UpdatePropertyTags(entity, _tagRepository); + + entity.ResetDirtyProperties(); + } + + #endregion + + #region IRecycleBinRepository members + + protected override int RecycleBinId + { + get { return Constants.System.RecycleBinMedia; } + } + + #endregion + + /// + /// Gets paged media results + /// + /// Query to excute + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") + { + var args = new List(); + var sbWhere = new StringBuilder(); + Func> filterCallback = null; + if (filter.IsNullOrWhiteSpace() == false) + { + sbWhere.Append("AND (umbracoNode." + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ")"); + args.Add("%" + filter + "%"); + filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray()); + } + + return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, + new Tuple("cmsContentVersion", "contentId"), + ProcessQuery, orderBy, orderDirection, orderBySystemField, + filterCallback); + + } + + private IEnumerable ProcessQuery(Sql sql) + { + //NOTE: This doesn't allow properties to be part of the query + var dtos = Database.Fetch(sql); + + var ids = dtos.Select(x => x.ContentDto.ContentTypeId).ToArray(); + + //content types + var contentTypes = ids.Length == 0 ? Enumerable.Empty() : _mediaTypeRepository.GetAll(ids).ToArray(); + + var dtosWithContentTypes = dtos + //This select into and null check are required because we don't have a foreign damn key on the contentType column + // http://issues.umbraco.org/issue/U4-5503 + .Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentDto.ContentTypeId) }) + .Where(x => x.contentType != null) + .ToArray(); + + //Go get the property data for each document + var docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition( + d.dto.NodeId, + d.dto.VersionId, + d.dto.VersionDate, + d.dto.ContentDto.NodeDto.CreateDate, + d.contentType)) + .ToArray(); + + var propertyData = GetPropertyCollection(sql, docDefs); + + return dtosWithContentTypes.Select(d => CreateMediaFromDto( + d.dto, + contentTypes.First(ct => ct.Id == d.dto.ContentDto.ContentTypeId), + propertyData[d.dto.NodeId])); + } + + /// + /// Private method to create a media object from a ContentDto + /// + /// + /// + /// + /// + private IMedia CreateMediaFromDto(ContentVersionDto dto, + IMediaType contentType, + PropertyCollection propCollection) + { + var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId); + var media = factory.BuildEntity(dto); + + media.Properties = propCollection; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)media).ResetDirtyProperties(false); + return media; + } + + /// + /// Private method to create a media object from a ContentDto + /// + /// + /// + /// + /// + private IMedia CreateMediaFromDto(ContentVersionDto dto, Guid versionId, Sql docSql) + { + var contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); + + var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId); + var media = factory.BuildEntity(dto); + + var docDef = new DocumentDefinition(dto.NodeId, versionId, media.UpdateDate, media.CreateDate, contentType); + + var properties = GetPropertyCollection(docSql, new[] { docDef }); + + media.Properties = properties[dto.NodeId]; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)media).ResetDirtyProperties(false); + return media; + } + + private string EnsureUniqueNodeName(int parentId, string nodeName, int id = 0) + { + if (EnsureUniqueNaming == false) + return nodeName; + + var sql = new Sql(); + sql.Select("*") + .From() + .Where(x => x.NodeObjectType == NodeObjectTypeId && x.ParentId == parentId && x.Text.StartsWith(nodeName)); + + int uniqueNumber = 1; + var currentName = nodeName; + + var dtos = Database.Fetch(sql); + if (dtos.Any()) + { + var results = dtos.OrderBy(x => x.Text, new SimilarNodeNameComparer()); + foreach (var dto in results) + { + if (id != 0 && id == dto.NodeId) continue; + + if (dto.Text.ToLowerInvariant().Equals(currentName.ToLowerInvariant())) + { + currentName = nodeName + string.Format(" ({0})", uniqueNumber); + uniqueNumber++; + } + } + } + + return currentName; + } + + /// + /// Dispose disposable properties + /// + /// + /// Ensure the unit of work is disposed + /// + protected override void DisposeResources() + { + _mediaTypeRepository.Dispose(); + _tagRepository.Dispose(); + _contentXmlRepository.Dispose(); + _contentPreviewRepository.Dispose(); + } + } } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index ac5529b523..c103d24b2d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -23,747 +23,748 @@ using Umbraco.Core.Dynamics; namespace Umbraco.Core.Persistence.Repositories { - /// - /// Represents a repository for doing CRUD operations for - /// - internal class MemberRepository : VersionableRepositoryBase, IMemberRepository - { - private readonly IMemberTypeRepository _memberTypeRepository; - private readonly ITagRepository _tagRepository; - private readonly IMemberGroupRepository _memberGroupRepository; - private readonly ContentXmlRepository _contentXmlRepository; - private readonly ContentPreviewRepository _contentPreviewRepository; - - public MemberRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, IContentSection contentSection) - : base(work, cache, logger, sqlSyntax, contentSection) - { - if (memberTypeRepository == null) throw new ArgumentNullException("memberTypeRepository"); - if (tagRepository == null) throw new ArgumentNullException("tagRepository"); - _memberTypeRepository = memberTypeRepository; - _tagRepository = tagRepository; - _memberGroupRepository = memberGroupRepository; - _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); - _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); - } - - #region Overrides of RepositoryBase - - protected override IMember PerformGet(int id) - { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(x => x.VersionDate); - - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - return null; - - var content = CreateMemberFromDto(dto, dto.ContentVersionDto.VersionId, sql); - - return content; - - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - var sql = GetBaseQuery(false); - if (ids.Any()) - { - sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); - } - - return ProcessQuery(sql); - - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - var baseQuery = GetBaseQuery(false); - - //check if the query is based on properties or not - - var wheres = query.GetWhereClauses(); - //this is a pretty rudimentary check but wil work, we just need to know if this query requires property - // level queries - if (wheres.Any(x => x.Item1.Contains("cmsPropertyType"))) - { - var sqlWithProps = GetNodeIdQueryWithPropertyData(); - var translator = new SqlTranslator(sqlWithProps, query); - var sql = translator.Translate(); - - baseQuery.Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)) - .OrderBy(x => x.SortOrder); - - return ProcessQuery(baseQuery); - } - else - { - var translator = new SqlTranslator(baseQuery, query); - var sql = translator.Translate() - .OrderBy(x => x.SortOrder); - - return ProcessQuery(sql); - } - - } - - #endregion - - #region Overrides of PetaPocoRepositoryBase - - protected override Sql GetBaseQuery(bool isCount) - { - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - //We're joining the type so we can do a query against the member type - not sure if this adds much overhead or not? - // the execution plan says it doesn't so we'll go with that and in that case, it might be worth joining the content - // types by default on the document and media repo's so we can query by content type there too. - .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId); - return sql; - - } - - protected override string GetBaseWhereClause() - { - return "umbracoNode.id = @Id"; - } - - protected Sql GetNodeIdQueryWithPropertyData() - { - var sql = new Sql(); - sql.Select("DISTINCT(umbracoNode.id)") - .From() - .InnerJoin().On(left => left.NodeId, right => right.NodeId) - .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) - .InnerJoin().On(left => left.NodeId, right => right.NodeId) - .InnerJoin().On(left => left.NodeId, right => right.NodeId) - .LeftJoin().On(left => left.ContentTypeId, right => right.ContentTypeId) - .LeftJoin().On(left => left.DataTypeId, right => right.DataTypeId) - .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) - .Append("AND cmsPropertyData.versionId = cmsContentVersion.VersionId") - .Where(x => x.NodeObjectType == NodeObjectTypeId); - return sql; - } - - protected override IEnumerable GetDeleteClauses() - { - var list = new List - { - "DELETE FROM cmsTask WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", - "DELETE FROM umbracoRelation WHERE parentId = @Id", - "DELETE FROM umbracoRelation WHERE childId = @Id", - "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", - "DELETE FROM cmsPropertyData WHERE contentNodeId = @Id", - "DELETE FROM cmsMember2MemberGroup WHERE Member = @Id", - "DELETE FROM cmsMember WHERE nodeId = @Id", - "DELETE FROM cmsContentVersion WHERE ContentId = @Id", - "DELETE FROM cmsContentXml WHERE nodeId = @Id", - "DELETE FROM cmsContent WHERE nodeId = @Id", - "DELETE FROM umbracoNode WHERE id = @Id" - }; - return list; - } - - protected override Guid NodeObjectTypeId - { - get { return new Guid(Constants.ObjectTypes.Member); } - } - - #endregion - - #region Unit of Work Implementation - - protected override void PersistNewItem(IMember entity) - { - ((Member)entity).AddingEntity(); - - //Ensure that strings don't contain characters that are invalid in XML - entity.SanitizeEntityPropertiesForXmlStorage(); - - var factory = new MemberFactory(NodeObjectTypeId, entity.Id); - var dto = factory.BuildDto(entity); - - //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 = - Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE parentID = @ParentId AND nodeObjectType = @NodeObjectType", - new { ParentId = ((IUmbracoEntity)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); - - //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; - - //Create the Content specific data - cmsContent - var contentDto = dto.ContentVersionDto.ContentDto; - contentDto.NodeId = nodeDto.NodeId; - Database.Insert(contentDto); - - //Create the first version - cmsContentVersion - //Assumes a new Version guid and Version date (modified date) has been set - dto.ContentVersionDto.NodeId = nodeDto.NodeId; - Database.Insert(dto.ContentVersionDto); - - //Create the first entry in cmsMember - dto.NodeId = nodeDto.NodeId; - Database.Insert(dto); - - //Create the PropertyData for this version - cmsPropertyData - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - //Add Properties - // - don't try to save the property if it doesn't exist (or doesn't have an ID) on the content type - // - this can occur if the member type doesn't contain the built-in properties that the - // - member object contains. - var propsToPersist = entity.Properties.Where(x => x.PropertyType.HasIdentity).ToArray(); - var propertyDataDtos = propertyFactory.BuildDto(propsToPersist); - var keyDictionary = new Dictionary(); - - //Add Properties - foreach (var propertyDataDto in propertyDataDtos) - { - var primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } - - //Update Properties with its newly set Id - foreach (var property in propsToPersist) - { - property.Id = keyDictionary[property.PropertyTypeId]; - } - - UpdatePropertyTags(entity, _tagRepository); - - ((Member)entity).ResetDirtyProperties(); - } - - protected override void PersistUpdatedItem(IMember entity) - { - //Updates Modified date - ((Member)entity).UpdatingEntity(); - - //Ensure that strings don't contain characters that are invalid in XML - entity.SanitizeEntityPropertiesForXmlStorage(); - - var dirtyEntity = (ICanBeDirty) entity; - - //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 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; - } - - 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 }); - factory.SetPrimaryKey(contentDto.PrimaryKey); - var dto = factory.BuildDto(entity); - - //Updates the (base) node data - umbracoNode - var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; - var o = Database.Update(nodeDto); - - //Only update this DTO if the contentType has actually changed - if (contentDto.ContentTypeId != ((Member)entity).ContentTypeId) - { - //Create the Content specific data - cmsContent - var newContentDto = dto.ContentVersionDto.ContentDto; - Database.Update(newContentDto); - } - - //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 }); - dto.ContentVersionDto.Id = contentVerDto.Id; - //Updates the current version - cmsContentVersion - //Assumes a Version guid exists and Version date (modified date) has been set/updated - Database.Update(dto.ContentVersionDto); - - //Updates the cmsMember entry if it has changed - - //NOTE: these cols are the REAL column names in the db - var changedCols = new List(); - - if (dirtyEntity.IsPropertyDirty("Email")) - { - changedCols.Add("Email"); - } - if (dirtyEntity.IsPropertyDirty("Username")) - { - changedCols.Add("LoginName"); - } - // DO NOT update the password if it has not changed or if it is null or empty - if (dirtyEntity.IsPropertyDirty("RawPasswordValue") && entity.RawPasswordValue.IsNullOrWhiteSpace() == false) - { - changedCols.Add("Password"); - } - //only update the changed cols - if (changedCols.Count > 0) - { - Database.Update(dto, changedCols); - } - - //TODO ContentType for the Member entity - - //Create the PropertyData for this version - cmsPropertyData - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - var keyDictionary = new Dictionary(); - - //Add Properties - // - don't try to save the property if it doesn't exist (or doesn't have an ID) on the content type - // - this can occur if the member type doesn't contain the built-in properties that the - // - member object contains. - var propsToPersist = entity.Properties.Where(x => x.PropertyType.HasIdentity).ToArray(); - - var propertyDataDtos = propertyFactory.BuildDto(propsToPersist); - - foreach (var propertyDataDto in propertyDataDtos) - { - if (propertyDataDto.Id > 0) - { - Database.Update(propertyDataDto); - } - else - { - int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } - } - - //Update Properties with its newly set Id - if (keyDictionary.Any()) - { - foreach (var property in ((Member)entity).Properties) - { - property.Id = keyDictionary[property.PropertyTypeId]; - } - } - - UpdatePropertyTags(entity, _tagRepository); - - dirtyEntity.ResetDirtyProperties(); - } - - #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 memberObjectType = Guid.Parse(Constants.ObjectTypes.Member); - var subQuery = new 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 = new 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.Builder; - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); - } - else - { - foreach (var contentTypeId in contentTypeIds) - { - //copy local - var id = contentTypeId; - var query = Query.Builder.Where(x => x.ContentTypeId == id && x.Trashed == false); - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); - } - } - - tr.Complete(); - } - } - - private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, Transaction 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); - - 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(xmlItems, tr); - - processed += xmlItems.Length; - - pageIndex++; - } while (processed < total); - } - - public override IMember GetByVersion(Guid versionId) - { - var sql = GetBaseQuery(false); - sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(x => x.VersionDate); - - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - return null; - - var memberType = _memberTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); - - var factory = new MemberFactory(memberType, NodeObjectTypeId, dto.NodeId); - var media = factory.BuildEntity(dto); - - var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.ContentVersionDto.VersionId, media.UpdateDate, media.CreateDate, memberType) }); - - media.Properties = properties[dto.NodeId]; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)media).ResetDirtyProperties(false); - return media; - - } - - protected override void PerformDeleteVersion(int id, Guid versionId) - { - Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); - } - - #endregion - - public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - //get the group id - var grpQry = new Query().Where(group => group.Name.Equals(roleName)); - var memberGroup = _memberGroupRepository.GetByQuery(grpQry).FirstOrDefault(); - if (memberGroup == null) return Enumerable.Empty(); - - // get the members by username - var query = new Query(); - switch (matchType) - { - case StringPropertyMatchType.Exact: - query.Where(member => member.Username.Equals(usernameToMatch)); - break; - case StringPropertyMatchType.Contains: - query.Where(member => member.Username.Contains(usernameToMatch)); - break; - case StringPropertyMatchType.StartsWith: - query.Where(member => member.Username.StartsWith(usernameToMatch)); - break; - case StringPropertyMatchType.EndsWith: - query.Where(member => member.Username.EndsWith(usernameToMatch)); - break; - case StringPropertyMatchType.Wildcard: - query.Where(member => member.Username.SqlWildcard(usernameToMatch, TextColumnType.NVarchar)); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); - } - var matchedMembers = GetByQuery(query).ToArray(); - - var membersInGroup = new List(); - //then we need to filter the matched members that are in the role - //since the max sql params are 2100 on sql server, we'll reduce that to be safe for potentially other servers and run the queries in batches - var inGroups = matchedMembers.InGroupsOf(1000); - foreach (var batch in inGroups) - { - var memberIdBatch = batch.Select(x => x.Id); - var sql = new Sql().Select("*").From() - .Where(dto => dto.MemberGroup == memberGroup.Id) - .Where("Member IN (@memberIds)", new { memberIds = memberIdBatch }); - var memberIdsInGroup = Database.Fetch(sql) - .Select(x => x.Member).ToArray(); - - membersInGroup.AddRange(matchedMembers.Where(x => memberIdsInGroup.Contains(x.Id))); - } - - return membersInGroup; - - } - - /// - /// Get all members in a specific group - /// - /// - /// - public IEnumerable GetByMemberGroup(string groupName) - { - var grpQry = new Query().Where(group => group.Name.Equals(groupName)); - var memberGroup = _memberGroupRepository.GetByQuery(grpQry).FirstOrDefault(); - if (memberGroup == null) return Enumerable.Empty(); - - var subQuery = new Sql().Select("Member").From().Where(dto => dto.MemberGroup == memberGroup.Id); - - var sql = GetBaseQuery(false) - //TODO: An inner join would be better, though I've read that the query optimizer will always turn a - // subquery with an IN clause into an inner join anyways. - .Append(new Sql("WHERE umbracoNode.id IN (" + subQuery.SQL + ")", subQuery.Arguments)) - .OrderByDescending(x => x.VersionDate) - .OrderBy(x => x.SortOrder); - - return ProcessQuery(sql); - - } - - public bool Exists(string username) - { - var sql = new Sql(); - - sql.Select("COUNT(*)") - .From() - .Where(x => x.LoginName == username); - - return Database.ExecuteScalar(sql) > 0; - } - - public int GetCountByQuery(IQuery query) - { - var sqlWithProps = GetNodeIdQueryWithPropertyData(); - var translator = new SqlTranslator(sqlWithProps, query); - var sql = translator.Translate(); - - //get the COUNT base query - var fullSql = GetBaseQuery(true) - .Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)); - - return Database.ExecuteScalar(fullSql); - } - - /// - /// Gets paged member results - /// - /// - /// The where clause, if this is null all records are queried - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// The query supplied will ONLY work with data specifically on the cmsMember table because we are using PetaPoco paging (SQL paging) - /// - public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string filter = "") - { - var args = new List(); - var sbWhere = new StringBuilder(); - Func> filterCallback = null; - if (filter.IsNullOrWhiteSpace() == false) - { - sbWhere.Append("AND ((umbracoNode. " + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ") " + - "OR (cmsMember.LoginName LIKE @0" + args.Count + "))"); - args.Add("%" + filter + "%"); - filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray()); - } - - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - new Tuple("cmsMember", "nodeId"), - ProcessQuery, orderBy, orderDirection, - filterCallback); - } - - 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 - switch (orderBy.ToUpperInvariant()) - { - case "EMAIL": - return "cmsMember.Email"; - case "LOGINNAME": - return "cmsMember.LoginName"; - } - - return base.GetDatabaseFieldNameForOrderBy(orderBy); - } - - protected override string GetEntityPropertyNameForOrderBy(string orderBy) - { - //Some custom ones - switch (orderBy.ToUpperInvariant()) - { - case "LOGINNAME": - return "Username"; - } - - return base.GetEntityPropertyNameForOrderBy(orderBy); - } - - private IEnumerable ProcessQuery(Sql sql) - { - //NOTE: This doesn't allow properties to be part of the query - var dtos = Database.Fetch(sql); - - var ids = dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray(); - - //content types - var contentTypes = ids.Length == 0 ? Enumerable.Empty() : _memberTypeRepository.GetAll(ids).ToArray(); - - var dtosWithContentTypes = dtos - //This select into and null check are required because we don't have a foreign damn key on the contentType column - // http://issues.umbraco.org/issue/U4-5503 - .Select(x => new {dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentVersionDto.ContentDto.ContentTypeId)}) - .Where(x => x.contentType != null) - .ToArray(); - - //Go get the property data for each document - IEnumerable docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition( - d.dto.NodeId, - d.dto.ContentVersionDto.VersionId, - d.dto.ContentVersionDto.VersionDate, - d.dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, - d.contentType)); - - var propertyData = GetPropertyCollection(sql, docDefs); - - return dtosWithContentTypes.Select(d => CreateMemberFromDto( - d.dto, - contentTypes.First(ct => ct.Id == d.dto.ContentVersionDto.ContentDto.ContentTypeId), - propertyData[d.dto.NodeId])); - } - - /// - /// Private method to create a member object from a MemberDto - /// - /// - /// - /// - /// - private IMember CreateMemberFromDto(MemberDto dto, - IMemberType contentType, - PropertyCollection propCollection) - { - var factory = new MemberFactory(contentType, NodeObjectTypeId, dto.ContentVersionDto.NodeId); - var member = factory.BuildEntity(dto); - - member.Properties = propCollection; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)member).ResetDirtyProperties(false); - return member; - } - - /// - /// Private method to create a member object from a MemberDto - /// - /// - /// - /// - /// - private IMember CreateMemberFromDto(MemberDto dto, Guid versionId, Sql docSql) - { - var memberType = _memberTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); - - var factory = new MemberFactory(memberType, NodeObjectTypeId, dto.ContentVersionDto.NodeId); - var member = factory.BuildEntity(dto); - - var docDef = new DocumentDefinition(dto.ContentVersionDto.NodeId, versionId, member.UpdateDate, member.CreateDate, memberType); - - var properties = GetPropertyCollection(docSql, new[] { docDef }); - - member.Properties = properties[dto.ContentVersionDto.NodeId]; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)member).ResetDirtyProperties(false); - return member; - } - - /// - /// Dispose disposable properties - /// - /// - /// Ensure the unit of work is disposed - /// - protected override void DisposeResources() - { - _memberTypeRepository.Dispose(); - _tagRepository.Dispose(); - _memberGroupRepository.Dispose(); - _contentXmlRepository.Dispose(); - _contentPreviewRepository.Dispose(); - } - - } + /// + /// Represents a repository for doing CRUD operations for + /// + internal class MemberRepository : VersionableRepositoryBase, IMemberRepository + { + private readonly IMemberTypeRepository _memberTypeRepository; + private readonly ITagRepository _tagRepository; + private readonly IMemberGroupRepository _memberGroupRepository; + private readonly ContentXmlRepository _contentXmlRepository; + private readonly ContentPreviewRepository _contentPreviewRepository; + + public MemberRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, IContentSection contentSection) + : base(work, cache, logger, sqlSyntax, contentSection) + { + if (memberTypeRepository == null) throw new ArgumentNullException("memberTypeRepository"); + if (tagRepository == null) throw new ArgumentNullException("tagRepository"); + _memberTypeRepository = memberTypeRepository; + _tagRepository = tagRepository; + _memberGroupRepository = memberGroupRepository; + _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); + _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); + } + + #region Overrides of RepositoryBase + + protected override IMember PerformGet(int id) + { + var sql = GetBaseQuery(false); + sql.Where(GetBaseWhereClause(), new { Id = id }); + sql.OrderByDescending(x => x.VersionDate); + + var dto = Database.Fetch(sql).FirstOrDefault(); + + if (dto == null) + return null; + + var content = CreateMemberFromDto(dto, dto.ContentVersionDto.VersionId, sql); + + return content; + + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + var sql = GetBaseQuery(false); + if (ids.Any()) + { + sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); + } + + return ProcessQuery(sql); + + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + var baseQuery = GetBaseQuery(false); + + //check if the query is based on properties or not + + var wheres = query.GetWhereClauses(); + //this is a pretty rudimentary check but wil work, we just need to know if this query requires property + // level queries + if (wheres.Any(x => x.Item1.Contains("cmsPropertyType"))) + { + var sqlWithProps = GetNodeIdQueryWithPropertyData(); + var translator = new SqlTranslator(sqlWithProps, query); + var sql = translator.Translate(); + + baseQuery.Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)) + .OrderBy(x => x.SortOrder); + + return ProcessQuery(baseQuery); + } + else + { + var translator = new SqlTranslator(baseQuery, query); + var sql = translator.Translate() + .OrderBy(x => x.SortOrder); + + return ProcessQuery(sql); + } + + } + + #endregion + + #region Overrides of PetaPocoRepositoryBase + + protected override Sql GetBaseQuery(bool isCount) + { + var sql = new Sql(); + sql.Select(isCount ? "COUNT(*)" : "*") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + //We're joining the type so we can do a query against the member type - not sure if this adds much overhead or not? + // the execution plan says it doesn't so we'll go with that and in that case, it might be worth joining the content + // types by default on the document and media repo's so we can query by content type there too. + .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; + + } + + protected override string GetBaseWhereClause() + { + return "umbracoNode.id = @Id"; + } + + protected Sql GetNodeIdQueryWithPropertyData() + { + var sql = new Sql(); + sql.Select("DISTINCT(umbracoNode.id)") + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .LeftJoin().On(left => left.ContentTypeId, right => right.ContentTypeId) + .LeftJoin().On(left => left.DataTypeId, right => right.DataTypeId) + .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) + .Append("AND cmsPropertyData.versionId = cmsContentVersion.VersionId") + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; + } + + protected override IEnumerable GetDeleteClauses() + { + var list = new List + { + "DELETE FROM cmsTask WHERE nodeId = @Id", + "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", + "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", + "DELETE FROM umbracoRelation WHERE parentId = @Id", + "DELETE FROM umbracoRelation WHERE childId = @Id", + "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", + "DELETE FROM cmsPropertyData WHERE contentNodeId = @Id", + "DELETE FROM cmsMember2MemberGroup WHERE Member = @Id", + "DELETE FROM cmsMember WHERE nodeId = @Id", + "DELETE FROM cmsContentVersion WHERE ContentId = @Id", + "DELETE FROM cmsContentXml WHERE nodeId = @Id", + "DELETE FROM cmsContent WHERE nodeId = @Id", + "DELETE FROM umbracoNode WHERE id = @Id" + }; + return list; + } + + protected override Guid NodeObjectTypeId + { + get { return new Guid(Constants.ObjectTypes.Member); } + } + + #endregion + + #region Unit of Work Implementation + + protected override void PersistNewItem(IMember entity) + { + ((Member)entity).AddingEntity(); + + //Ensure that strings don't contain characters that are invalid in XML + entity.SanitizeEntityPropertiesForXmlStorage(); + + var factory = new MemberFactory(NodeObjectTypeId, entity.Id); + var dto = factory.BuildDto(entity); + + //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 = + Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE parentID = @ParentId AND nodeObjectType = @NodeObjectType", + new { ParentId = ((IUmbracoEntity)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); + + //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; + + //Create the Content specific data - cmsContent + var contentDto = dto.ContentVersionDto.ContentDto; + contentDto.NodeId = nodeDto.NodeId; + Database.Insert(contentDto); + + //Create the first version - cmsContentVersion + //Assumes a new Version guid and Version date (modified date) has been set + dto.ContentVersionDto.NodeId = nodeDto.NodeId; + Database.Insert(dto.ContentVersionDto); + + //Create the first entry in cmsMember + dto.NodeId = nodeDto.NodeId; + Database.Insert(dto); + + //Create the PropertyData for this version - cmsPropertyData + var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); + //Add Properties + // - don't try to save the property if it doesn't exist (or doesn't have an ID) on the content type + // - this can occur if the member type doesn't contain the built-in properties that the + // - member object contains. + var propsToPersist = entity.Properties.Where(x => x.PropertyType.HasIdentity).ToArray(); + var propertyDataDtos = propertyFactory.BuildDto(propsToPersist); + var keyDictionary = new Dictionary(); + + //Add Properties + foreach (var propertyDataDto in propertyDataDtos) + { + var primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); + keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); + } + + //Update Properties with its newly set Id + foreach (var property in propsToPersist) + { + property.Id = keyDictionary[property.PropertyTypeId]; + } + + UpdatePropertyTags(entity, _tagRepository); + + ((Member)entity).ResetDirtyProperties(); + } + + protected override void PersistUpdatedItem(IMember entity) + { + //Updates Modified date + ((Member)entity).UpdatingEntity(); + + //Ensure that strings don't contain characters that are invalid in XML + entity.SanitizeEntityPropertiesForXmlStorage(); + + var dirtyEntity = (ICanBeDirty)entity; + + //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 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; + } + + 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 }); + factory.SetPrimaryKey(contentDto.PrimaryKey); + var dto = factory.BuildDto(entity); + + //Updates the (base) node data - umbracoNode + var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; + var o = Database.Update(nodeDto); + + //Only update this DTO if the contentType has actually changed + if (contentDto.ContentTypeId != ((Member)entity).ContentTypeId) + { + //Create the Content specific data - cmsContent + var newContentDto = dto.ContentVersionDto.ContentDto; + Database.Update(newContentDto); + } + + //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 }); + dto.ContentVersionDto.Id = contentVerDto.Id; + //Updates the current version - cmsContentVersion + //Assumes a Version guid exists and Version date (modified date) has been set/updated + Database.Update(dto.ContentVersionDto); + + //Updates the cmsMember entry if it has changed + + //NOTE: these cols are the REAL column names in the db + var changedCols = new List(); + + if (dirtyEntity.IsPropertyDirty("Email")) + { + changedCols.Add("Email"); + } + if (dirtyEntity.IsPropertyDirty("Username")) + { + changedCols.Add("LoginName"); + } + // DO NOT update the password if it has not changed or if it is null or empty + if (dirtyEntity.IsPropertyDirty("RawPasswordValue") && entity.RawPasswordValue.IsNullOrWhiteSpace() == false) + { + changedCols.Add("Password"); + } + //only update the changed cols + if (changedCols.Count > 0) + { + Database.Update(dto, changedCols); + } + + //TODO ContentType for the Member entity + + //Create the PropertyData for this version - cmsPropertyData + var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); + var keyDictionary = new Dictionary(); + + //Add Properties + // - don't try to save the property if it doesn't exist (or doesn't have an ID) on the content type + // - this can occur if the member type doesn't contain the built-in properties that the + // - member object contains. + var propsToPersist = entity.Properties.Where(x => x.PropertyType.HasIdentity).ToArray(); + + var propertyDataDtos = propertyFactory.BuildDto(propsToPersist); + + foreach (var propertyDataDto in propertyDataDtos) + { + if (propertyDataDto.Id > 0) + { + Database.Update(propertyDataDto); + } + else + { + int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); + keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); + } + } + + //Update Properties with its newly set Id + if (keyDictionary.Any()) + { + foreach (var property in ((Member)entity).Properties) + { + property.Id = keyDictionary[property.PropertyTypeId]; + } + } + + UpdatePropertyTags(entity, _tagRepository); + + dirtyEntity.ResetDirtyProperties(); + } + + #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 memberObjectType = Guid.Parse(Constants.ObjectTypes.Member); + var subQuery = new 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 = new 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.Builder; + RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); + } + else + { + foreach (var contentTypeId in contentTypeIds) + { + //copy local + var id = contentTypeId; + var query = Query.Builder.Where(x => x.ContentTypeId == id && x.Trashed == false); + RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); + } + } + + tr.Complete(); + } + } + + private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, Transaction 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, orderBySystemField: 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(xmlItems, tr); + + processed += xmlItems.Length; + + pageIndex++; + } while (processed < total); + } + + public override IMember GetByVersion(Guid versionId) + { + var sql = GetBaseQuery(false); + sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); + sql.OrderByDescending(x => x.VersionDate); + + var dto = Database.Fetch(sql).FirstOrDefault(); + + if (dto == null) + return null; + + var memberType = _memberTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); + + var factory = new MemberFactory(memberType, NodeObjectTypeId, dto.NodeId); + var media = factory.BuildEntity(dto); + + var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.ContentVersionDto.VersionId, media.UpdateDate, media.CreateDate, memberType) }); + + media.Properties = properties[dto.NodeId]; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)media).ResetDirtyProperties(false); + return media; + + } + + protected override void PerformDeleteVersion(int id, Guid versionId) + { + Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); + } + + #endregion + + public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + //get the group id + var grpQry = new Query().Where(group => group.Name.Equals(roleName)); + var memberGroup = _memberGroupRepository.GetByQuery(grpQry).FirstOrDefault(); + if (memberGroup == null) return Enumerable.Empty(); + + // get the members by username + var query = new Query(); + switch (matchType) + { + case StringPropertyMatchType.Exact: + query.Where(member => member.Username.Equals(usernameToMatch)); + break; + case StringPropertyMatchType.Contains: + query.Where(member => member.Username.Contains(usernameToMatch)); + break; + case StringPropertyMatchType.StartsWith: + query.Where(member => member.Username.StartsWith(usernameToMatch)); + break; + case StringPropertyMatchType.EndsWith: + query.Where(member => member.Username.EndsWith(usernameToMatch)); + break; + case StringPropertyMatchType.Wildcard: + query.Where(member => member.Username.SqlWildcard(usernameToMatch, TextColumnType.NVarchar)); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } + var matchedMembers = GetByQuery(query).ToArray(); + + var membersInGroup = new List(); + //then we need to filter the matched members that are in the role + //since the max sql params are 2100 on sql server, we'll reduce that to be safe for potentially other servers and run the queries in batches + var inGroups = matchedMembers.InGroupsOf(1000); + foreach (var batch in inGroups) + { + var memberIdBatch = batch.Select(x => x.Id); + var sql = new Sql().Select("*").From() + .Where(dto => dto.MemberGroup == memberGroup.Id) + .Where("Member IN (@memberIds)", new { memberIds = memberIdBatch }); + var memberIdsInGroup = Database.Fetch(sql) + .Select(x => x.Member).ToArray(); + + membersInGroup.AddRange(matchedMembers.Where(x => memberIdsInGroup.Contains(x.Id))); + } + + return membersInGroup; + + } + + /// + /// Get all members in a specific group + /// + /// + /// + public IEnumerable GetByMemberGroup(string groupName) + { + var grpQry = new Query().Where(group => group.Name.Equals(groupName)); + var memberGroup = _memberGroupRepository.GetByQuery(grpQry).FirstOrDefault(); + if (memberGroup == null) return Enumerable.Empty(); + + var subQuery = new Sql().Select("Member").From().Where(dto => dto.MemberGroup == memberGroup.Id); + + var sql = GetBaseQuery(false) + //TODO: An inner join would be better, though I've read that the query optimizer will always turn a + // subquery with an IN clause into an inner join anyways. + .Append(new Sql("WHERE umbracoNode.id IN (" + subQuery.SQL + ")", subQuery.Arguments)) + .OrderByDescending(x => x.VersionDate) + .OrderBy(x => x.SortOrder); + + return ProcessQuery(sql); + + } + + public bool Exists(string username) + { + var sql = new Sql(); + + sql.Select("COUNT(*)") + .From() + .Where(x => x.LoginName == username); + + return Database.ExecuteScalar(sql) > 0; + } + + public int GetCountByQuery(IQuery query) + { + var sqlWithProps = GetNodeIdQueryWithPropertyData(); + var translator = new SqlTranslator(sqlWithProps, query); + var sql = translator.Translate(); + + //get the COUNT base query + var fullSql = GetBaseQuery(true) + .Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)); + + return Database.ExecuteScalar(fullSql); + } + + /// + /// Gets paged member results + /// + /// + /// The where clause, if this is null all records are queried + /// + /// Index of the page. + /// Size of the page. + /// The total records. + /// The order by column + /// The order direction. + /// Flag to indicate when ordering by system field + /// Search query + /// + /// + /// The query supplied will ONLY work with data specifically on the cmsMember table because we are using PetaPoco paging (SQL paging) + /// + public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") + { + var args = new List(); + var sbWhere = new StringBuilder(); + Func> filterCallback = null; + if (filter.IsNullOrWhiteSpace() == false) + { + sbWhere.Append("AND ((umbracoNode. " + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ") " + + "OR (cmsMember.LoginName LIKE @0" + args.Count + "))"); + args.Add("%" + filter + "%"); + filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray()); + } + + return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, + new Tuple("cmsMember", "nodeId"), + ProcessQuery, orderBy, orderDirection, orderBySystemField, + filterCallback); + } + + 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 + switch (orderBy.ToUpperInvariant()) + { + case "EMAIL": + return "cmsMember.Email"; + case "LOGINNAME": + return "cmsMember.LoginName"; + } + + return base.GetDatabaseFieldNameForOrderBy(orderBy); + } + + protected override string GetEntityPropertyNameForOrderBy(string orderBy) + { + //Some custom ones + switch (orderBy.ToUpperInvariant()) + { + case "LOGINNAME": + return "Username"; + } + + return base.GetEntityPropertyNameForOrderBy(orderBy); + } + + private IEnumerable ProcessQuery(Sql sql) + { + //NOTE: This doesn't allow properties to be part of the query + var dtos = Database.Fetch(sql); + + var ids = dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray(); + + //content types + var contentTypes = ids.Length == 0 ? Enumerable.Empty() : _memberTypeRepository.GetAll(ids).ToArray(); + + var dtosWithContentTypes = dtos + //This select into and null check are required because we don't have a foreign damn key on the contentType column + // http://issues.umbraco.org/issue/U4-5503 + .Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentVersionDto.ContentDto.ContentTypeId) }) + .Where(x => x.contentType != null) + .ToArray(); + + //Go get the property data for each document + IEnumerable docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition( + d.dto.NodeId, + d.dto.ContentVersionDto.VersionId, + d.dto.ContentVersionDto.VersionDate, + d.dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, + d.contentType)); + + var propertyData = GetPropertyCollection(sql, docDefs); + + return dtosWithContentTypes.Select(d => CreateMemberFromDto( + d.dto, + contentTypes.First(ct => ct.Id == d.dto.ContentVersionDto.ContentDto.ContentTypeId), + propertyData[d.dto.NodeId])); + } + + /// + /// Private method to create a member object from a MemberDto + /// + /// + /// + /// + /// + private IMember CreateMemberFromDto(MemberDto dto, + IMemberType contentType, + PropertyCollection propCollection) + { + var factory = new MemberFactory(contentType, NodeObjectTypeId, dto.ContentVersionDto.NodeId); + var member = factory.BuildEntity(dto); + + member.Properties = propCollection; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)member).ResetDirtyProperties(false); + return member; + } + + /// + /// Private method to create a member object from a MemberDto + /// + /// + /// + /// + /// + private IMember CreateMemberFromDto(MemberDto dto, Guid versionId, Sql docSql) + { + var memberType = _memberTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); + + var factory = new MemberFactory(memberType, NodeObjectTypeId, dto.ContentVersionDto.NodeId); + var member = factory.BuildEntity(dto); + + var docDef = new DocumentDefinition(dto.ContentVersionDto.NodeId, versionId, member.UpdateDate, member.CreateDate, memberType); + + var properties = GetPropertyCollection(docSql, new[] { docDef }); + + member.Properties = properties[dto.ContentVersionDto.NodeId]; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)member).ResetDirtyProperties(false); + return member; + } + + /// + /// Dispose disposable properties + /// + /// + /// Ensure the unit of work is disposed + /// + protected override void DisposeResources() + { + _memberTypeRepository.Dispose(); + _tagRepository.Dispose(); + _memberGroupRepository.Dispose(); + _contentXmlRepository.Dispose(); + _contentPreviewRepository.Dispose(); + } + + } } diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 56b0b63ad5..4fd1c83436 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -25,368 +25,391 @@ using Umbraco.Core.IO; namespace Umbraco.Core.Persistence.Repositories { - internal abstract class VersionableRepositoryBase : PetaPocoRepositoryBase - where TEntity : class, IAggregateRoot - { - private readonly IContentSection _contentSection; + using SqlSyntax; + internal abstract class VersionableRepositoryBase : PetaPocoRepositoryBase + where TEntity : class, IAggregateRoot + { + private readonly IContentSection _contentSection; - protected VersionableRepositoryBase(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IContentSection contentSection) - : base(work, cache, logger, sqlSyntax) - { - _contentSection = contentSection; - } + protected VersionableRepositoryBase(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IContentSection contentSection) + : base(work, cache, logger, sqlSyntax) + { + _contentSection = contentSection; + } - #region IRepositoryVersionable Implementation + #region IRepositoryVersionable Implementation - public virtual IEnumerable GetAllVersions(int id) - { - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId) - .Where(x => x.NodeId == id) - .OrderByDescending(x => x.VersionDate, SqlSyntax); + public virtual IEnumerable GetAllVersions(int id) + { + var sql = new Sql(); + sql.Select("*") + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.NodeId == id) + .OrderByDescending(x => x.VersionDate, SqlSyntax); - var dtos = Database.Fetch(sql); - foreach (var dto in dtos) - { - yield return GetByVersion(dto.VersionId); - } - } + var dtos = Database.Fetch(sql); + foreach (var dto in dtos) + { + yield return GetByVersion(dto.VersionId); + } + } - public virtual void DeleteVersion(Guid versionId) - { - var dto = Database.FirstOrDefault("WHERE versionId = @VersionId", new { VersionId = versionId }); - if(dto == null) return; + public virtual void DeleteVersion(Guid versionId) + { + var dto = Database.FirstOrDefault("WHERE versionId = @VersionId", new { VersionId = versionId }); + if (dto == null) return; - //Ensure that the lastest version is not deleted - var latestVersionDto = Database.FirstOrDefault("WHERE ContentId = @Id ORDER BY VersionDate DESC", new { Id = dto.NodeId }); - if(latestVersionDto.VersionId == dto.VersionId) - return; + //Ensure that the lastest version is not deleted + var latestVersionDto = Database.FirstOrDefault("WHERE ContentId = @Id ORDER BY VersionDate DESC", new { Id = dto.NodeId }); + if (latestVersionDto.VersionId == dto.VersionId) + return; - using (var transaction = Database.GetTransaction()) - { - PerformDeleteVersion(dto.NodeId, versionId); + using (var transaction = Database.GetTransaction()) + { + PerformDeleteVersion(dto.NodeId, versionId); - transaction.Complete(); - } - } + transaction.Complete(); + } + } - public virtual void DeleteVersions(int id, DateTime versionDate) - { - //Ensure that the latest version is not part of the versions being deleted - var latestVersionDto = Database.FirstOrDefault("WHERE ContentId = @Id ORDER BY VersionDate DESC", new { Id = id }); - var list = - Database.Fetch( - "WHERE versionId <> @VersionId AND (ContentId = @Id AND VersionDate < @VersionDate)", - new {VersionId = latestVersionDto.VersionId, Id = id, VersionDate = versionDate}); - if (list.Any() == false) return; + public virtual void DeleteVersions(int id, DateTime versionDate) + { + //Ensure that the latest version is not part of the versions being deleted + var latestVersionDto = Database.FirstOrDefault("WHERE ContentId = @Id ORDER BY VersionDate DESC", new { Id = id }); + var list = + Database.Fetch( + "WHERE versionId <> @VersionId AND (ContentId = @Id AND VersionDate < @VersionDate)", + new { VersionId = latestVersionDto.VersionId, Id = id, VersionDate = versionDate }); + if (list.Any() == false) return; - using (var transaction = Database.GetTransaction()) - { - foreach (var dto in list) - { - PerformDeleteVersion(id, dto.VersionId); - } + using (var transaction = Database.GetTransaction()) + { + foreach (var dto in list) + { + PerformDeleteVersion(id, dto.VersionId); + } - transaction.Complete(); - } - } + transaction.Complete(); + } + } - public abstract TEntity GetByVersion(Guid versionId); + public abstract TEntity GetByVersion(Guid versionId); - /// - /// Protected method to execute the delete statements for removing a single version for a TEntity item. - /// - /// Id of the to delete a version from - /// Guid id of the version to delete - protected abstract void PerformDeleteVersion(int id, Guid versionId); + /// + /// Protected method to execute the delete statements for removing a single version for a TEntity item. + /// + /// Id of the to delete a version from + /// Guid id of the version to delete + protected abstract void PerformDeleteVersion(int id, Guid versionId); - #endregion + #endregion - public int CountDescendants(int parentId, string contentTypeAlias = null) - { - var pathMatch = parentId == -1 - ? "-1," - : "," + parentId + ","; - var sql = new Sql(); - if (contentTypeAlias.IsNullOrWhiteSpace()) - { - sql.Select("COUNT(*)") - .From() - .Where(x => x.NodeObjectType == NodeObjectTypeId) - .Where(x => x.Path.Contains(pathMatch)); - } - else - { - sql.Select("COUNT(*)") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.ContentTypeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId) - .Where(x => x.Path.Contains(pathMatch)) - .Where(x => x.Alias == contentTypeAlias); - } + public int CountDescendants(int parentId, string contentTypeAlias = null) + { + var pathMatch = parentId == -1 + ? "-1," + : "," + parentId + ","; + var sql = new Sql(); + if (contentTypeAlias.IsNullOrWhiteSpace()) + { + sql.Select("COUNT(*)") + .From() + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.Path.Contains(pathMatch)); + } + else + { + sql.Select("COUNT(*)") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.ContentTypeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.Path.Contains(pathMatch)) + .Where(x => x.Alias == contentTypeAlias); + } - return Database.ExecuteScalar(sql); - } + return Database.ExecuteScalar(sql); + } - public int CountChildren(int parentId, string contentTypeAlias = null) - { - var sql = new Sql(); - if (contentTypeAlias.IsNullOrWhiteSpace()) - { - sql.Select("COUNT(*)") - .From() - .Where(x => x.NodeObjectType == NodeObjectTypeId) - .Where(x => x.ParentId == parentId); - } - else - { - sql.Select("COUNT(*)") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.ContentTypeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId) - .Where(x => x.ParentId == parentId) - .Where(x => x.Alias == contentTypeAlias); - } + public int CountChildren(int parentId, string contentTypeAlias = null) + { + var sql = new Sql(); + if (contentTypeAlias.IsNullOrWhiteSpace()) + { + sql.Select("COUNT(*)") + .From() + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.ParentId == parentId); + } + else + { + sql.Select("COUNT(*)") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.ContentTypeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.ParentId == parentId) + .Where(x => x.Alias == contentTypeAlias); + } - return Database.ExecuteScalar(sql); - } + return Database.ExecuteScalar(sql); + } - /// - /// Get the total count of entities - /// - /// - /// - public int Count(string contentTypeAlias = null) - { - var sql = new Sql(); - if (contentTypeAlias.IsNullOrWhiteSpace()) - { - sql.Select("COUNT(*)") - .From() - .Where(x => x.NodeObjectType == NodeObjectTypeId); - } - else - { - sql.Select("COUNT(*)") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.ContentTypeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId) - .Where(x => x.Alias == contentTypeAlias); - } + /// + /// Get the total count of entities + /// + /// + /// + public int Count(string contentTypeAlias = null) + { + var sql = new Sql(); + if (contentTypeAlias.IsNullOrWhiteSpace()) + { + sql.Select("COUNT(*)") + .From() + .Where(x => x.NodeObjectType == NodeObjectTypeId); + } + else + { + sql.Select("COUNT(*)") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.ContentTypeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.Alias == contentTypeAlias); + } - return Database.ExecuteScalar(sql); - } + return Database.ExecuteScalar(sql); + } - /// - /// This removes associated tags from the entity - used generally when an entity is recycled - /// - /// - /// - protected void ClearEntityTags(IContentBase entity, ITagRepository tagRepo) - { - tagRepo.ClearTagsFromEntity(entity.Id); - } + /// + /// This removes associated tags from the entity - used generally when an entity is recycled + /// + /// + /// + protected void ClearEntityTags(IContentBase entity, ITagRepository tagRepo) + { + tagRepo.ClearTagsFromEntity(entity.Id); + } - /// - /// Updates the tag repository with any tag enabled properties and their values - /// - /// - /// - protected void UpdatePropertyTags(IContentBase entity, ITagRepository tagRepo) - { - foreach (var tagProp in entity.Properties.Where(x => x.TagSupport.Enable)) - { - if (tagProp.TagSupport.Behavior == PropertyTagBehavior.Remove) - { - //remove the specific tags - tagRepo.RemoveTagsFromProperty( - entity.Id, - tagProp.PropertyTypeId, - tagProp.TagSupport.Tags.Select(x => new Tag { Text = x.Item1, Group = x.Item2 })); - } - else - { - //assign the tags - tagRepo.AssignTagsToProperty( - entity.Id, - tagProp.PropertyTypeId, - tagProp.TagSupport.Tags.Select(x => new Tag { Text = x.Item1, Group = x.Item2 }), - tagProp.TagSupport.Behavior == PropertyTagBehavior.Replace); - } - } - } + /// + /// Updates the tag repository with any tag enabled properties and their values + /// + /// + /// + protected void UpdatePropertyTags(IContentBase entity, ITagRepository tagRepo) + { + foreach (var tagProp in entity.Properties.Where(x => x.TagSupport.Enable)) + { + if (tagProp.TagSupport.Behavior == PropertyTagBehavior.Remove) + { + //remove the specific tags + tagRepo.RemoveTagsFromProperty( + entity.Id, + tagProp.PropertyTypeId, + tagProp.TagSupport.Tags.Select(x => new Tag { Text = x.Item1, Group = x.Item2 })); + } + else + { + //assign the tags + tagRepo.AssignTagsToProperty( + entity.Id, + tagProp.PropertyTypeId, + tagProp.TagSupport.Tags.Select(x => new Tag { Text = x.Item1, Group = x.Item2 }), + tagProp.TagSupport.Behavior == PropertyTagBehavior.Replace); + } + } + } - private Sql GetFilteredSqlForPagedResults(Sql sql, Func> defaultFilter = null) - { - //copy to var so that the original isn't changed - var filteredSql = new Sql(sql.SQL, sql.Arguments); - // Apply filter - if (defaultFilter != null) - { - var filterResult = defaultFilter(); - filteredSql.Append(filterResult.Item1, filterResult.Item2); - } - return filteredSql; - } + private Sql GetFilteredSqlForPagedResults(Sql sql, Func> defaultFilter = null) + { + //copy to var so that the original isn't changed + var filteredSql = new Sql(sql.SQL, sql.Arguments); + // Apply filter + if (defaultFilter != null) + { + var filterResult = defaultFilter(); + filteredSql.Append(filterResult.Item1, filterResult.Item2); + } + return filteredSql; + } - private Sql GetSortedSqlForPagedResults(Sql sql, Direction orderDirection, string orderBy) - { - //copy to var so that the original isn't changed - var sortedSql = new Sql(sql.SQL, sql.Arguments); - // Apply order according to parameters - if (string.IsNullOrEmpty(orderBy) == false) - { - var orderByParams = new[] { GetDatabaseFieldNameForOrderBy(orderBy) }; - if (orderDirection == Direction.Ascending) - { - sortedSql.OrderBy(orderByParams); - } - else - { - sortedSql.OrderByDescending(orderByParams); - } - return sortedSql; - } - return sortedSql; - } + private Sql GetSortedSqlForPagedResults(Sql sql, Direction orderDirection, string orderBy, bool orderBySystemField) + { + //copy to var so that the original isn't changed + var sortedSql = new Sql(sql.SQL, sql.Arguments); - /// - /// A helper method for inheritors to get the paged results by query in a way that minimizes queries - /// - /// The type of the d. - /// The 'true' entity type (i.e. Content, Member, etc...) - /// The query. - /// Index of the page. - /// Size of the page. - /// The total records. - /// The tablename + column name for the SELECT statement fragment to return the node id from the query - /// A callback to create the default filter to be applied if there is one - /// A callback to process the query result - /// The order by column - /// The order direction. - /// - /// orderBy - protected IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - Tuple nodeIdSelect, - Func> processQuery, - string orderBy, - Direction orderDirection, - Func> defaultFilter = null) - where TContentBase : class, IAggregateRoot, TEntity - { - if (orderBy == null) throw new ArgumentNullException("orderBy"); + if (orderBySystemField) + { + // Apply order according to parameters + if (string.IsNullOrEmpty(orderBy) == false) + { + var orderByParams = new[] { GetDatabaseFieldNameForOrderBy(orderBy) }; + if (orderDirection == Direction.Ascending) + { + sortedSql.OrderBy(orderByParams); + } + else + { + sortedSql.OrderByDescending(orderByParams); + } + } + } + else + { + // Sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through valie + // from most recent content version for the given order by field + var sortedInt = string.Format(SqlSyntaxContext.SqlSyntaxProvider.ConvertIntegerToOrderableString, "dataInt"); + var sortedDate = string.Format(SqlSyntaxContext.SqlSyntaxProvider.ConvertDateToOrderableString, "dataDate"); + var sortedString = string.Format(SqlSyntaxContext.SqlSyntaxProvider.IsNull, "dataNvarchar", "''"); - // Get base query - var sqlBase = GetBaseQuery(false); + var orderBySql = string.Format(@"ORDER BY ( + SELECT CASE + WHEN dataInt Is Not Null THEN {0} + WHEN dataDate Is Not Null THEN {1} + ELSE {2} + END + FROM cmsContent c + INNER JOIN cmsContentVersion cv ON cv.ContentId = c.nodeId AND VersionDate = ( + SELECT Max(VersionDate) + FROM cmsContentVersion + WHERE ContentId = c.nodeId + ) + INNER JOIN cmsPropertyData cpd ON cpd.contentNodeId = c.nodeId + AND cpd.versionId = cv.VersionId + INNER JOIN cmsPropertyType cpt ON cpt.Id = cpd.propertytypeId + WHERE c.nodeId = umbracoNode.Id and cpt.Alias = @0)", sortedInt, sortedDate, sortedString); - if (query == null) query = new Query(); - var translator = new SqlTranslator(sqlBase, query); - var sqlQuery = translator.Translate(); - - // Note we can't do multi-page for several DTOs like we can multi-fetch and are doing in PerformGetByQuery, - // but actually given we are doing a Get on each one (again as in PerformGetByQuery), we only need the node Id. - // So we'll modify the SQL. - var sqlNodeIds = new Sql( - sqlQuery.SQL.Replace("SELECT *", string.Format("SELECT {0}.{1}",nodeIdSelect.Item1, nodeIdSelect.Item2)), - sqlQuery.Arguments); - - //get sorted and filtered sql - var sqlNodeIdsWithSort = GetSortedSqlForPagedResults( - GetFilteredSqlForPagedResults(sqlNodeIds, defaultFilter), - orderDirection, orderBy); + sortedSql.Append(orderBySql, orderBy); + if (orderDirection == Direction.Descending) + { + sortedSql.Append(" DESC"); + } + } + return sortedSql; + } - // Get page of results and total count - IEnumerable result; - var pagedResult = Database.Page(pageIndex + 1, pageSize, sqlNodeIdsWithSort); - totalRecords = Convert.ToInt32(pagedResult.TotalItems); + /// + /// A helper method for inheritors to get the paged results by query in a way that minimizes queries + /// + /// The type of the d. + /// The 'true' entity type (i.e. Content, Member, etc...) + /// The query. + /// Index of the page. + /// Size of the page. + /// The total records. + /// The tablename + column name for the SELECT statement fragment to return the node id from the query + /// A callback to create the default filter to be applied if there is one + /// A callback to process the query result + /// The order by column + /// The order direction. + /// Flag to indicate when ordering by system field + /// + /// orderBy + protected IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, + Tuple nodeIdSelect, + Func> processQuery, + string orderBy, + Direction orderDirection, + bool orderBySystemField, + Func> defaultFilter = null) + where TContentBase : class, IAggregateRoot, TEntity + { + if (orderBy == null) throw new ArgumentNullException("orderBy"); - //NOTE: We need to check the actual items returned, not the 'totalRecords', that is because if you request a page number - // that doesn't actually have any data on it, the totalRecords will still indicate there are records but there are none in - // the pageResult, then the GetAll will actually return ALL records in the db. - if (pagedResult.Items.Any()) - { - //Crete the inner paged query that was used above to get the paged result, we'll use that as the inner sub query - var args = sqlNodeIdsWithSort.Arguments; - string sqlStringCount, sqlStringPage; - Database.BuildPageQueries(pageIndex * pageSize, pageSize, sqlNodeIdsWithSort.SQL, ref args, out sqlStringCount, out sqlStringPage); - - //if this is for sql server, the sqlPage will start with a SELECT * but we don't want that, we only want to return the nodeId - sqlStringPage = sqlStringPage - .Replace("SELECT *", - //This ensures we only take the field name of the node id select and not the table name - since the resulting select - // will ony work with the field name. - "SELECT " + nodeIdSelect.Item2); + // Get base query + var sqlBase = GetBaseQuery(false); - //We need to make this an inner join on the paged query - var splitQuery = sqlQuery.SQL.Split(new[] {"WHERE "}, StringSplitOptions.None); - var withInnerJoinSql = new Sql(splitQuery[0]) - .Append("INNER JOIN (") - //join the paged query with the paged query arguments - .Append(sqlStringPage, args) - .Append(") temp ") - .Append(string.Format("ON {0}.{1} = temp.{1}", nodeIdSelect.Item1, nodeIdSelect.Item2)) - //add the original where clause back with the original arguments - .Where(splitQuery[1], sqlQuery.Arguments); + if (query == null) query = new Query(); + var translator = new SqlTranslator(sqlBase, query); + var sqlQuery = translator.Translate(); - //get sorted and filtered sql - var fullQuery = GetSortedSqlForPagedResults( - GetFilteredSqlForPagedResults(withInnerJoinSql, defaultFilter), - orderDirection, orderBy); - - var content = processQuery(fullQuery) - .Cast() - .AsQueryable(); + // Note we can't do multi-page for several DTOs like we can multi-fetch and are doing in PerformGetByQuery, + // but actually given we are doing a Get on each one (again as in PerformGetByQuery), we only need the node Id. + // So we'll modify the SQL. + var sqlNodeIds = new Sql( + sqlQuery.SQL.Replace("SELECT *", string.Format("SELECT {0}.{1}", nodeIdSelect.Item1, nodeIdSelect.Item2)), + sqlQuery.Arguments); - // Now we need to ensure this result is also ordered by the same order by clause - var orderByProperty = GetEntityPropertyNameForOrderBy(orderBy); - if (orderDirection == Direction.Ascending) - { - result = content.OrderBy(orderByProperty); - } - else - { - result = content.OrderByDescending(orderByProperty); - } - } - else - { - result = Enumerable.Empty(); - } + //get sorted and filtered sql + var sqlNodeIdsWithSort = GetSortedSqlForPagedResults( + GetFilteredSqlForPagedResults(sqlNodeIds, defaultFilter), + orderDirection, orderBy, orderBySystemField); - return result; - } + // Get page of results and total count + IEnumerable result; + var pagedResult = Database.Page(pageIndex + 1, pageSize, sqlNodeIdsWithSort); + totalRecords = Convert.ToInt32(pagedResult.TotalItems); - protected IDictionary GetPropertyCollection( - Sql docSql, - IEnumerable documentDefs) - { - if (documentDefs.Any() == false) return new Dictionary(); + //NOTE: We need to check the actual items returned, not the 'totalRecords', that is because if you request a page number + // that doesn't actually have any data on it, the totalRecords will still indicate there are records but there are none in + // the pageResult, then the GetAll will actually return ALL records in the db. + if (pagedResult.Items.Any()) + { + //Crete the inner paged query that was used above to get the paged result, we'll use that as the inner sub query + var args = sqlNodeIdsWithSort.Arguments; + string sqlStringCount, sqlStringPage; + Database.BuildPageQueries(pageIndex * pageSize, pageSize, sqlNodeIdsWithSort.SQL, ref args, out sqlStringCount, out sqlStringPage); - //we need to parse the original SQL statement and reduce the columns to just cmsContent.nodeId, cmsContentVersion.VersionId so that we can use - // the statement to go get the property data for all of the items by using an inner join - var parsedOriginalSql = "SELECT {0} " + docSql.SQL.Substring(docSql.SQL.IndexOf("FROM", StringComparison.Ordinal)); - //now remove everything from an Orderby clause and beyond - if (parsedOriginalSql.InvariantContains("ORDER BY ")) - { - parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal)); - } + //if this is for sql server, the sqlPage will start with a SELECT * but we don't want that, we only want to return the nodeId + sqlStringPage = sqlStringPage + .Replace("SELECT *", + //This ensures we only take the field name of the node id select and not the table name - since the resulting select + // will ony work with the field name. + "SELECT " + nodeIdSelect.Item2); - var propSql = new Sql(@"SELECT cmsPropertyData.* + //We need to make this an inner join on the paged query + var splitQuery = sqlQuery.SQL.Split(new[] { "WHERE " }, StringSplitOptions.None); + var withInnerJoinSql = new Sql(splitQuery[0]) + .Append("INNER JOIN (") + //join the paged query with the paged query arguments + .Append(sqlStringPage, args) + .Append(") temp ") + .Append(string.Format("ON {0}.{1} = temp.{1}", nodeIdSelect.Item1, nodeIdSelect.Item2)) + //add the original where clause back with the original arguments + .Where(splitQuery[1], sqlQuery.Arguments); + + //get sorted and filtered sql + var fullQuery = GetSortedSqlForPagedResults( + GetFilteredSqlForPagedResults(withInnerJoinSql, defaultFilter), + orderDirection, orderBy, orderBySystemField); + return processQuery(fullQuery); + } + else + { + result = Enumerable.Empty(); + } + + return result; + } + + protected IDictionary GetPropertyCollection( + Sql docSql, + IEnumerable documentDefs) + { + if (documentDefs.Any() == false) return new Dictionary(); + + //we need to parse the original SQL statement and reduce the columns to just cmsContent.nodeId, cmsContentVersion.VersionId so that we can use + // the statement to go get the property data for all of the items by using an inner join + var parsedOriginalSql = "SELECT {0} " + docSql.SQL.Substring(docSql.SQL.IndexOf("FROM", StringComparison.Ordinal)); + //now remove everything from an Orderby clause and beyond + if (parsedOriginalSql.InvariantContains("ORDER BY ")) + { + parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal)); + } + + var propSql = new Sql(@"SELECT cmsPropertyData.* FROM cmsPropertyData INNER JOIN cmsPropertyType ON cmsPropertyData.propertytypeid = cmsPropertyType.id @@ -394,15 +417,15 @@ INNER JOIN (" + string.Format(parsedOriginalSql, "cmsContent.nodeId, cmsContentVersion.VersionId") + @") as docData ON cmsPropertyData.versionId = docData.VersionId AND cmsPropertyData.contentNodeId = docData.nodeId LEFT OUTER JOIN cmsDataTypePreValues -ON cmsPropertyType.dataTypeId = cmsDataTypePreValues.datatypeNodeId", docSql.Arguments); +ON cmsPropertyType.dataTypeId = cmsDataTypePreValues.datatypeNodeId", docSql.Arguments); - var allPropertyData = Database.Fetch(propSql); + var allPropertyData = Database.Fetch(propSql); - //This is a lazy access call to get all prevalue data for the data types that make up all of these properties which we use - // below if any property requires tag support - var allPreValues = new Lazy>(() => - { - var preValsSql = new Sql(@"SELECT a.id, a.value, a.sortorder, a.alias, a.datatypeNodeId + //This is a lazy access call to get all prevalue data for the data types that make up all of these properties which we use + // below if any property requires tag support + var allPreValues = new Lazy>(() => + { + var preValsSql = new Sql(@"SELECT a.id, a.value, a.sortorder, a.alias, a.datatypeNodeId FROM cmsDataTypePreValues a WHERE EXISTS( SELECT DISTINCT b.id as preValIdInner @@ -414,182 +437,185 @@ WHERE EXISTS( ON cmsPropertyType.contentTypeId = docData.contentType WHERE a.id = b.id)", docSql.Arguments); - return Database.Fetch(preValsSql); - }); + return Database.Fetch(preValsSql); + }); - var result = new Dictionary(); + var result = new Dictionary(); - var propertiesWithTagSupport = new Dictionary(); + var propertiesWithTagSupport = new Dictionary(); - //iterate each definition grouped by it's content type - this will mean less property type iterations while building - // up the property collections - foreach (var compositionGroup in documentDefs.GroupBy(x => x.Composition)) - { - var compositionProperties = compositionGroup.Key.CompositionPropertyTypes.ToArray(); + //iterate each definition grouped by it's content type - this will mean less property type iterations while building + // up the property collections + foreach (var compositionGroup in documentDefs.GroupBy(x => x.Composition)) + { + var compositionProperties = compositionGroup.Key.CompositionPropertyTypes.ToArray(); - foreach (var def in compositionGroup) - { - var propertyDataDtos = allPropertyData.Where(x => x.NodeId == def.Id).Distinct(); + foreach (var def in compositionGroup) + { + var propertyDataDtos = allPropertyData.Where(x => x.NodeId == def.Id).Distinct(); - var propertyFactory = new PropertyFactory(compositionProperties, def.Version, def.Id, def.CreateDate, def.VersionDate); - var properties = propertyFactory.BuildEntity(propertyDataDtos.ToArray()).ToArray(); - - var newProperties = properties.Where(x => x.HasIdentity == false && x.PropertyType.HasIdentity); + var propertyFactory = new PropertyFactory(compositionProperties, def.Version, def.Id, def.CreateDate, def.VersionDate); + var properties = propertyFactory.BuildEntity(propertyDataDtos.ToArray()).ToArray(); - foreach (var property in newProperties) - { - var propertyDataDto = new PropertyDataDto { NodeId = def.Id, PropertyTypeId = property.PropertyTypeId, VersionId = def.Version }; - int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); + var newProperties = properties.Where(x => x.HasIdentity == false && x.PropertyType.HasIdentity); - property.Version = def.Version; - property.Id = primaryKey; - } + foreach (var property in newProperties) + { + var propertyDataDto = new PropertyDataDto { NodeId = def.Id, PropertyTypeId = property.PropertyTypeId, VersionId = def.Version }; + int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - foreach (var property in properties) - { - //NOTE: The benchmarks run with and without the following code show very little change so this is not a perf bottleneck - var editor = PropertyEditorResolver.Current.GetByAlias(property.PropertyType.PropertyEditorAlias); + property.Version = def.Version; + property.Id = primaryKey; + } - var tagSupport = propertiesWithTagSupport.ContainsKey(property.PropertyType.PropertyEditorAlias) - ? propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] - : TagExtractor.GetAttribute(editor); + foreach (var property in properties) + { + //NOTE: The benchmarks run with and without the following code show very little change so this is not a perf bottleneck + var editor = PropertyEditorResolver.Current.GetByAlias(property.PropertyType.PropertyEditorAlias); - if (tagSupport != null) - { - //add to local cache so we don't need to reflect next time for this property editor alias - propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] = tagSupport; + var tagSupport = propertiesWithTagSupport.ContainsKey(property.PropertyType.PropertyEditorAlias) + ? propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] + : TagExtractor.GetAttribute(editor); - //this property has tags, so we need to extract them and for that we need the prevals which we've already looked up - var preValData = allPreValues.Value.Where(x => x.DataTypeNodeId == property.PropertyType.DataTypeDefinitionId) - .Distinct() - .ToArray(); + if (tagSupport != null) + { + //add to local cache so we don't need to reflect next time for this property editor alias + propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] = tagSupport; - var asDictionary = preValData.ToDictionary(x => x.Alias, x => new PreValue(x.Id, x.Value, x.SortOrder)); + //this property has tags, so we need to extract them and for that we need the prevals which we've already looked up + var preValData = allPreValues.Value.Where(x => x.DataTypeNodeId == property.PropertyType.DataTypeDefinitionId) + .Distinct() + .ToArray(); - var preVals = new PreValueCollection(asDictionary); + var asDictionary = preValData.ToDictionary(x => x.Alias, x => new PreValue(x.Id, x.Value, x.SortOrder)); - var contentPropData = new ContentPropertyData(property.Value, - preVals, - new Dictionary()); + var preVals = new PreValueCollection(asDictionary); - TagExtractor.SetPropertyTags(property, contentPropData, property.Value, tagSupport); - } - } + var contentPropData = new ContentPropertyData(property.Value, + preVals, + new Dictionary()); - if (result.ContainsKey(def.Id)) - { - Logger.Warn>("The query returned multiple property sets for document definition " + def.Id + ", " + def.Composition.Name); - } - result[def.Id] = new PropertyCollection(properties); - } - } + TagExtractor.SetPropertyTags(property, contentPropData, property.Value, tagSupport); + } + } - return result; - } + if (result.ContainsKey(def.Id)) + { + Logger.Warn>("The query returned multiple property sets for document definition " + def.Id + ", " + def.Composition.Name); + } + result[def.Id] = new PropertyCollection(properties); + } + } - public class DocumentDefinition - { - /// - /// Initializes a new instance of the class. - /// - public DocumentDefinition(int id, Guid version, DateTime versionDate, DateTime createDate, IContentTypeComposition composition) - { - Id = id; - Version = version; - VersionDate = versionDate; - CreateDate = createDate; - Composition = composition; - } + return result; + } - public int Id { get; set; } - public Guid Version { get; set; } - public DateTime VersionDate { get; set; } - public DateTime CreateDate { get; set; } - public IContentTypeComposition Composition { get; set; } - } + public class DocumentDefinition + { + /// + /// Initializes a new instance of the class. + /// + public DocumentDefinition(int id, Guid version, DateTime versionDate, DateTime createDate, IContentTypeComposition composition) + { + Id = id; + Version = version; + VersionDate = versionDate; + CreateDate = createDate; + Composition = composition; + } - protected virtual string GetDatabaseFieldNameForOrderBy(string orderBy) - { - // Translate the passed order by field (which were originally defined for in-memory object sorting - // of ContentItemBasic instances) to the database field names. - switch (orderBy.ToUpperInvariant()) - { - case "UPDATEDATE": - return "cmsContentVersion.VersionDate"; - case "NAME": - return "umbracoNode.text"; - case "OWNER": - //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter - return "umbracoNode.nodeUser"; - default: - //ensure invalid SQL cannot be submitted - return Regex.Replace(orderBy, @"[^\w\.,`\[\]@-]", ""); - } - } + public int Id { get; set; } + public Guid Version { get; set; } + public DateTime VersionDate { get; set; } + public DateTime CreateDate { get; set; } + public IContentTypeComposition Composition { get; set; } + } - protected virtual string GetEntityPropertyNameForOrderBy(string orderBy) - { - // Translate the passed order by field (which were originally defined for in-memory object sorting - // of ContentItemBasic instances) to the IMedia property names. - switch (orderBy.ToUpperInvariant()) - { - case "OWNER": - //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter - return "CreatorId"; - case "UPDATER": - //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter - return "WriterId"; - case "VERSIONDATE": - return "UpdateDate"; - default: - //ensure invalid SQL cannot be submitted - return Regex.Replace(orderBy, @"[^\w\.,`\[\]@-]", ""); - } - } + protected virtual string GetDatabaseFieldNameForOrderBy(string orderBy) + { + // Translate the passed order by field (which were originally defined for in-memory object sorting + // of ContentItemBasic instances) to the database field names. + switch (orderBy.ToUpperInvariant()) + { + case "UPDATEDATE": + return "cmsContentVersion.VersionDate"; + case "NAME": + return "umbracoNode.text"; + case "OWNER": + //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter + return "umbracoNode.nodeUser"; + // Members only + case "USERNAME": + return "cmsMember.LoginName"; + default: + //ensure invalid SQL cannot be submitted + return Regex.Replace(orderBy, @"[^\w\.,`\[\]@-]", ""); + } + } - /// - /// Deletes all media files passed in. - /// - /// - /// - public virtual bool DeleteMediaFiles(IEnumerable files) - { - //ensure duplicates are removed - files = files.Distinct(); + protected virtual string GetEntityPropertyNameForOrderBy(string orderBy) + { + // Translate the passed order by field (which were originally defined for in-memory object sorting + // of ContentItemBasic instances) to the IMedia property names. + switch (orderBy.ToUpperInvariant()) + { + case "OWNER": + //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter + return "CreatorId"; + case "UPDATER": + //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter + return "WriterId"; + case "VERSIONDATE": + return "UpdateDate"; + default: + //ensure invalid SQL cannot be submitted + return Regex.Replace(orderBy, @"[^\w\.,`\[\]@-]", ""); + } + } - var allsuccess = true; + /// + /// Deletes all media files passed in. + /// + /// + /// + public virtual bool DeleteMediaFiles(IEnumerable files) + { + //ensure duplicates are removed + files = files.Distinct(); - var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); - Parallel.ForEach(files, file => - { - try - { - if (file.IsNullOrWhiteSpace()) return; + var allsuccess = true; - var relativeFilePath = fs.GetRelativePath(file); - if (fs.FileExists(relativeFilePath) == false) return; + var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); + Parallel.ForEach(files, file => + { + try + { + if (file.IsNullOrWhiteSpace()) return; - var parentDirectory = System.IO.Path.GetDirectoryName(relativeFilePath); + var relativeFilePath = fs.GetRelativePath(file); + if (fs.FileExists(relativeFilePath) == false) return; - // don't want to delete the media folder if not using directories. - if (_contentSection.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) - { - Logger.Error>("An error occurred while deleting file attached to nodes: " + file, e); - allsuccess = false; - } - }); + var parentDirectory = System.IO.Path.GetDirectoryName(relativeFilePath); - return allsuccess; - } - } + // don't want to delete the media folder if not using directories. + if (_contentSection.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) + { + Logger.Error>("An error occurred while deleting file attached to nodes: " + file, e); + allsuccess = false; + } + }); + + return allsuccess; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index 056d9c1d9a..ef4601f1e5 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -6,75 +6,79 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.SqlSyntax { - /// - /// Defines an SqlSyntaxProvider - /// - public interface ISqlSyntaxProvider - { - string EscapeString(string val); + /// + /// Defines an SqlSyntaxProvider + /// + public interface ISqlSyntaxProvider + { + string EscapeString(string val); - string GetWildcardPlaceholder(); - string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType); - string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType); + string GetWildcardPlaceholder(); + string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType); + string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType); - [Obsolete("Use the overload with the parameter index instead")] - string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType); - [Obsolete("Use the overload with the parameter index instead")] - string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType); - [Obsolete("Use the overload with the parameter index instead")] - string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType); - [Obsolete("Use the overload with the parameter index instead")] - string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType); - [Obsolete("Use the overload with the parameter index instead")] - string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType); + [Obsolete("Use the overload with the parameter index instead")] + string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType); + [Obsolete("Use the overload with the parameter index instead")] + string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType); + [Obsolete("Use the overload with the parameter index instead")] + string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType); + [Obsolete("Use the overload with the parameter index instead")] + string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType); + [Obsolete("Use the overload with the parameter index instead")] + string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType); - string GetQuotedTableName(string tableName); - string GetQuotedColumnName(string columnName); - string GetQuotedName(string name); - bool DoesTableExist(Database db, string tableName); - string GetIndexType(IndexTypes indexTypes); - string GetSpecialDbType(SpecialDbTypes dbTypes); - string CreateTable { get; } - string DropTable { get; } - string AddColumn { get; } - string DropColumn { get; } - string AlterColumn { get; } - string RenameColumn { get; } - string RenameTable { get; } - string CreateSchema { get; } - string AlterSchema { get; } - string DropSchema { get; } - string CreateIndex { get; } - string DropIndex { get; } - string InsertData { get; } - string UpdateData { get; } - string DeleteData { get; } - string TruncateTable { get; } - string CreateConstraint { get; } - string DeleteConstraint { get; } - string CreateForeignKeyConstraint { get; } - string DeleteDefaultConstraint { get; } - string FormatDateTime(DateTime date, bool includeTime = true); - string Format(TableDefinition table); - string Format(IEnumerable columns); - List Format(IEnumerable indexes); - List Format(IEnumerable foreignKeys); - string FormatPrimaryKey(TableDefinition table); - string GetQuotedValue(string value); - string Format(ColumnDefinition column); - string Format(IndexDefinition index); - string Format(ForeignKeyDefinition foreignKey); - string FormatColumnRename(string tableName, string oldName, string newName); - string FormatTableRename(string oldName, string newName); - bool SupportsClustered(); - bool SupportsIdentityInsert(); - bool? SupportsCaseInsensitiveQueries(Database db); + string GetQuotedTableName(string tableName); + string GetQuotedColumnName(string columnName); + string GetQuotedName(string name); + bool DoesTableExist(Database db, string tableName); + string GetIndexType(IndexTypes indexTypes); + string GetSpecialDbType(SpecialDbTypes dbTypes); + string CreateTable { get; } + string DropTable { get; } + string AddColumn { get; } + string DropColumn { get; } + string AlterColumn { get; } + string RenameColumn { get; } + string RenameTable { get; } + string CreateSchema { get; } + string AlterSchema { get; } + string DropSchema { get; } + string CreateIndex { get; } + string DropIndex { get; } + string InsertData { get; } + string UpdateData { get; } + string DeleteData { get; } + string TruncateTable { get; } + string CreateConstraint { get; } + string DeleteConstraint { get; } + string CreateForeignKeyConstraint { get; } + string DeleteDefaultConstraint { get; } + string FormatDateTime(DateTime date, bool includeTime = true); + string Format(TableDefinition table); + string Format(IEnumerable columns); + List Format(IEnumerable indexes); + List Format(IEnumerable foreignKeys); + string FormatPrimaryKey(TableDefinition table); + string GetQuotedValue(string value); + string Format(ColumnDefinition column); + string Format(IndexDefinition index); + string Format(ForeignKeyDefinition foreignKey); + string FormatColumnRename(string tableName, string oldName, string newName); + string FormatTableRename(string oldName, string newName); + bool SupportsClustered(); + bool SupportsIdentityInsert(); + bool? SupportsCaseInsensitiveQueries(Database db); - IEnumerable GetTablesInSchema(Database db); - IEnumerable GetColumnsInSchema(Database db); - IEnumerable> GetConstraintsPerTable(Database db); - IEnumerable> GetConstraintsPerColumn(Database db); + string IsNull { get; } + string ConvertIntegerToOrderableString { get; } + string ConvertDateToOrderableString { get; } - IEnumerable> GetDefinedIndexes(Database db); - } + IEnumerable GetTablesInSchema(Database db); + IEnumerable GetColumnsInSchema(Database db); + IEnumerable> GetConstraintsPerTable(Database db); + IEnumerable> GetConstraintsPerColumn(Database db); + + IEnumerable> GetDefinedIndexes(Database db); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index c18a5f8477..c29afa7d32 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -7,390 +7,393 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Persistence.SqlSyntax { - /// - /// Represents an SqlSyntaxProvider for MySql - /// - [SqlSyntaxProviderAttribute("MySql.Data.MySqlClient")] - public class MySqlSyntaxProvider : SqlSyntaxProviderBase - { - private readonly ILogger _logger; + /// + /// Represents an SqlSyntaxProvider for MySql + /// + [SqlSyntaxProviderAttribute("MySql.Data.MySqlClient")] + public class MySqlSyntaxProvider : SqlSyntaxProviderBase + { + private readonly ILogger _logger; - public MySqlSyntaxProvider(ILogger logger) - { - _logger = logger; - - AutoIncrementDefinition = "AUTO_INCREMENT"; - IntColumnDefinition = "int(11)"; - BoolColumnDefinition = "tinyint(1)"; - DateTimeColumnDefinition = "TIMESTAMP"; - TimeColumnDefinition = "time"; - DecimalColumnDefinition = "decimal(38,6)"; - GuidColumnDefinition = "char(36)"; - - DefaultValueFormat = "DEFAULT {0}"; + public MySqlSyntaxProvider(ILogger logger) + { + _logger = logger; + + AutoIncrementDefinition = "AUTO_INCREMENT"; + IntColumnDefinition = "int(11)"; + BoolColumnDefinition = "tinyint(1)"; + DateTimeColumnDefinition = "TIMESTAMP"; + TimeColumnDefinition = "time"; + DecimalColumnDefinition = "decimal(38,6)"; + GuidColumnDefinition = "char(36)"; + + DefaultValueFormat = "DEFAULT {0}"; InitColumnTypeMap(); - } + } - public override IEnumerable GetTablesInSchema(Database db) - { - List list; - try - { - //needs to be open to read the schema name - db.OpenSharedConnection(); + public override IEnumerable GetTablesInSchema(Database db) + { + List list; + try + { + //needs to be open to read the schema name + db.OpenSharedConnection(); - var items = - db.Fetch( - "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = @TableSchema", - new { TableSchema = db.Connection.Database }); - list = items.Select(x => x.TABLE_NAME).Cast().ToList(); - } - finally - { - db.CloseSharedConnection(); - } - return list; - } + var items = + db.Fetch( + "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = @TableSchema", + new { TableSchema = db.Connection.Database }); + list = items.Select(x => x.TABLE_NAME).Cast().ToList(); + } + finally + { + db.CloseSharedConnection(); + } + return list; + } - public override IEnumerable GetColumnsInSchema(Database db) - { - List list; - try - { - //needs to be open to read the schema name - db.OpenSharedConnection(); + public override IEnumerable GetColumnsInSchema(Database db) + { + List list; + try + { + //needs to be open to read the schema name + db.OpenSharedConnection(); - var items = - db.Fetch( - "SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @TableSchema", - new { TableSchema = db.Connection.Database }); - list = - items.Select( - item => - new ColumnInfo(item.TABLE_NAME, item.COLUMN_NAME, int.Parse(item.ORDINAL_POSITION.ToString()), item.COLUMN_DEFAULT ?? "", - item.IS_NULLABLE, item.DATA_TYPE)).ToList(); - } - finally - { - db.CloseSharedConnection(); - } - return list; - } + var items = + db.Fetch( + "SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @TableSchema", + new { TableSchema = db.Connection.Database }); + list = + items.Select( + item => + new ColumnInfo(item.TABLE_NAME, item.COLUMN_NAME, int.Parse(item.ORDINAL_POSITION.ToString()), item.COLUMN_DEFAULT ?? "", + item.IS_NULLABLE, item.DATA_TYPE)).ToList(); + } + finally + { + db.CloseSharedConnection(); + } + return list; + } - public override IEnumerable> GetConstraintsPerTable(Database db) - { - List> list; - try - { - //needs to be open to read the schema name - db.OpenSharedConnection(); + public override IEnumerable> GetConstraintsPerTable(Database db) + { + List> list; + try + { + //needs to be open to read the schema name + db.OpenSharedConnection(); - //Does not include indexes and constraints are named differently - var items = - db.Fetch( - "SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = @TableSchema", - new { TableSchema = db.Connection.Database }); - list = items.Select(item => new Tuple(item.TABLE_NAME, item.CONSTRAINT_NAME)).ToList(); - } - finally - { - db.CloseSharedConnection(); - } - return list; - } + //Does not include indexes and constraints are named differently + var items = + db.Fetch( + "SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = @TableSchema", + new { TableSchema = db.Connection.Database }); + list = items.Select(item => new Tuple(item.TABLE_NAME, item.CONSTRAINT_NAME)).ToList(); + } + finally + { + db.CloseSharedConnection(); + } + return list; + } - public override IEnumerable> GetConstraintsPerColumn(Database db) - { - List> list; - try - { - //needs to be open to read the schema name - db.OpenSharedConnection(); + public override IEnumerable> GetConstraintsPerColumn(Database db) + { + List> list; + try + { + //needs to be open to read the schema name + db.OpenSharedConnection(); - //Does not include indexes and constraints are named differently - var items = - db.Fetch( - "SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = @TableSchema", - new { TableSchema = db.Connection.Database }); - list = - items.Select( - item => - new Tuple(item.TABLE_NAME, item.COLUMN_NAME, item.CONSTRAINT_NAME)) - .ToList(); - } - finally - { - db.CloseSharedConnection(); - } - return list; - } + //Does not include indexes and constraints are named differently + var items = + db.Fetch( + "SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = @TableSchema", + new { TableSchema = db.Connection.Database }); + list = + items.Select( + item => + new Tuple(item.TABLE_NAME, item.COLUMN_NAME, item.CONSTRAINT_NAME)) + .ToList(); + } + finally + { + db.CloseSharedConnection(); + } + return list; + } - public override IEnumerable> GetDefinedIndexes(Database db) - { - List> list; - try - { - //needs to be open to read the schema name - db.OpenSharedConnection(); + public override IEnumerable> GetDefinedIndexes(Database db) + { + List> list; + try + { + //needs to be open to read the schema name + db.OpenSharedConnection(); - var indexes = - db.Fetch(@"SELECT DISTINCT + var indexes = + db.Fetch(@"SELECT DISTINCT TABLE_NAME, INDEX_NAME, COLUMN_NAME, CASE NON_UNIQUE WHEN 1 THEN 0 ELSE 1 END AS `UNIQUE` FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = @TableSchema AND INDEX_NAME <> COLUMN_NAME AND INDEX_NAME <> 'PRIMARY' ORDER BY TABLE_NAME, INDEX_NAME", - new { TableSchema = db.Connection.Database }); - list = - indexes.Select( - item => - new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, item.UNIQUE == 1)) - .ToList(); - } - finally - { - db.CloseSharedConnection(); - } - return list; - } + new { TableSchema = db.Connection.Database }); + list = + indexes.Select( + item => + new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, item.UNIQUE == 1)) + .ToList(); + } + finally + { + db.CloseSharedConnection(); + } + return list; + } - public override bool DoesTableExist(Database db, string tableName) - { - long result; - try - { - //needs to be open to read the schema name - db.OpenSharedConnection(); + public override bool DoesTableExist(Database db, string tableName) + { + long result; + try + { + //needs to be open to read the schema name + db.OpenSharedConnection(); - result = - db.ExecuteScalar("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES " + - "WHERE TABLE_NAME = @TableName AND " + - "TABLE_SCHEMA = @TableSchema", - new { TableName = tableName, TableSchema = db.Connection.Database }); + result = + db.ExecuteScalar("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES " + + "WHERE TABLE_NAME = @TableName AND " + + "TABLE_SCHEMA = @TableSchema", + new { TableName = tableName, TableSchema = db.Connection.Database }); - } - finally - { - db.CloseSharedConnection(); - } + } + finally + { + db.CloseSharedConnection(); + } - return result > 0; - } + return result > 0; + } - public override bool SupportsClustered() - { - return true; - } + public override bool SupportsClustered() + { + return true; + } - public override bool SupportsIdentityInsert() - { - return false; - } + public override bool SupportsIdentityInsert() + { + return false; + } - /// - /// This is used ONLY if we need to format datetime without using SQL parameters (i.e. during migrations) - /// - /// - /// - /// - /// - /// MySQL has a DateTime standard that is unambiguous and works on all servers: - /// YYYYMMDDHHMMSS - /// - public override string FormatDateTime(DateTime date, bool includeTime = true) - { - return includeTime ? date.ToString("yyyyMMddHHmmss") : date.ToString("yyyyMMdd"); - } + /// + /// This is used ONLY if we need to format datetime without using SQL parameters (i.e. during migrations) + /// + /// + /// + /// + /// + /// MySQL has a DateTime standard that is unambiguous and works on all servers: + /// YYYYMMDDHHMMSS + /// + public override string FormatDateTime(DateTime date, bool includeTime = true) + { + return includeTime ? date.ToString("yyyyMMddHHmmss") : date.ToString("yyyyMMdd"); + } - public override string GetQuotedTableName(string tableName) - { - return string.Format("`{0}`", tableName); - } + public override string GetQuotedTableName(string tableName) + { + return string.Format("`{0}`", tableName); + } - public override string GetQuotedColumnName(string columnName) - { - return string.Format("`{0}`", columnName); - } + public override string GetQuotedColumnName(string columnName) + { + return string.Format("`{0}`", columnName); + } - public override string GetQuotedName(string name) - { - return string.Format("`{0}`", name); - } + public override string GetQuotedName(string name) + { + return string.Format("`{0}`", name); + } - public override string GetSpecialDbType(SpecialDbTypes dbTypes) - { - if (dbTypes == SpecialDbTypes.NCHAR) - { - return "CHAR"; - } - else if (dbTypes == SpecialDbTypes.NTEXT) - return "LONGTEXT"; + public override string GetSpecialDbType(SpecialDbTypes dbTypes) + { + if (dbTypes == SpecialDbTypes.NCHAR) + { + return "CHAR"; + } + else if (dbTypes == SpecialDbTypes.NTEXT) + return "LONGTEXT"; - return "NVARCHAR"; - } + return "NVARCHAR"; + } - public override string Format(TableDefinition table) - { - string primaryKey = string.Empty; - var columnDefinition = table.Columns.FirstOrDefault(x => x.IsPrimaryKey); - if (columnDefinition != null) - { - string columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns) - ? GetQuotedColumnName(columnDefinition.Name) - : string.Join(", ", columnDefinition.PrimaryKeyColumns - .Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(GetQuotedColumnName)); + public override string Format(TableDefinition table) + { + string primaryKey = string.Empty; + var columnDefinition = table.Columns.FirstOrDefault(x => x.IsPrimaryKey); + if (columnDefinition != null) + { + string columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns) + ? GetQuotedColumnName(columnDefinition.Name) + : string.Join(", ", columnDefinition.PrimaryKeyColumns + .Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + .Select(GetQuotedColumnName)); - primaryKey = string.Format(", \nPRIMARY KEY {0} ({1})", columnDefinition.IsIndexed ? "CLUSTERED" : "NONCLUSTERED", columns); - } + primaryKey = string.Format(", \nPRIMARY KEY {0} ({1})", columnDefinition.IsIndexed ? "CLUSTERED" : "NONCLUSTERED", columns); + } - var statement = string.Format(CreateTable, GetQuotedTableName(table.Name), Format(table.Columns), primaryKey); + var statement = string.Format(CreateTable, GetQuotedTableName(table.Name), Format(table.Columns), primaryKey); - return statement; - } + return statement; + } - public override string Format(IndexDefinition index) - { - string name = string.IsNullOrEmpty(index.Name) - ? string.Format("IX_{0}_{1}", index.TableName, index.ColumnName) - : index.Name; + public override string Format(IndexDefinition index) + { + string name = string.IsNullOrEmpty(index.Name) + ? string.Format("IX_{0}_{1}", index.TableName, index.ColumnName) + : index.Name; - string columns = index.Columns.Any() - ? string.Join(",", index.Columns.Select(x => GetQuotedColumnName(x.Name))) - : GetQuotedColumnName(index.ColumnName); + string columns = index.Columns.Any() + ? string.Join(",", index.Columns.Select(x => GetQuotedColumnName(x.Name))) + : GetQuotedColumnName(index.ColumnName); - return string.Format(CreateIndex, - GetQuotedName(name), - GetQuotedTableName(index.TableName), - columns); - } + return string.Format(CreateIndex, + GetQuotedName(name), + GetQuotedTableName(index.TableName), + columns); + } - public override string Format(ForeignKeyDefinition foreignKey) - { - return string.Format(CreateForeignKeyConstraint, - GetQuotedTableName(foreignKey.ForeignTable), - GetQuotedColumnName(foreignKey.ForeignColumns.First()), - GetQuotedTableName(foreignKey.PrimaryTable), - GetQuotedColumnName(foreignKey.PrimaryColumns.First()), - FormatCascade("DELETE", foreignKey.OnDelete), - FormatCascade("UPDATE", foreignKey.OnUpdate)); - } + public override string Format(ForeignKeyDefinition foreignKey) + { + return string.Format(CreateForeignKeyConstraint, + GetQuotedTableName(foreignKey.ForeignTable), + GetQuotedColumnName(foreignKey.ForeignColumns.First()), + GetQuotedTableName(foreignKey.PrimaryTable), + GetQuotedColumnName(foreignKey.PrimaryColumns.First()), + FormatCascade("DELETE", foreignKey.OnDelete), + FormatCascade("UPDATE", foreignKey.OnUpdate)); + } - public override string FormatPrimaryKey(TableDefinition table) - { - return string.Empty; - } + public override string FormatPrimaryKey(TableDefinition table) + { + return string.Empty; + } - protected override string FormatConstraint(ColumnDefinition column) - { - return string.Empty; - } + protected override string FormatConstraint(ColumnDefinition column) + { + return string.Empty; + } - protected override string FormatIdentity(ColumnDefinition column) - { - return column.IsIdentity ? AutoIncrementDefinition : string.Empty; - } + protected override string FormatIdentity(ColumnDefinition column) + { + return column.IsIdentity ? AutoIncrementDefinition : string.Empty; + } - protected override string FormatDefaultValue(ColumnDefinition column) - { - if (column.DefaultValue == null) - return string.Empty; + protected override string FormatDefaultValue(ColumnDefinition column) + { + if (column.DefaultValue == null) + return string.Empty; - //hack - probably not needed with latest changes - if (column.DefaultValue.ToString().ToLower().Equals("getdate()".ToLower())) - column.DefaultValue = SystemMethods.CurrentDateTime; + //hack - probably not needed with latest changes + if (column.DefaultValue.ToString().ToLower().Equals("getdate()".ToLower())) + column.DefaultValue = SystemMethods.CurrentDateTime; - // see if this is for a system method - if (column.DefaultValue is SystemMethods) - { - string method = FormatSystemMethods((SystemMethods)column.DefaultValue); - if (string.IsNullOrEmpty(method)) - return string.Empty; + // see if this is for a system method + if (column.DefaultValue is SystemMethods) + { + string method = FormatSystemMethods((SystemMethods)column.DefaultValue); + if (string.IsNullOrEmpty(method)) + return string.Empty; - return string.Format(DefaultValueFormat, method); - } + return string.Format(DefaultValueFormat, method); + } - //needs quote - return string.Format(DefaultValueFormat, string.Format("'{0}'", column.DefaultValue)); - } + //needs quote + return string.Format(DefaultValueFormat, string.Format("'{0}'", column.DefaultValue)); + } - protected override string FormatPrimaryKey(ColumnDefinition column) - { - return string.Empty; - } + protected override string FormatPrimaryKey(ColumnDefinition column) + { + return string.Empty; + } - protected override string FormatSystemMethods(SystemMethods systemMethod) - { - switch (systemMethod) - { - case SystemMethods.NewGuid: - return null; // NOT SUPPORTED! - //return "NEWID()"; - case SystemMethods.CurrentDateTime: - return "CURRENT_TIMESTAMP"; - //case SystemMethods.NewSequentialId: - // return "NEWSEQUENTIALID()"; - //case SystemMethods.CurrentUTCDateTime: - // return "GETUTCDATE()"; - } + protected override string FormatSystemMethods(SystemMethods systemMethod) + { + switch (systemMethod) + { + case SystemMethods.NewGuid: + return null; // NOT SUPPORTED! + //return "NEWID()"; + case SystemMethods.CurrentDateTime: + return "CURRENT_TIMESTAMP"; + //case SystemMethods.NewSequentialId: + // return "NEWSEQUENTIALID()"; + //case SystemMethods.CurrentUTCDateTime: + // return "GETUTCDATE()"; + } - return null; - } + return null; + } - public override string DeleteDefaultConstraint - { - get - { - return "ALTER TABLE {0} ALTER COLUMN {1} DROP DEFAULT"; - } - } + public override string DeleteDefaultConstraint + { + get + { + return "ALTER TABLE {0} ALTER COLUMN {1} DROP DEFAULT"; + } + } - public override string AlterColumn { get { return "ALTER TABLE {0} MODIFY COLUMN {1}"; } } + public override string AlterColumn { get { return "ALTER TABLE {0} MODIFY COLUMN {1}"; } } - //CREATE TABLE {0} ({1}) ENGINE = INNODB versus CREATE TABLE {0} ({1}) ENGINE = MYISAM ? - public override string CreateTable { get { return "CREATE TABLE {0} ({1}{2})"; } } + //CREATE TABLE {0} ({1}) ENGINE = INNODB versus CREATE TABLE {0} ({1}) ENGINE = MYISAM ? + public override string CreateTable { get { return "CREATE TABLE {0} ({1}{2})"; } } - public override string CreateIndex { get { return "CREATE INDEX {0} ON {1} ({2})"; } } + public override string CreateIndex { get { return "CREATE INDEX {0} ON {1} ({2})"; } } - public override string CreateForeignKeyConstraint { get { return "ALTER TABLE {0} ADD FOREIGN KEY ({1}) REFERENCES {2} ({3}){4}{5}"; } } + public override string CreateForeignKeyConstraint { get { return "ALTER TABLE {0} ADD FOREIGN KEY ({1}) REFERENCES {2} ({3}){4}{5}"; } } - public override string DeleteConstraint { get { return "ALTER TABLE {0} DROP {1} {2}"; } } + public override string DeleteConstraint { get { return "ALTER TABLE {0} DROP {1} {2}"; } } - public override string DropIndex { get { return "DROP INDEX {0} ON {1}"; } } + public override string DropIndex { get { return "DROP INDEX {0} ON {1}"; } } - public override string RenameColumn { get { return "ALTER TABLE {0} CHANGE {1} {2}"; } } + public override string RenameColumn { get { return "ALTER TABLE {0} CHANGE {1} {2}"; } } + public override string IsNull { get { return "IFNULL({0},{1})"; } } + public override string ConvertIntegerToOrderableString { get { return "LPAD({0}, 8, '0')"; } } + public override string ConvertDateToOrderableString { get { return "DATE_FORMAT({0}, '%Y%m%d')"; } } - public override bool? SupportsCaseInsensitiveQueries(Database db) - { - bool? supportsCaseInsensitiveQueries = null; + public override bool? SupportsCaseInsensitiveQueries(Database db) + { + bool? supportsCaseInsensitiveQueries = null; - try - { - db.OpenSharedConnection(); - // Need 4 @ signs as it is regarded as a parameter, @@ escapes it once, @@@@ escapes it twice - var lowerCaseTableNames = db.Fetch("SELECT @@@@Global.lower_case_table_names"); + try + { + db.OpenSharedConnection(); + // Need 4 @ signs as it is regarded as a parameter, @@ escapes it once, @@@@ escapes it twice + var lowerCaseTableNames = db.Fetch("SELECT @@@@Global.lower_case_table_names"); - if (lowerCaseTableNames.Any()) - supportsCaseInsensitiveQueries = lowerCaseTableNames.First() == 1; - } - catch (Exception ex) - { - _logger.Error("Error querying for lower_case support", ex); - } - finally - { - db.CloseSharedConnection(); - } + if (lowerCaseTableNames.Any()) + supportsCaseInsensitiveQueries = lowerCaseTableNames.First() == 1; + } + catch (Exception ex) + { + _logger.Error("Error querying for lower_case support", ex); + } + finally + { + db.CloseSharedConnection(); + } - // Could return null, which means testing failed, - // add message to check with their hosting provider - return supportsCaseInsensitiveQueries; - } + // Could return null, which means testing failed, + // add message to check with their hosting provider + return supportsCaseInsensitiveQueries; + } - public override string EscapeString(string val) - { - return PetaPocoExtensions.EscapeAtSymbols(MySql.Data.MySqlClient.MySqlHelper.EscapeString(val)); - } - } + public override string EscapeString(string val) + { + return PetaPocoExtensions.EscapeAtSymbols(MySql.Data.MySqlClient.MySqlHelper.EscapeString(val)); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index c2b81aa753..3b3fe103d0 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -10,531 +10,535 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.SqlSyntax { - /// - /// Represents the Base Sql Syntax provider implementation. - /// - /// - /// All Sql Syntax provider implementations should derive from this abstract class. - /// - /// - public abstract class SqlSyntaxProviderBase : ISqlSyntaxProvider - where TSyntax : ISqlSyntaxProvider - { - protected SqlSyntaxProviderBase() - { - ClauseOrder = new List> - { - FormatString, - FormatType, - FormatNullable, - FormatConstraint, - FormatDefaultValue, - FormatPrimaryKey, - FormatIdentity - }; - - //defaults for all providers - StringLengthColumnDefinitionFormat = StringLengthUnicodeColumnDefinitionFormat; - StringColumnDefinition = string.Format(StringLengthColumnDefinitionFormat, DefaultStringLength); - DecimalColumnDefinition = string.Format(DecimalColumnDefinitionFormat, DefaultDecimalPrecision, DefaultDecimalScale); - - InitColumnTypeMap(); - } - - public string GetWildcardPlaceholder() - { - return "%"; - } - - public string StringLengthNonUnicodeColumnDefinitionFormat = "VARCHAR({0})"; - public string StringLengthUnicodeColumnDefinitionFormat = "NVARCHAR({0})"; - public string DecimalColumnDefinitionFormat = "DECIMAL({0},{1})"; - - public string DefaultValueFormat = "DEFAULT ({0})"; - public int DefaultStringLength = 255; - public int DefaultDecimalPrecision = 20; - public int DefaultDecimalScale = 9; - - //Set by Constructor - public string StringColumnDefinition; - public string StringLengthColumnDefinitionFormat; - - public string AutoIncrementDefinition = "AUTOINCREMENT"; - public string IntColumnDefinition = "INTEGER"; - public string LongColumnDefinition = "BIGINT"; - public string GuidColumnDefinition = "GUID"; - public string BoolColumnDefinition = "BOOL"; - public string RealColumnDefinition = "DOUBLE"; - public string DecimalColumnDefinition; - public string BlobColumnDefinition = "BLOB"; - public string DateTimeColumnDefinition = "DATETIME"; - public string TimeColumnDefinition = "DATETIME"; - - protected IList> ClauseOrder { get; set; } - - protected DbTypes DbTypeMap = new DbTypes(); - protected void InitColumnTypeMap() - { - DbTypeMap.Set(DbType.String, StringColumnDefinition); - DbTypeMap.Set(DbType.StringFixedLength, StringColumnDefinition); - DbTypeMap.Set(DbType.StringFixedLength, StringColumnDefinition); - DbTypeMap.Set(DbType.String, StringColumnDefinition); - DbTypeMap.Set(DbType.Boolean, BoolColumnDefinition); - DbTypeMap.Set(DbType.Boolean, BoolColumnDefinition); - DbTypeMap.Set(DbType.Guid, GuidColumnDefinition); - DbTypeMap.Set(DbType.Guid, GuidColumnDefinition); - DbTypeMap.Set(DbType.DateTime, DateTimeColumnDefinition); - DbTypeMap.Set(DbType.DateTime, DateTimeColumnDefinition); - DbTypeMap.Set(DbType.Time, TimeColumnDefinition); - DbTypeMap.Set(DbType.Time, TimeColumnDefinition); - DbTypeMap.Set(DbType.Time, TimeColumnDefinition); - DbTypeMap.Set(DbType.Time, TimeColumnDefinition); - - DbTypeMap.Set(DbType.Byte, IntColumnDefinition); - DbTypeMap.Set(DbType.Byte, IntColumnDefinition); - DbTypeMap.Set(DbType.SByte, IntColumnDefinition); - DbTypeMap.Set(DbType.SByte, IntColumnDefinition); - DbTypeMap.Set(DbType.Int16, IntColumnDefinition); - DbTypeMap.Set(DbType.Int16, IntColumnDefinition); - DbTypeMap.Set(DbType.UInt16, IntColumnDefinition); - DbTypeMap.Set(DbType.UInt16, IntColumnDefinition); - DbTypeMap.Set(DbType.Int32, IntColumnDefinition); - DbTypeMap.Set(DbType.Int32, IntColumnDefinition); - DbTypeMap.Set(DbType.UInt32, IntColumnDefinition); - DbTypeMap.Set(DbType.UInt32, IntColumnDefinition); - - DbTypeMap.Set(DbType.Int64, LongColumnDefinition); - DbTypeMap.Set(DbType.Int64, LongColumnDefinition); - DbTypeMap.Set(DbType.UInt64, LongColumnDefinition); - DbTypeMap.Set(DbType.UInt64, LongColumnDefinition); - - DbTypeMap.Set(DbType.Single, RealColumnDefinition); - DbTypeMap.Set(DbType.Single, RealColumnDefinition); - DbTypeMap.Set(DbType.Double, RealColumnDefinition); - DbTypeMap.Set(DbType.Double, RealColumnDefinition); - - DbTypeMap.Set(DbType.Decimal, DecimalColumnDefinition); - DbTypeMap.Set(DbType.Decimal, DecimalColumnDefinition); - - DbTypeMap.Set(DbType.Binary, BlobColumnDefinition); - } - - public virtual string EscapeString(string val) - { - return PetaPocoExtensions.EscapeAtSymbols(val.Replace("'", "''")); - } - - public virtual string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return string.Format("upper({0}) = upper(@{1})", column, paramIndex); - } - - public virtual string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return string.Format("upper({0}) LIKE upper(@{1})", column, paramIndex); - } - - [Obsolete("Use the overload with the parameter index instead")] - public virtual string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return string.Format("upper({0}) = '{1}'", column, value.ToUpper()); - } - - [Obsolete("Use the overload with the parameter index instead")] - public virtual string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return string.Format("upper({0}) LIKE '{1}%'", column, value.ToUpper()); - } - - [Obsolete("Use the overload with the parameter index instead")] - public virtual string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return string.Format("upper({0}) LIKE '%{1}'", column, value.ToUpper()); - } - - [Obsolete("Use the overload with the parameter index instead")] - public virtual string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return string.Format("upper({0}) LIKE '%{1}%'", column, value.ToUpper()); - } - - [Obsolete("Use the overload with the parameter index instead")] - public virtual string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return string.Format("upper({0}) LIKE '{1}'", column, value.ToUpper()); - } - - public virtual string GetQuotedTableName(string tableName) - { - return string.Format("\"{0}\"", tableName); - } - - public virtual string GetQuotedColumnName(string columnName) - { - return string.Format("\"{0}\"", columnName); - } - - public virtual string GetQuotedName(string name) - { - return string.Format("\"{0}\"", name); - } - - public virtual string GetQuotedValue(string value) - { - return string.Format("'{0}'", value); - } - - public virtual string GetIndexType(IndexTypes indexTypes) - { - string indexType; - - if (indexTypes == IndexTypes.Clustered) - { - indexType = "CLUSTERED"; - } - else - { - indexType = indexTypes == IndexTypes.NonClustered - ? "NONCLUSTERED" - : "UNIQUE NONCLUSTERED"; - } - return indexType; - } - - public virtual string GetSpecialDbType(SpecialDbTypes dbTypes) - { - if (dbTypes == SpecialDbTypes.NCHAR) - { - return "NCHAR"; - } - else if (dbTypes == SpecialDbTypes.NTEXT) - return "NTEXT"; - - return "NVARCHAR"; - } - - public virtual bool? SupportsCaseInsensitiveQueries(Database db) - { - return true; - } - - public virtual IEnumerable GetTablesInSchema(Database db) - { - return new List(); - } - - public virtual IEnumerable GetColumnsInSchema(Database db) - { - return new List(); - } - - public virtual IEnumerable> GetConstraintsPerTable(Database db) - { - return new List>(); - } - - public virtual IEnumerable> GetConstraintsPerColumn(Database db) - { - return new List>(); - } - - public abstract IEnumerable> GetDefinedIndexes(Database db); - - public virtual bool DoesTableExist(Database db, string tableName) - { - return false; - } - - public virtual bool SupportsClustered() - { - return true; - } - - public virtual bool SupportsIdentityInsert() - { - return true; - } - - /// - /// This is used ONLY if we need to format datetime without using SQL parameters (i.e. during migrations) - /// - /// - /// - /// - /// - /// MSSQL has a DateTime standard that is unambiguous and works on all servers: - /// YYYYMMDD HH:mm:ss - /// - public virtual string FormatDateTime(DateTime date, bool includeTime = true) - { - // need CultureInfo.InvariantCulture because ":" here is the "time separator" and - // may be converted to something else in different cultures (eg "." in DK). - return date.ToString(includeTime ? "yyyyMMdd HH:mm:ss" : "yyyyMMdd", CultureInfo.InvariantCulture); - } - - public virtual string Format(TableDefinition table) - { - var statement = string.Format(CreateTable, GetQuotedTableName(table.Name), Format(table.Columns)); - - return statement; - } - - public virtual List Format(IEnumerable indexes) - { - return indexes.Select(Format).ToList(); - } - - public virtual string Format(IndexDefinition index) - { - string name = string.IsNullOrEmpty(index.Name) - ? string.Format("IX_{0}_{1}", index.TableName, index.ColumnName) - : index.Name; - - string columns = index.Columns.Any() - ? string.Join(",", index.Columns.Select(x => GetQuotedColumnName(x.Name))) - : GetQuotedColumnName(index.ColumnName); - - return string.Format(CreateIndex, GetIndexType(index.IndexType), " ", GetQuotedName(name), - GetQuotedTableName(index.TableName), columns); - } - - public virtual List Format(IEnumerable foreignKeys) - { - return foreignKeys.Select(Format).ToList(); - } - - public virtual string Format(ForeignKeyDefinition foreignKey) - { - string constraintName = string.IsNullOrEmpty(foreignKey.Name) - ? string.Format("FK_{0}_{1}_{2}", foreignKey.ForeignTable, foreignKey.PrimaryTable, foreignKey.PrimaryColumns.First()) - : foreignKey.Name; - - return string.Format(CreateForeignKeyConstraint, - GetQuotedTableName(foreignKey.ForeignTable), - GetQuotedName(constraintName), - GetQuotedColumnName(foreignKey.ForeignColumns.First()), - GetQuotedTableName(foreignKey.PrimaryTable), - GetQuotedColumnName(foreignKey.PrimaryColumns.First()), - FormatCascade("DELETE", foreignKey.OnDelete), - FormatCascade("UPDATE", foreignKey.OnUpdate)); - } - - public virtual string Format(IEnumerable columns) - { - var sb = new StringBuilder(); - foreach (var column in columns) - { - sb.Append(Format(column) +",\n"); - } - return sb.ToString().TrimEnd(",\n"); - } - - public virtual string Format(ColumnDefinition column) - { - var clauses = new List(); - - foreach (var action in ClauseOrder) - { - string clause = action(column); - if (!string.IsNullOrEmpty(clause)) - clauses.Add(clause); - } - - return string.Join(" ", clauses.ToArray()); - } - - public virtual string FormatPrimaryKey(TableDefinition table) - { - var columnDefinition = table.Columns.FirstOrDefault(x => x.IsPrimaryKey); - if (columnDefinition == null) - return string.Empty; - - string constraintName = string.IsNullOrEmpty(columnDefinition.PrimaryKeyName) - ? string.Format("PK_{0}", table.Name) - : columnDefinition.PrimaryKeyName; - - string columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns) - ? GetQuotedColumnName(columnDefinition.Name) - : string.Join(", ", columnDefinition.PrimaryKeyColumns - .Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(GetQuotedColumnName)); - - string primaryKeyPart = string.Concat("PRIMARY KEY", columnDefinition.IsIndexed ? " CLUSTERED" : " NONCLUSTERED"); - - return string.Format(CreateConstraint, - GetQuotedTableName(table.Name), - GetQuotedName(constraintName), - primaryKeyPart, - columns); - } - - public virtual string FormatColumnRename(string tableName, string oldName, string newName) - { - return string.Format(RenameColumn, - GetQuotedTableName(tableName), - GetQuotedColumnName(oldName), - GetQuotedColumnName(newName)); - } - - public virtual string FormatTableRename(string oldName, string newName) - { - return string.Format(RenameTable, GetQuotedTableName(oldName), GetQuotedTableName(newName)); - } - - protected virtual string FormatCascade(string onWhat, Rule rule) - { - string action = "NO ACTION"; - switch (rule) - { - case Rule.None: - return ""; - case Rule.Cascade: - action = "CASCADE"; - break; - case Rule.SetNull: - action = "SET NULL"; - break; - case Rule.SetDefault: - action = "SET DEFAULT"; - break; - } - - return string.Format(" ON {0} {1}", onWhat, action); - } - - protected virtual string FormatString(ColumnDefinition column) - { - return GetQuotedColumnName(column.Name); - } - - protected virtual string FormatType(ColumnDefinition column) - { - if (column.Type.HasValue == false && string.IsNullOrEmpty(column.CustomType) == false) - return column.CustomType; - - if (column.HasSpecialDbType) - { - if (column.Size != default(int)) - { - return string.Format("{0}({1})", - GetSpecialDbType(column.DbType), - column.Size); - } - - return GetSpecialDbType(column.DbType); - } - - Type type = column.Type.HasValue - ? DbTypeMap.ColumnDbTypeMap.First(x => x.Value == column.Type.Value).Key - : column.PropertyType; - - if (type == typeof (string)) - { - var valueOrDefault = column.Size != default(int) ? column.Size : DefaultStringLength; - return string.Format(StringLengthColumnDefinitionFormat, valueOrDefault); - } - - if (type == typeof(decimal)) - { - var precision = column.Size != default(int) ? column.Size : DefaultDecimalPrecision; - var scale = column.Precision != default(int) ? column.Precision : DefaultDecimalScale; - return string.Format(DecimalColumnDefinitionFormat, precision, scale); - } - - string definition = DbTypeMap.ColumnTypeMap.First(x => x.Key == type).Value; - string dbTypeDefinition = column.Size != default(int) - ? string.Format("{0}({1})", definition, column.Size) - : definition; - //NOTE Percision is left out - return dbTypeDefinition; - } - - protected virtual string FormatNullable(ColumnDefinition column) - { - return column.IsNullable ? "NULL" : "NOT NULL"; - } - - protected virtual string FormatConstraint(ColumnDefinition column) - { - if (string.IsNullOrEmpty(column.ConstraintName) && column.DefaultValue == null) - return string.Empty; - - return string.Format("CONSTRAINT {0}", - string.IsNullOrEmpty(column.ConstraintName) - ? GetQuotedName(string.Format("DF_{0}_{1}", column.TableName, column.Name)) - : column.ConstraintName); - } - - protected virtual string FormatDefaultValue(ColumnDefinition column) - { - if (column.DefaultValue == null) - return string.Empty; - - //hack - probably not needed with latest changes - if (column.DefaultValue.ToString().ToLower().Equals("getdate()".ToLower())) - column.DefaultValue = SystemMethods.CurrentDateTime; - - // see if this is for a system method - if (column.DefaultValue is SystemMethods) - { - string method = FormatSystemMethods((SystemMethods)column.DefaultValue); - if (string.IsNullOrEmpty(method)) - return string.Empty; - - return string.Format(DefaultValueFormat, method); - } - - return string.Format(DefaultValueFormat, GetQuotedValue(column.DefaultValue.ToString())); - } - - protected virtual string FormatPrimaryKey(ColumnDefinition column) - { - return string.Empty; - } - - protected abstract string FormatSystemMethods(SystemMethods systemMethod); - - protected abstract string FormatIdentity(ColumnDefinition column); - - public virtual string DeleteDefaultConstraint - { - get - { - throw new NotSupportedException("Default constraints are not supported"); - } - } - - public virtual string CreateTable { get { return "CREATE TABLE {0} ({1})"; } } - public virtual string DropTable { get { return "DROP TABLE {0}"; } } - - public virtual string AddColumn { get { return "ALTER TABLE {0} ADD COLUMN {1}"; } } - public virtual string DropColumn { get { return "ALTER TABLE {0} DROP COLUMN {1}"; } } - public virtual string AlterColumn { get { return "ALTER TABLE {0} ALTER COLUMN {1}"; } } - public virtual string RenameColumn { get { return "ALTER TABLE {0} RENAME COLUMN {1} TO {2}"; } } - - public virtual string RenameTable { get { return "RENAME TABLE {0} TO {1}"; } } - - public virtual string CreateSchema { get { return "CREATE SCHEMA {0}"; } } - public virtual string AlterSchema { get { return "ALTER SCHEMA {0} TRANSFER {1}.{2}"; } } - public virtual string DropSchema { get { return "DROP SCHEMA {0}"; } } - - public virtual string CreateIndex { get { return "CREATE {0}{1}INDEX {2} ON {3} ({4})"; } } - public virtual string DropIndex { get { return "DROP INDEX {0}"; } } - - public virtual string InsertData { get { return "INSERT INTO {0} ({1}) VALUES ({2})"; } } - public virtual string UpdateData { get { return "UPDATE {0} SET {1} WHERE {2}"; } } - public virtual string DeleteData { get { return "DELETE FROM {0} WHERE {1}"; } } - public virtual string TruncateTable { get { return "TRUNCATE TABLE {0}"; } } - - public virtual string CreateConstraint { get { return "ALTER TABLE {0} ADD CONSTRAINT {1} {2} ({3})"; } } - public virtual string DeleteConstraint { get { return "ALTER TABLE {0} DROP CONSTRAINT {1}"; } } - public virtual string CreateForeignKeyConstraint { get { return "ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4}){5}{6}"; } } - } + /// + /// Represents the Base Sql Syntax provider implementation. + /// + /// + /// All Sql Syntax provider implementations should derive from this abstract class. + /// + /// + public abstract class SqlSyntaxProviderBase : ISqlSyntaxProvider + where TSyntax : ISqlSyntaxProvider + { + protected SqlSyntaxProviderBase() + { + ClauseOrder = new List> + { + FormatString, + FormatType, + FormatNullable, + FormatConstraint, + FormatDefaultValue, + FormatPrimaryKey, + FormatIdentity + }; + + //defaults for all providers + StringLengthColumnDefinitionFormat = StringLengthUnicodeColumnDefinitionFormat; + StringColumnDefinition = string.Format(StringLengthColumnDefinitionFormat, DefaultStringLength); + DecimalColumnDefinition = string.Format(DecimalColumnDefinitionFormat, DefaultDecimalPrecision, DefaultDecimalScale); + + InitColumnTypeMap(); + } + + public string GetWildcardPlaceholder() + { + return "%"; + } + + public string StringLengthNonUnicodeColumnDefinitionFormat = "VARCHAR({0})"; + public string StringLengthUnicodeColumnDefinitionFormat = "NVARCHAR({0})"; + public string DecimalColumnDefinitionFormat = "DECIMAL({0},{1})"; + + public string DefaultValueFormat = "DEFAULT ({0})"; + public int DefaultStringLength = 255; + public int DefaultDecimalPrecision = 20; + public int DefaultDecimalScale = 9; + + //Set by Constructor + public string StringColumnDefinition; + public string StringLengthColumnDefinitionFormat; + + public string AutoIncrementDefinition = "AUTOINCREMENT"; + public string IntColumnDefinition = "INTEGER"; + public string LongColumnDefinition = "BIGINT"; + public string GuidColumnDefinition = "GUID"; + public string BoolColumnDefinition = "BOOL"; + public string RealColumnDefinition = "DOUBLE"; + public string DecimalColumnDefinition; + public string BlobColumnDefinition = "BLOB"; + public string DateTimeColumnDefinition = "DATETIME"; + public string TimeColumnDefinition = "DATETIME"; + + protected IList> ClauseOrder { get; set; } + + protected DbTypes DbTypeMap = new DbTypes(); + protected void InitColumnTypeMap() + { + DbTypeMap.Set(DbType.String, StringColumnDefinition); + DbTypeMap.Set(DbType.StringFixedLength, StringColumnDefinition); + DbTypeMap.Set(DbType.StringFixedLength, StringColumnDefinition); + DbTypeMap.Set(DbType.String, StringColumnDefinition); + DbTypeMap.Set(DbType.Boolean, BoolColumnDefinition); + DbTypeMap.Set(DbType.Boolean, BoolColumnDefinition); + DbTypeMap.Set(DbType.Guid, GuidColumnDefinition); + DbTypeMap.Set(DbType.Guid, GuidColumnDefinition); + DbTypeMap.Set(DbType.DateTime, DateTimeColumnDefinition); + DbTypeMap.Set(DbType.DateTime, DateTimeColumnDefinition); + DbTypeMap.Set(DbType.Time, TimeColumnDefinition); + DbTypeMap.Set(DbType.Time, TimeColumnDefinition); + DbTypeMap.Set(DbType.Time, TimeColumnDefinition); + DbTypeMap.Set(DbType.Time, TimeColumnDefinition); + + DbTypeMap.Set(DbType.Byte, IntColumnDefinition); + DbTypeMap.Set(DbType.Byte, IntColumnDefinition); + DbTypeMap.Set(DbType.SByte, IntColumnDefinition); + DbTypeMap.Set(DbType.SByte, IntColumnDefinition); + DbTypeMap.Set(DbType.Int16, IntColumnDefinition); + DbTypeMap.Set(DbType.Int16, IntColumnDefinition); + DbTypeMap.Set(DbType.UInt16, IntColumnDefinition); + DbTypeMap.Set(DbType.UInt16, IntColumnDefinition); + DbTypeMap.Set(DbType.Int32, IntColumnDefinition); + DbTypeMap.Set(DbType.Int32, IntColumnDefinition); + DbTypeMap.Set(DbType.UInt32, IntColumnDefinition); + DbTypeMap.Set(DbType.UInt32, IntColumnDefinition); + + DbTypeMap.Set(DbType.Int64, LongColumnDefinition); + DbTypeMap.Set(DbType.Int64, LongColumnDefinition); + DbTypeMap.Set(DbType.UInt64, LongColumnDefinition); + DbTypeMap.Set(DbType.UInt64, LongColumnDefinition); + + DbTypeMap.Set(DbType.Single, RealColumnDefinition); + DbTypeMap.Set(DbType.Single, RealColumnDefinition); + DbTypeMap.Set(DbType.Double, RealColumnDefinition); + DbTypeMap.Set(DbType.Double, RealColumnDefinition); + + DbTypeMap.Set(DbType.Decimal, DecimalColumnDefinition); + DbTypeMap.Set(DbType.Decimal, DecimalColumnDefinition); + + DbTypeMap.Set(DbType.Binary, BlobColumnDefinition); + } + + public virtual string EscapeString(string val) + { + return PetaPocoExtensions.EscapeAtSymbols(val.Replace("'", "''")); + } + + public virtual string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) = upper(@{1})", column, paramIndex); + } + + public virtual string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) LIKE upper(@{1})", column, paramIndex); + } + + [Obsolete("Use the overload with the parameter index instead")] + public virtual string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) = '{1}'", column, value.ToUpper()); + } + + [Obsolete("Use the overload with the parameter index instead")] + public virtual string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) LIKE '{1}%'", column, value.ToUpper()); + } + + [Obsolete("Use the overload with the parameter index instead")] + public virtual string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) LIKE '%{1}'", column, value.ToUpper()); + } + + [Obsolete("Use the overload with the parameter index instead")] + public virtual string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) LIKE '%{1}%'", column, value.ToUpper()); + } + + [Obsolete("Use the overload with the parameter index instead")] + public virtual string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) LIKE '{1}'", column, value.ToUpper()); + } + + public virtual string GetQuotedTableName(string tableName) + { + return string.Format("\"{0}\"", tableName); + } + + public virtual string GetQuotedColumnName(string columnName) + { + return string.Format("\"{0}\"", columnName); + } + + public virtual string GetQuotedName(string name) + { + return string.Format("\"{0}\"", name); + } + + public virtual string GetQuotedValue(string value) + { + return string.Format("'{0}'", value); + } + + public virtual string GetIndexType(IndexTypes indexTypes) + { + string indexType; + + if (indexTypes == IndexTypes.Clustered) + { + indexType = "CLUSTERED"; + } + else + { + indexType = indexTypes == IndexTypes.NonClustered + ? "NONCLUSTERED" + : "UNIQUE NONCLUSTERED"; + } + return indexType; + } + + public virtual string GetSpecialDbType(SpecialDbTypes dbTypes) + { + if (dbTypes == SpecialDbTypes.NCHAR) + { + return "NCHAR"; + } + else if (dbTypes == SpecialDbTypes.NTEXT) + return "NTEXT"; + + return "NVARCHAR"; + } + + public virtual bool? SupportsCaseInsensitiveQueries(Database db) + { + return true; + } + + public virtual IEnumerable GetTablesInSchema(Database db) + { + return new List(); + } + + public virtual IEnumerable GetColumnsInSchema(Database db) + { + return new List(); + } + + public virtual IEnumerable> GetConstraintsPerTable(Database db) + { + return new List>(); + } + + public virtual IEnumerable> GetConstraintsPerColumn(Database db) + { + return new List>(); + } + + public abstract IEnumerable> GetDefinedIndexes(Database db); + + public virtual bool DoesTableExist(Database db, string tableName) + { + return false; + } + + public virtual bool SupportsClustered() + { + return true; + } + + public virtual bool SupportsIdentityInsert() + { + return true; + } + + /// + /// This is used ONLY if we need to format datetime without using SQL parameters (i.e. during migrations) + /// + /// + /// + /// + /// + /// MSSQL has a DateTime standard that is unambiguous and works on all servers: + /// YYYYMMDD HH:mm:ss + /// + public virtual string FormatDateTime(DateTime date, bool includeTime = true) + { + // need CultureInfo.InvariantCulture because ":" here is the "time separator" and + // may be converted to something else in different cultures (eg "." in DK). + return date.ToString(includeTime ? "yyyyMMdd HH:mm:ss" : "yyyyMMdd", CultureInfo.InvariantCulture); + } + + public virtual string Format(TableDefinition table) + { + var statement = string.Format(CreateTable, GetQuotedTableName(table.Name), Format(table.Columns)); + + return statement; + } + + public virtual List Format(IEnumerable indexes) + { + return indexes.Select(Format).ToList(); + } + + public virtual string Format(IndexDefinition index) + { + string name = string.IsNullOrEmpty(index.Name) + ? string.Format("IX_{0}_{1}", index.TableName, index.ColumnName) + : index.Name; + + string columns = index.Columns.Any() + ? string.Join(",", index.Columns.Select(x => GetQuotedColumnName(x.Name))) + : GetQuotedColumnName(index.ColumnName); + + return string.Format(CreateIndex, GetIndexType(index.IndexType), " ", GetQuotedName(name), + GetQuotedTableName(index.TableName), columns); + } + + public virtual List Format(IEnumerable foreignKeys) + { + return foreignKeys.Select(Format).ToList(); + } + + public virtual string Format(ForeignKeyDefinition foreignKey) + { + string constraintName = string.IsNullOrEmpty(foreignKey.Name) + ? string.Format("FK_{0}_{1}_{2}", foreignKey.ForeignTable, foreignKey.PrimaryTable, foreignKey.PrimaryColumns.First()) + : foreignKey.Name; + + return string.Format(CreateForeignKeyConstraint, + GetQuotedTableName(foreignKey.ForeignTable), + GetQuotedName(constraintName), + GetQuotedColumnName(foreignKey.ForeignColumns.First()), + GetQuotedTableName(foreignKey.PrimaryTable), + GetQuotedColumnName(foreignKey.PrimaryColumns.First()), + FormatCascade("DELETE", foreignKey.OnDelete), + FormatCascade("UPDATE", foreignKey.OnUpdate)); + } + + public virtual string Format(IEnumerable columns) + { + var sb = new StringBuilder(); + foreach (var column in columns) + { + sb.Append(Format(column) + ",\n"); + } + return sb.ToString().TrimEnd(",\n"); + } + + public virtual string Format(ColumnDefinition column) + { + var clauses = new List(); + + foreach (var action in ClauseOrder) + { + string clause = action(column); + if (!string.IsNullOrEmpty(clause)) + clauses.Add(clause); + } + + return string.Join(" ", clauses.ToArray()); + } + + public virtual string FormatPrimaryKey(TableDefinition table) + { + var columnDefinition = table.Columns.FirstOrDefault(x => x.IsPrimaryKey); + if (columnDefinition == null) + return string.Empty; + + string constraintName = string.IsNullOrEmpty(columnDefinition.PrimaryKeyName) + ? string.Format("PK_{0}", table.Name) + : columnDefinition.PrimaryKeyName; + + string columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns) + ? GetQuotedColumnName(columnDefinition.Name) + : string.Join(", ", columnDefinition.PrimaryKeyColumns + .Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + .Select(GetQuotedColumnName)); + + string primaryKeyPart = string.Concat("PRIMARY KEY", columnDefinition.IsIndexed ? " CLUSTERED" : " NONCLUSTERED"); + + return string.Format(CreateConstraint, + GetQuotedTableName(table.Name), + GetQuotedName(constraintName), + primaryKeyPart, + columns); + } + + public virtual string FormatColumnRename(string tableName, string oldName, string newName) + { + return string.Format(RenameColumn, + GetQuotedTableName(tableName), + GetQuotedColumnName(oldName), + GetQuotedColumnName(newName)); + } + + public virtual string FormatTableRename(string oldName, string newName) + { + return string.Format(RenameTable, GetQuotedTableName(oldName), GetQuotedTableName(newName)); + } + + protected virtual string FormatCascade(string onWhat, Rule rule) + { + string action = "NO ACTION"; + switch (rule) + { + case Rule.None: + return ""; + case Rule.Cascade: + action = "CASCADE"; + break; + case Rule.SetNull: + action = "SET NULL"; + break; + case Rule.SetDefault: + action = "SET DEFAULT"; + break; + } + + return string.Format(" ON {0} {1}", onWhat, action); + } + + protected virtual string FormatString(ColumnDefinition column) + { + return GetQuotedColumnName(column.Name); + } + + protected virtual string FormatType(ColumnDefinition column) + { + if (column.Type.HasValue == false && string.IsNullOrEmpty(column.CustomType) == false) + return column.CustomType; + + if (column.HasSpecialDbType) + { + if (column.Size != default(int)) + { + return string.Format("{0}({1})", + GetSpecialDbType(column.DbType), + column.Size); + } + + return GetSpecialDbType(column.DbType); + } + + Type type = column.Type.HasValue + ? DbTypeMap.ColumnDbTypeMap.First(x => x.Value == column.Type.Value).Key + : column.PropertyType; + + if (type == typeof(string)) + { + var valueOrDefault = column.Size != default(int) ? column.Size : DefaultStringLength; + return string.Format(StringLengthColumnDefinitionFormat, valueOrDefault); + } + + if (type == typeof(decimal)) + { + var precision = column.Size != default(int) ? column.Size : DefaultDecimalPrecision; + var scale = column.Precision != default(int) ? column.Precision : DefaultDecimalScale; + return string.Format(DecimalColumnDefinitionFormat, precision, scale); + } + + string definition = DbTypeMap.ColumnTypeMap.First(x => x.Key == type).Value; + string dbTypeDefinition = column.Size != default(int) + ? string.Format("{0}({1})", definition, column.Size) + : definition; + //NOTE Percision is left out + return dbTypeDefinition; + } + + protected virtual string FormatNullable(ColumnDefinition column) + { + return column.IsNullable ? "NULL" : "NOT NULL"; + } + + protected virtual string FormatConstraint(ColumnDefinition column) + { + if (string.IsNullOrEmpty(column.ConstraintName) && column.DefaultValue == null) + return string.Empty; + + return string.Format("CONSTRAINT {0}", + string.IsNullOrEmpty(column.ConstraintName) + ? GetQuotedName(string.Format("DF_{0}_{1}", column.TableName, column.Name)) + : column.ConstraintName); + } + + protected virtual string FormatDefaultValue(ColumnDefinition column) + { + if (column.DefaultValue == null) + return string.Empty; + + //hack - probably not needed with latest changes + if (column.DefaultValue.ToString().ToLower().Equals("getdate()".ToLower())) + column.DefaultValue = SystemMethods.CurrentDateTime; + + // see if this is for a system method + if (column.DefaultValue is SystemMethods) + { + string method = FormatSystemMethods((SystemMethods)column.DefaultValue); + if (string.IsNullOrEmpty(method)) + return string.Empty; + + return string.Format(DefaultValueFormat, method); + } + + return string.Format(DefaultValueFormat, GetQuotedValue(column.DefaultValue.ToString())); + } + + protected virtual string FormatPrimaryKey(ColumnDefinition column) + { + return string.Empty; + } + + protected abstract string FormatSystemMethods(SystemMethods systemMethod); + + protected abstract string FormatIdentity(ColumnDefinition column); + + public virtual string DeleteDefaultConstraint + { + get + { + throw new NotSupportedException("Default constraints are not supported"); + } + } + + public virtual string CreateTable { get { return "CREATE TABLE {0} ({1})"; } } + public virtual string DropTable { get { return "DROP TABLE {0}"; } } + + public virtual string AddColumn { get { return "ALTER TABLE {0} ADD COLUMN {1}"; } } + public virtual string DropColumn { get { return "ALTER TABLE {0} DROP COLUMN {1}"; } } + public virtual string AlterColumn { get { return "ALTER TABLE {0} ALTER COLUMN {1}"; } } + public virtual string RenameColumn { get { return "ALTER TABLE {0} RENAME COLUMN {1} TO {2}"; } } + + public virtual string RenameTable { get { return "RENAME TABLE {0} TO {1}"; } } + + public virtual string CreateSchema { get { return "CREATE SCHEMA {0}"; } } + public virtual string AlterSchema { get { return "ALTER SCHEMA {0} TRANSFER {1}.{2}"; } } + public virtual string DropSchema { get { return "DROP SCHEMA {0}"; } } + + public virtual string CreateIndex { get { return "CREATE {0}{1}INDEX {2} ON {3} ({4})"; } } + public virtual string DropIndex { get { return "DROP INDEX {0}"; } } + + public virtual string InsertData { get { return "INSERT INTO {0} ({1}) VALUES ({2})"; } } + public virtual string UpdateData { get { return "UPDATE {0} SET {1} WHERE {2}"; } } + public virtual string DeleteData { get { return "DELETE FROM {0} WHERE {1}"; } } + public virtual string TruncateTable { get { return "TRUNCATE TABLE {0}"; } } + + public virtual string CreateConstraint { get { return "ALTER TABLE {0} ADD CONSTRAINT {1} {2} ({3})"; } } + public virtual string DeleteConstraint { get { return "ALTER TABLE {0} DROP CONSTRAINT {1}"; } } + public virtual string CreateForeignKeyConstraint { get { return "ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4}){5}{6}"; } } + + public virtual string IsNull { get { return "ISNULL({0},{1})"; } } + public virtual string ConvertIntegerToOrderableString { get { return "RIGHT('00000000' + CAST({0} AS varchar(8)),8)"; } } + public virtual string ConvertDateToOrderableString { get { return "CONVERT(varchar, {0}, 102)"; } } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index e7298a5c6e..0e5b55866f 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -23,2319 +23,2321 @@ using Umbraco.Core.Publishing; namespace Umbraco.Core.Services { - /// - /// Represents the Content Service, which is an easy access to operations involving - /// - public class ContentService : RepositoryService, IContentService, IContentServiceOperations - { - private readonly IPublishingStrategy _publishingStrategy; - private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); - private readonly IDataTypeService _dataTypeService; - private readonly IUserService _userService; - - //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 ContentService( - IDatabaseUnitOfWorkProvider provider, - RepositoryFactory repositoryFactory, - ILogger logger, - IEventMessagesFactory eventMessagesFactory, - IPublishingStrategy publishingStrategy, - IDataTypeService dataTypeService, - IUserService userService) - : base(provider, repositoryFactory, logger, eventMessagesFactory) - { - if (publishingStrategy == null) throw new ArgumentNullException("publishingStrategy"); - if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); - if (userService == null) throw new ArgumentNullException("userService"); - _publishingStrategy = publishingStrategy; - _dataTypeService = dataTypeService; - _userService = userService; - } - - public int CountPublished(string contentTypeAlias = null) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - return repository.CountPublished(); - } - } - - public int Count(string contentTypeAlias = null) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - return repository.Count(contentTypeAlias); - } - } - - public int CountChildren(int parentId, string contentTypeAlias = null) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - return repository.CountChildren(parentId, contentTypeAlias); - } - } - - public int CountDescendants(int parentId, string contentTypeAlias = null) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - return repository.CountDescendants(parentId, contentTypeAlias); - } - } - - /// - /// 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. - /// - /// - public void ReplaceContentPermissions(EntityPermissionSet permissionSet) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - repository.ReplaceContentPermissions(permissionSet); - } - } - - /// - /// Assigns a single permission to the current content item for the specified user ids - /// - /// - /// - /// - public void AssignContentPermission(IContent entity, char permission, IEnumerable userIds) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - repository.AssignEntityPermission(entity, permission, userIds); - } - } - - /// - /// Gets the list of permissions for the content item - /// - /// - /// - public IEnumerable GetPermissionsForEntity(IContent content) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - return repository.GetPermissionsForEntity(content.Id); - } - } - - /// - /// Creates an object using the alias of the - /// that this Content should based on. - /// - /// - /// 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. - /// - /// Name of the Content object - /// Id of Parent for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// - public IContent CreateContent(string name, int parentId, string contentTypeAlias, int userId = 0) - { - var contentType = FindContentTypeByAlias(contentTypeAlias); - 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); - - var uow = UowProvider.GetUnitOfWork(); - using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) - { - auditRepo.AddOrUpdate(new AuditItem(content.Id, string.Format("Content '{0}' was created", name), AuditType.New, content.CreatorId)); - uow.Commit(); - } - - return content; - } - - /// - /// Creates an object using the alias of the - /// that this Content should based on. - /// - /// - /// 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. - /// - /// Name of the Content object - /// Parent object for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// - public IContent CreateContent(string name, IContent parent, string contentTypeAlias, int userId = 0) - { - if (parent == null) throw new ArgumentNullException("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)) - { - content.WasCancelled = true; - 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. - /// - /// - /// 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 - /// - 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; - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - content.CreatorId = userId; - content.WriterId = userId; - repository.AddOrUpdate(content); - //Generate a new preview - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - uow.Commit(); - } - - Saved.RaiseEvent(new SaveEventArgs(content, false), this); - - Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parentId), this); - - Audit(AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id); - - return content; - } - - /// - /// Creates and saves an object using the alias of the - /// that this Content should based on. - /// - /// - /// 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 - /// - public IContent CreateContentWithIdentity(string name, IContent parent, string contentTypeAlias, int userId = 0) - { - if (parent == null) throw new ArgumentNullException("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)) - { - content.WasCancelled = true; - return content; - } - - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(content), this)) - { - content.WasCancelled = true; - return content; - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - content.CreatorId = userId; - content.WriterId = userId; - repository.AddOrUpdate(content); - //Generate a new preview - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - uow.Commit(); - } - - Saved.RaiseEvent(new SaveEventArgs(content, false), this); - - Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parent), this); - - Audit(AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id); - - return content; - } - - /// - /// Gets an object by Id - /// - /// Id of the Content to retrieve - /// - public IContent GetById(int id) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - return repository.Get(id); - } - } - - /// - /// Gets an object by Id - /// - /// Ids of the Content to retrieve - /// - public IEnumerable GetByIds(IEnumerable ids) - { - if (ids.Any() == false) return Enumerable.Empty(); - - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - return repository.GetAll(ids.ToArray()); - } - } - - /// - /// Gets an object by its 'UniqueId' - /// - /// Guid key of the Content to retrieve - /// - public IContent GetById(Guid key) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Key == key); - var contents = repository.GetByQuery(query); - return contents.SingleOrDefault(); - } - } - - /// - /// Gets a collection of objects by the Id of the - /// - /// Id of the - /// An Enumerable list of objects - public IEnumerable GetContentOfContentType(int id) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.ContentTypeId == id); - var contents = repository.GetByQuery(query); - - return contents; - } - } - - internal IEnumerable GetPublishedContentOfContentType(int id) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.ContentTypeId == id); - var contents = repository.GetByPublishedVersion(query); - - return contents; - } - } - - /// - /// Gets a collection of objects by Level - /// - /// The level to retrieve Content from - /// An Enumerable list of objects - public IEnumerable GetByLevel(int level) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Level == level && !x.Path.StartsWith(Constants.System.RecycleBinContent.ToInvariantString())); - var contents = repository.GetByQuery(query); - - return contents; - } - } - - /// - /// Gets a specific version of an item. - /// - /// Id of the version to retrieve - /// An item - public IContent GetByVersion(Guid versionId) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - return repository.GetByVersion(versionId); - } - } - - - /// - /// Gets a collection of an objects versions by Id - /// - /// - /// An Enumerable list of objects - public IEnumerable GetVersions(int id) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var versions = repository.GetAllVersions(id); - return versions; - } - } - - /// - /// Gets a collection of objects, which are ancestors of the current content. - /// - /// Id of the to retrieve ancestors for - /// An Enumerable list of objects - public IEnumerable GetAncestors(int id) - { - var content = GetById(id); - return GetAncestors(content); - } - - /// - /// Gets a collection of objects, which are ancestors of the current content. - /// - /// to retrieve ancestors for - /// An Enumerable list of objects - public IEnumerable GetAncestors(IContent content) - { - //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(); - if (ids.Any() == false) - return new List(); - - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - return repository.GetAll(ids); - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// An Enumerable list of objects - public IEnumerable GetChildren(int id) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.ParentId == id); - var contents = repository.GetByQuery(query).OrderBy(x => x.SortOrder); - - return contents; - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, - string orderBy, Direction orderDirection, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - - var query = Query.Builder; - //if the id is System Root, then just get all - if (id != Constants.System.Root) - { - query.Where(x => x.ParentId == id); - } - long total; - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); - totalChildren = Convert.ToInt32(total); - return contents; - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page index (zero based) - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects - public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - string orderBy, Direction orderDirection, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - - var query = Query.Builder; - //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, filter); - - return contents; - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - - var query = Query.Builder; - //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)); - } - long total; - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); - totalChildren = Convert.ToInt32(total); - return contents; - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - - var query = Query.Builder; - //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)); - } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, filter); - - return contents; - } - } - - /// - /// Gets a collection of objects by its name or partial name - /// - /// Id of the Parent to retrieve Children from - /// Full or partial name of the children - /// An Enumerable list of objects - public IEnumerable GetChildrenByName(int parentId, string name) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.ParentId == parentId && x.Name.Contains(name)); - var contents = repository.GetByQuery(query); - - return contents; - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// An Enumerable list of objects - public IEnumerable GetDescendants(int id) - { - var content = GetById(id); - if (content == null) - { - return Enumerable.Empty(); - } - return GetDescendants(content); - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// item to retrieve Descendants from - /// An Enumerable list of objects - public IEnumerable GetDescendants(IContent content) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var pathMatch = content.Path + ","; - var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != content.Id); - var contents = repository.GetByQuery(query); - - return contents; - } - } - - /// - /// Gets the parent of the current content as an item. - /// - /// Id of the to retrieve the parent from - /// Parent object - public IContent GetParent(int id) - { - var content = GetById(id); - return GetParent(content); - } - - /// - /// Gets the parent of the current content as an item. - /// - /// to retrieve the parent from - /// Parent object - public IContent GetParent(IContent content) - { - if (content.ParentId == Constants.System.Root || content.ParentId == Constants.System.RecycleBinContent) - return null; - - return GetById(content.ParentId); - } - - /// - /// Gets the published version of an item - /// - /// Id of the to retrieve version from - /// An item - public IContent GetPublishedVersion(int id) - { - var version = GetVersions(id); - return version.FirstOrDefault(x => x.Published == true); - } - - /// - /// Gets the published version of a item. - /// - /// The content item. - /// The published version, if any; otherwise, null. - public IContent GetPublishedVersion(IContent content) - { - if (content.Published) return content; - return content.HasPublishedVersion - ? GetByVersion(content.PublishedVersionGuid) - : null; - } - - /// - /// Gets a collection of objects, which reside at the first level / root - /// - /// An Enumerable list of objects - public IEnumerable GetRootContent() - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.ParentId == Constants.System.Root); - var contents = repository.GetByQuery(query); - - return contents; - } - } - - /// - /// Gets all published content items - /// - /// - internal IEnumerable GetAllPublished() - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Trashed == false); - return repository.GetByPublishedVersion(query); - } - } - - /// - /// Gets a collection of objects, which has an expiration date less than or equal to today. - /// - /// An Enumerable list of objects - public IEnumerable GetContentForExpiration() - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Published == true && x.ExpireDate <= DateTime.Now); - var contents = repository.GetByQuery(query); - - return contents; - } - } - - /// - /// Gets a collection of objects, which has a release date less than or equal to today. - /// - /// An Enumerable list of objects - public IEnumerable GetContentForRelease() - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Published == false && x.ReleaseDate <= DateTime.Now); - var contents = repository.GetByQuery(query); - - return contents; - } - } - - /// - /// Gets a collection of an objects, which resides in the Recycle Bin - /// - /// An Enumerable list of objects - public IEnumerable GetContentInRecycleBin() - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Path.Contains(Constants.System.RecycleBinContent.ToInvariantString())); - var contents = repository.GetByQuery(query); - - return contents; - } - } - - - - /// - /// Checks whether an item has any children - /// - /// Id of the - /// True if the content has any children otherwise False - public bool HasChildren(int id) - { - return CountChildren(id) > 0; - } - - internal int CountChildren(int id) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.ParentId == id); - var count = repository.Count(query); - return count; - } - } - - /// - /// Checks whether an item has any published versions - /// - /// Id of the - /// True if the content has any published version otherwise False - public bool HasPublishedVersion(int id) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Published == true && x.Id == id && x.Trashed == false); - int count = repository.Count(query); - return count > 0; - } - } - - /// - /// Checks if the passed in can be published based on the anscestors publish state. - /// - /// to check if anscestors are published - /// 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); - } - - return IsPublishable(content, false); - } - - /// - /// This will rebuild the xml structures for content in the database. - /// - /// This is not used for anything - /// True if publishing succeeded, otherwise False - /// - /// This is used for when a document type alias or a document type property is changed, the xml will need to - /// be regenerated. - /// - public bool RePublishAll(int userId = 0) - { - try - { - RebuildXmlStructures(); - return true; - } - catch (Exception ex) - { - Logger.Error("An error occurred executing RePublishAll", ex); - return false; - } - } - - /// - /// This will rebuild the xml structures for content in the database. - /// - /// - /// If specified will only rebuild the xml for the content type's specified, otherwise will update the structure - /// for all published content. - /// - internal void RePublishAll(params int[] contentTypeIds) - { - try - { - RebuildXmlStructures(contentTypeIds); - } - catch (Exception ex) - { - Logger.Error("An error occurred executing RePublishAll", ex); - } - } - - /// - /// Publishes 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) - { - 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; - } - - /// - /// 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); - } - - /// - /// 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) - { - var evtMsgs = EventMessagesFactory.Get(); - - using (new WriteLock(Locker)) - { - 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); - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - content.WriterId = userId; - content.ChangeTrashedState(true); - 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.Commit(); - } - - 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); - } - } - - /// - /// 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); - } - - /// - /// 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; - } - - /// - /// 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 - [Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")] - public bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true) - { - var result = SaveAndPublishDo(content, userId, raiseEvents); - return result.Success; - } - - /// - /// 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 - public Attempt SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true) - { - return ((IContentServiceOperations)this).SaveAndPublish(content, userId, raiseEvents); - } - - /// - /// Saves 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) - { - ((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); - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - 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, 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, c)); - } - } - - uow.Commit(); - } - - 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); - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - repository.Delete(content); - uow.Commit(); - - 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); - } - } - - /// - /// Publishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// The published status attempt - Attempt IContentServiceOperations.Publish(IContent content, int userId) - { - return SaveAndPublishDo(content, userId); - } - - /// - /// Saves 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) - { - return Save(content, true, userId, raiseEvents); - } - - /// - /// 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); - } - - /// - /// 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. - - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - //NOTE What about content that has the contenttype as part of its composition? - var query = Query.Builder.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 = Query.Builder.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); - } - } - - /// - /// Permanently deletes an object as well as all of its Children. - /// - /// - /// 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 - public void Delete(IContent content, int userId = 0) - { - ((IContentServiceOperations)this).Delete(content, userId); - } - - /// - /// Permanently deletes versions from an object prior to a specific date. - /// This method will never delete the latest version of a content item. - /// - /// Id of the object to delete versions from - /// Latest version date - /// Optional Id of the User deleting versions of a Content object - public void DeleteVersions(int id, DateTime versionDate, int userId = 0) - { - if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, dateToRetain: versionDate), this)) - return; - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - repository.DeleteVersions(id, versionDate); - uow.Commit(); - } - - DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate), this); - - Audit(AuditType.Delete, "Delete Content by version date performed by user", userId, Constants.System.Root); - } - - /// - /// Permanently deletes specific version(s) from an object. - /// This method will never delete the latest version of a content item. - /// - /// Id of the object to delete a version from - /// Id of the version to delete - /// Boolean indicating whether to delete versions prior to the versionId - /// 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) - { - var content = GetByVersion(versionId); - DeleteVersions(id, content.UpdateDate, userId); - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - repository.DeleteVersion(versionId); - uow.Commit(); - } - - DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), this); - - Audit(AuditType.Delete, "Delete Content by version performed by user", userId, Constants.System.Root); - } - } - - /// - /// 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 - public void MoveToRecycleBin(IContent content, int userId = 0) - { - ((IContentServiceOperations) this).MoveToRecycleBin(content, userId); - } - - /// - /// Moves an object to a new location by changing its parent id. - /// - /// - /// If the object is already published it will be - /// published after being moved to its new location. Otherwise it'll just - /// be saved with a new parent id. - /// - /// The to move - /// Id of the Content's new Parent - /// Optional Id of the User moving the Content - public void Move(IContent content, int parentId, int userId = 0) - { - using (new WriteLock(Locker)) - { - //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); - } - } - - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - public void EmptyRecycleBin() - { - using (new WriteLock(Locker)) - { - Dictionary> entities; - List files; - bool success; - var nodeObjectType = new Guid(Constants.ObjectTypes.Document); - - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - //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); - } - } - Audit(AuditType.Delete, "Empty Content Recycle Bin performed by user", 0, Constants.System.RecycleBinContent); - } - - /// - /// 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. - /// - /// The to copy - /// Id of the Content's new Parent - /// Boolean indicating whether the copy should be related to the original - /// Optional Id of the User copying the Content - /// The newly created object - public IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0) - { - return Copy(content, parentId, relateToOriginal, true, userId); - } - - /// - /// 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. - /// - /// The to copy - /// Id of the Content's new Parent - /// Boolean indicating whether the copy should be related to the original - /// A value indicating whether to recursively copy children. - /// Optional Id of the User copying the Content - /// 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 - - using (new WriteLock(Locker)) - { - var copy = content.DeepCloneWithResetIdentities(); - copy.ParentId = parentId; - - // A copy should never be set to published automatically even if the original was. - copy.ChangePublishedState(PublishedState.Unpublished); - - if (Copying.IsRaisedEventCancelled(new CopyEventArgs(content, copy, parentId), this)) - return null; - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - // Update the create author and last edit author - copy.CreatorId = userId; - copy.WriterId = userId; - - repository.AddOrUpdate(copy); - //add or update a preview - repository.AddOrUpdatePreviewXml(copy, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - uow.Commit(); - - - //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 }); - } - } - } - - if (recursive) - { - //Look for children and copy those as well - var children = GetChildren(content.Id); - foreach (var child in children) - { - //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); - } - } - - 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. - /// - /// The to send to publication - /// Optional Id of the User issueing the send to publication - /// True if sending publication was succesfull otherwise false - public bool SendToPublication(IContent content, int userId = 0) - { - if (SendingToPublish.IsRaisedEventCancelled(new SendToPublishEventArgs(content), this)) - return false; - - //Save before raising event - Save(content, userId); - - SentToPublish.RaiseEvent(new SendToPublishEventArgs(content, false), this); - - Audit(AuditType.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id); - - return true; - } - - /// - /// Rollback an object to a previous version. - /// This will create a new version, which is a copy of all the old data. - /// - /// - /// The way data is stored actually only allows us to rollback on properties - /// and not data like Name and Alias of the Content. - /// - /// Id of the being rolled back - /// Id of the version to rollback to - /// Optional Id of the User issueing the rollback of the Content - /// The newly created object - public IContent Rollback(int id, Guid versionId, int userId = 0) - { - var content = GetByVersion(versionId); - - if (RollingBack.IsRaisedEventCancelled(new RollbackEventArgs(content), this)) - return content; - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - content.WriterId = userId; - content.CreatorId = userId; - content.ChangePublishedState(PublishedState.Unpublished); - - repository.AddOrUpdate(content); - //add or update a preview - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - uow.Commit(); - } - - RolledBack.RaiseEvent(new RollbackEventArgs(content, false), this); - - Audit(AuditType.RollBack, "Content rollback performed by user", content.WriterId, content.Id); - - return content; - } - - /// - /// Sorts a collection of objects by updating the SortOrder according - /// to the ordering of items in the passed in . - /// - /// - /// Using this method will ensure that the Published-state is maintained upon sorting - /// so the cache is updated accordingly - as needed. - /// - /// - /// - /// - /// 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)) - return false; - } - - var shouldBePublished = new List(); - var shouldBeSaved = new List(); - - var asArray = items.ToArray(); - using (new WriteLock(Locker)) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - 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 == 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, c)); - } - - foreach (var content in shouldBePublished) - { - //Create and Save ContentXml DTO - repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - } - - uow.Commit(); - } - } - - 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); - } - - - Audit(AuditType.Sort, "Sorting content performed by user", userId, 0); - - return true; - } - - /// - /// 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) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - repository.RebuildXmlStructures( - content => _entitySerializer.Serialize(this, _dataTypeService, _userService, content), - contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds); - - uow.Commit(); - } - - Audit(AuditType.Publish, "ContentService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, Constants.System.Root); - - } - - #region Internal Methods - - /// - /// Gets a collection of descendants by the first Parent. - /// - /// item to retrieve Descendants from - /// An Enumerable list of objects - internal IEnumerable GetPublishedDescendants(IContent content) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Id != content.Id && x.Path.StartsWith(content.Path) && x.Trashed == false); - var contents = repository.GetByPublishedVersion(query); - - return contents; - } - } - - #endregion - - #region Private Methods - - private void Audit(AuditType type, string message, int userId, int objectId) - { - var uow = UowProvider.GetUnitOfWork(); - using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) - { - auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); - uow.Commit(); - } - } - - //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.GetUnitOfWork()) - { - var xml = _entitySerializer.Serialize(this, _dataTypeService, _userService, 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 - /// - /// The to publish along with its children - /// Optional Id of the User issueing the publishing - /// If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published - /// - /// A list of publish statues. If the parent document is not valid or cannot be published because it's parent(s) is not published - /// 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) - { - if (content == null) throw new ArgumentNullException("content"); - - var evtMsgs = EventMessagesFactory.Get(); - - using (new WriteLock(Locker)) - { - var result = new List>(); - - //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 or one of its ancestors is not published.", - content.Name, content.Id)); - result.Add(Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedPathNotPublished, evtMsgs))); - return result; - } - - //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 = Query.Builder.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); - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - //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, c)); - //add or update the published xml - repository.AddOrUpdateContentXml(item.Result.ContentItem, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - updated.Add(item.Result.ContentItem); - } - - uow.Commit(); - - } - //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; - } - } - - /// - /// UnPublishes a single object - /// - /// The to publish - /// Optional boolean to avoid having the cache refreshed when calling this Unpublish method. By default this method will update the cache. - /// Optional Id of the User issueing the publishing - /// 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; - - 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)); - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - 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); - repository.DeleteContentXml(content); - - uow.Commit(); - } - //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)); - } - - /// - /// 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 - private Attempt SaveAndPublishDo(IContent content, int userId = 0, bool raiseEvents = true) - { - var evtMsgs = EventMessagesFactory.Get(); - - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled( - new SaveEventArgs(content, evtMsgs), this)) - { - return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent, evtMsgs)); - } - } - - 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 - - //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; - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - 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, c)); - - if (published) - { - //Content Xml - repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - } - - uow.Commit(); - } - - 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)) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - 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, c)); - - uow.Commit(); - } - - 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 repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Alias == contentTypeAlias); - var types = repository.GetByQuery(query); - - if (types.Any() == false) - throw new Exception( - string.Format("No ContentType matching the passed in Alias: '{0}' was found", - contentTypeAlias)); - - var contentType = types.First(); - - if (contentType == null) - throw new Exception(string.Format("ContentType matching the passed in Alias: '{0}' was null", - contentTypeAlias)); - - return contentType; - } - } - - #endregion - - #region Proxy Event Handlers - /// - /// Occurs before publish. - /// - /// Proxy to the real event on the - public static event TypedEventHandler> Publishing - { - add { PublishingStrategy.Publishing += value; } - remove { PublishingStrategy.Publishing -= value; } - } - - /// - /// 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 - /// - public static event TypedEventHandler> Deleting; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> Deleted; - - /// - /// Occurs before Delete Versions - /// - public static event TypedEventHandler DeletingVersions; - - /// - /// Occurs after Delete Versions - /// - public static event TypedEventHandler DeletedVersions; - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> Saving; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> Saved; - - /// - /// Occurs before Create - /// - [Obsolete("Use the Created event instead, the Creating and Created events both offer the same functionality, Creating event has been deprecated.")] - public static event TypedEventHandler> Creating; - - /// - /// Occurs after Create - /// - /// - /// Please note that the Content object has been created, but might not have been saved - /// so it does not have an identity yet (meaning no Id has been set). - /// - public static event TypedEventHandler> Created; - - /// - /// Occurs before Copy - /// - public static event TypedEventHandler> Copying; - - /// - /// Occurs after Copy - /// - public static event TypedEventHandler> Copied; - - /// - /// Occurs before Content is moved to Recycle Bin - /// - public static event TypedEventHandler> Trashing; - - /// - /// Occurs after Content is moved to Recycle Bin - /// - public static event TypedEventHandler> Trashed; - - /// - /// Occurs before Move - /// - public static event TypedEventHandler> Moving; - - /// - /// Occurs after Move - /// - public static event TypedEventHandler> Moved; - - /// - /// Occurs before Rollback - /// - public static event TypedEventHandler> RollingBack; - - /// - /// Occurs after Rollback - /// - public static event TypedEventHandler> RolledBack; - - /// - /// Occurs before Send to Publish - /// - public static event TypedEventHandler> SendingToPublish; - - /// - /// Occurs after Send to Publish - /// - public static event TypedEventHandler> SentToPublish; - - /// - /// Occurs before the Recycle Bin is emptied - /// - public static event TypedEventHandler EmptyingRecycleBin; - - /// - /// Occurs after the Recycle Bin has been Emptied - /// - public static event TypedEventHandler EmptiedRecycleBin; - #endregion - } + /// + /// Represents the Content Service, which is an easy access to operations involving + /// + public class ContentService : RepositoryService, IContentService, IContentServiceOperations + { + private readonly IPublishingStrategy _publishingStrategy; + private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); + private readonly IDataTypeService _dataTypeService; + private readonly IUserService _userService; + + //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 ContentService( + IDatabaseUnitOfWorkProvider provider, + RepositoryFactory repositoryFactory, + ILogger logger, + IEventMessagesFactory eventMessagesFactory, + IPublishingStrategy publishingStrategy, + IDataTypeService dataTypeService, + IUserService userService) + : base(provider, repositoryFactory, logger, eventMessagesFactory) + { + if (publishingStrategy == null) throw new ArgumentNullException("publishingStrategy"); + if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); + if (userService == null) throw new ArgumentNullException("userService"); + _publishingStrategy = publishingStrategy; + _dataTypeService = dataTypeService; + _userService = userService; + } + + public int CountPublished(string contentTypeAlias = null) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + return repository.CountPublished(); + } + } + + public int Count(string contentTypeAlias = null) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + return repository.Count(contentTypeAlias); + } + } + + public int CountChildren(int parentId, string contentTypeAlias = null) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + return repository.CountChildren(parentId, contentTypeAlias); + } + } + + public int CountDescendants(int parentId, string contentTypeAlias = null) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + return repository.CountDescendants(parentId, contentTypeAlias); + } + } + + /// + /// 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. + /// + /// + public void ReplaceContentPermissions(EntityPermissionSet permissionSet) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + repository.ReplaceContentPermissions(permissionSet); + } + } + + /// + /// Assigns a single permission to the current content item for the specified user ids + /// + /// + /// + /// + public void AssignContentPermission(IContent entity, char permission, IEnumerable userIds) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + repository.AssignEntityPermission(entity, permission, userIds); + } + } + + /// + /// Gets the list of permissions for the content item + /// + /// + /// + public IEnumerable GetPermissionsForEntity(IContent content) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + return repository.GetPermissionsForEntity(content.Id); + } + } + + /// + /// Creates an object using the alias of the + /// that this Content should based on. + /// + /// + /// 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. + /// + /// Name of the Content object + /// Id of Parent for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + public IContent CreateContent(string name, int parentId, string contentTypeAlias, int userId = 0) + { + var contentType = FindContentTypeByAlias(contentTypeAlias); + 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); + + var uow = UowProvider.GetUnitOfWork(); + using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) + { + auditRepo.AddOrUpdate(new AuditItem(content.Id, string.Format("Content '{0}' was created", name), AuditType.New, content.CreatorId)); + uow.Commit(); + } + + return content; + } + + /// + /// Creates an object using the alias of the + /// that this Content should based on. + /// + /// + /// 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. + /// + /// Name of the Content object + /// Parent object for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + public IContent CreateContent(string name, IContent parent, string contentTypeAlias, int userId = 0) + { + if (parent == null) throw new ArgumentNullException("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)) + { + content.WasCancelled = true; + 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. + /// + /// + /// 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 + /// + 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; + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + content.CreatorId = userId; + content.WriterId = userId; + repository.AddOrUpdate(content); + //Generate a new preview + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + uow.Commit(); + } + + Saved.RaiseEvent(new SaveEventArgs(content, false), this); + + Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parentId), this); + + Audit(AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id); + + return content; + } + + /// + /// Creates and saves an object using the alias of the + /// that this Content should based on. + /// + /// + /// 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 + /// + public IContent CreateContentWithIdentity(string name, IContent parent, string contentTypeAlias, int userId = 0) + { + if (parent == null) throw new ArgumentNullException("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)) + { + content.WasCancelled = true; + return content; + } + + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(content), this)) + { + content.WasCancelled = true; + return content; + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + content.CreatorId = userId; + content.WriterId = userId; + repository.AddOrUpdate(content); + //Generate a new preview + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + uow.Commit(); + } + + Saved.RaiseEvent(new SaveEventArgs(content, false), this); + + Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parent), this); + + Audit(AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id); + + return content; + } + + /// + /// Gets an object by Id + /// + /// Id of the Content to retrieve + /// + public IContent GetById(int id) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + return repository.Get(id); + } + } + + /// + /// Gets an object by Id + /// + /// Ids of the Content to retrieve + /// + public IEnumerable GetByIds(IEnumerable ids) + { + if (ids.Any() == false) return Enumerable.Empty(); + + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetAll(ids.ToArray()); + } + } + + /// + /// Gets an object by its 'UniqueId' + /// + /// Guid key of the Content to retrieve + /// + public IContent GetById(Guid key) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Key == key); + var contents = repository.GetByQuery(query); + return contents.SingleOrDefault(); + } + } + + /// + /// Gets a collection of objects by the Id of the + /// + /// Id of the + /// An Enumerable list of objects + public IEnumerable GetContentOfContentType(int id) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ContentTypeId == id); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + internal IEnumerable GetPublishedContentOfContentType(int id) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ContentTypeId == id); + var contents = repository.GetByPublishedVersion(query); + + return contents; + } + } + + /// + /// Gets a collection of objects by Level + /// + /// The level to retrieve Content from + /// An Enumerable list of objects + public IEnumerable GetByLevel(int level) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Level == level && !x.Path.StartsWith(Constants.System.RecycleBinContent.ToInvariantString())); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + /// + /// Gets a specific version of an item. + /// + /// Id of the version to retrieve + /// An item + public IContent GetByVersion(Guid versionId) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetByVersion(versionId); + } + } + + + /// + /// Gets a collection of an objects versions by Id + /// + /// + /// An Enumerable list of objects + public IEnumerable GetVersions(int id) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var versions = repository.GetAllVersions(id); + return versions; + } + } + + /// + /// Gets a collection of objects, which are ancestors of the current content. + /// + /// Id of the to retrieve ancestors for + /// An Enumerable list of objects + public IEnumerable GetAncestors(int id) + { + var content = GetById(id); + return GetAncestors(content); + } + + /// + /// Gets a collection of objects, which are ancestors of the current content. + /// + /// to retrieve ancestors for + /// An Enumerable list of objects + public IEnumerable GetAncestors(IContent content) + { + //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(); + if (ids.Any() == false) + return new List(); + + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetAll(ids); + } + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// An Enumerable list of objects + public IEnumerable GetChildren(int id) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ParentId == id); + var contents = repository.GetByQuery(query).OrderBy(x => x.SortOrder); + + return contents; + } + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, + string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + + var query = Query.Builder; + //if the id is System Root, then just get all + if (id != Constants.System.Root) + { + query.Where(x => x.ParentId == id); + } + long total; + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, orderBySystemField, filter); + totalChildren = Convert.ToInt32(total); + return contents; + } + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page index (zero based) + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, + string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + + var query = Query.Builder; + //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; + } + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + + var query = Query.Builder; + //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)); + } + long total; + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, orderBySystemField, filter); + totalChildren = Convert.ToInt32(total); + return contents; + } + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + + var query = Query.Builder; + //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)); + } + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + + return contents; + } + } + + /// + /// Gets a collection of objects by its name or partial name + /// + /// Id of the Parent to retrieve Children from + /// Full or partial name of the children + /// An Enumerable list of objects + public IEnumerable GetChildrenByName(int parentId, string name) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ParentId == parentId && x.Name.Contains(name)); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// An Enumerable list of objects + public IEnumerable GetDescendants(int id) + { + var content = GetById(id); + if (content == null) + { + return Enumerable.Empty(); + } + return GetDescendants(content); + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// item to retrieve Descendants from + /// An Enumerable list of objects + public IEnumerable GetDescendants(IContent content) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var pathMatch = content.Path + ","; + var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != content.Id); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + /// + /// Gets the parent of the current content as an item. + /// + /// Id of the to retrieve the parent from + /// Parent object + public IContent GetParent(int id) + { + var content = GetById(id); + return GetParent(content); + } + + /// + /// Gets the parent of the current content as an item. + /// + /// to retrieve the parent from + /// Parent object + public IContent GetParent(IContent content) + { + if (content.ParentId == Constants.System.Root || content.ParentId == Constants.System.RecycleBinContent) + return null; + + return GetById(content.ParentId); + } + + /// + /// Gets the published version of an item + /// + /// Id of the to retrieve version from + /// An item + public IContent GetPublishedVersion(int id) + { + var version = GetVersions(id); + return version.FirstOrDefault(x => x.Published == true); + } + + /// + /// Gets the published version of a item. + /// + /// The content item. + /// The published version, if any; otherwise, null. + public IContent GetPublishedVersion(IContent content) + { + if (content.Published) return content; + return content.HasPublishedVersion + ? GetByVersion(content.PublishedVersionGuid) + : null; + } + + /// + /// Gets a collection of objects, which reside at the first level / root + /// + /// An Enumerable list of objects + public IEnumerable GetRootContent() + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ParentId == Constants.System.Root); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + /// + /// Gets all published content items + /// + /// + internal IEnumerable GetAllPublished() + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Trashed == false); + return repository.GetByPublishedVersion(query); + } + } + + /// + /// Gets a collection of objects, which has an expiration date less than or equal to today. + /// + /// An Enumerable list of objects + public IEnumerable GetContentForExpiration() + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Published == true && x.ExpireDate <= DateTime.Now); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + /// + /// Gets a collection of objects, which has a release date less than or equal to today. + /// + /// An Enumerable list of objects + public IEnumerable GetContentForRelease() + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Published == false && x.ReleaseDate <= DateTime.Now); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + /// + /// Gets a collection of an objects, which resides in the Recycle Bin + /// + /// An Enumerable list of objects + public IEnumerable GetContentInRecycleBin() + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Path.Contains(Constants.System.RecycleBinContent.ToInvariantString())); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + + + /// + /// Checks whether an item has any children + /// + /// Id of the + /// True if the content has any children otherwise False + public bool HasChildren(int id) + { + return CountChildren(id) > 0; + } + + internal int CountChildren(int id) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ParentId == id); + var count = repository.Count(query); + return count; + } + } + + /// + /// Checks whether an item has any published versions + /// + /// Id of the + /// True if the content has any published version otherwise False + public bool HasPublishedVersion(int id) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Published == true && x.Id == id && x.Trashed == false); + int count = repository.Count(query); + return count > 0; + } + } + + /// + /// Checks if the passed in can be published based on the anscestors publish state. + /// + /// to check if anscestors are published + /// 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); + } + + return IsPublishable(content, false); + } + + /// + /// This will rebuild the xml structures for content in the database. + /// + /// This is not used for anything + /// True if publishing succeeded, otherwise False + /// + /// This is used for when a document type alias or a document type property is changed, the xml will need to + /// be regenerated. + /// + public bool RePublishAll(int userId = 0) + { + try + { + RebuildXmlStructures(); + return true; + } + catch (Exception ex) + { + Logger.Error("An error occurred executing RePublishAll", ex); + return false; + } + } + + /// + /// This will rebuild the xml structures for content in the database. + /// + /// + /// If specified will only rebuild the xml for the content type's specified, otherwise will update the structure + /// for all published content. + /// + internal void RePublishAll(params int[] contentTypeIds) + { + try + { + RebuildXmlStructures(contentTypeIds); + } + catch (Exception ex) + { + Logger.Error("An error occurred executing RePublishAll", ex); + } + } + + /// + /// Publishes 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) + { + 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; + } + + /// + /// 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); + } + + /// + /// 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) + { + var evtMsgs = EventMessagesFactory.Get(); + + using (new WriteLock(Locker)) + { + 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); + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + content.WriterId = userId; + content.ChangeTrashedState(true); + 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.Commit(); + } + + 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); + } + } + + /// + /// 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); + } + + /// + /// 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; + } + + /// + /// 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 + [Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")] + public bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true) + { + var result = SaveAndPublishDo(content, userId, raiseEvents); + return result.Success; + } + + /// + /// 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 + public Attempt SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true) + { + return ((IContentServiceOperations)this).SaveAndPublish(content, userId, raiseEvents); + } + + /// + /// Saves 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) + { + ((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); + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + 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, 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, c)); + } + } + + uow.Commit(); + } + + 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); + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + repository.Delete(content); + uow.Commit(); + + 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); + } + } + + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// The published status attempt + Attempt IContentServiceOperations.Publish(IContent content, int userId) + { + return SaveAndPublishDo(content, userId); + } + + /// + /// Saves 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) + { + return Save(content, true, userId, raiseEvents); + } + + /// + /// 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); + } + + /// + /// 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. + + using (new WriteLock(Locker)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + //NOTE What about content that has the contenttype as part of its composition? + var query = Query.Builder.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 = Query.Builder.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); + } + } + + /// + /// Permanently deletes an object as well as all of its Children. + /// + /// + /// 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 + public void Delete(IContent content, int userId = 0) + { + ((IContentServiceOperations)this).Delete(content, userId); + } + + /// + /// Permanently deletes versions from an object prior to a specific date. + /// This method will never delete the latest version of a content item. + /// + /// Id of the object to delete versions from + /// Latest version date + /// Optional Id of the User deleting versions of a Content object + public void DeleteVersions(int id, DateTime versionDate, int userId = 0) + { + if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, dateToRetain: versionDate), this)) + return; + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + repository.DeleteVersions(id, versionDate); + uow.Commit(); + } + + DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate), this); + + Audit(AuditType.Delete, "Delete Content by version date performed by user", userId, Constants.System.Root); + } + + /// + /// Permanently deletes specific version(s) from an object. + /// This method will never delete the latest version of a content item. + /// + /// Id of the object to delete a version from + /// Id of the version to delete + /// Boolean indicating whether to delete versions prior to the versionId + /// 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) + { + var content = GetByVersion(versionId); + DeleteVersions(id, content.UpdateDate, userId); + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + repository.DeleteVersion(versionId); + uow.Commit(); + } + + DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), this); + + Audit(AuditType.Delete, "Delete Content by version performed by user", userId, Constants.System.Root); + } + } + + /// + /// 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 + public void MoveToRecycleBin(IContent content, int userId = 0) + { + ((IContentServiceOperations)this).MoveToRecycleBin(content, userId); + } + + /// + /// Moves an object to a new location by changing its parent id. + /// + /// + /// If the object is already published it will be + /// published after being moved to its new location. Otherwise it'll just + /// be saved with a new parent id. + /// + /// The to move + /// Id of the Content's new Parent + /// Optional Id of the User moving the Content + public void Move(IContent content, int parentId, int userId = 0) + { + using (new WriteLock(Locker)) + { + //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); + } + } + + /// + /// Empties the Recycle Bin by deleting all that resides in the bin + /// + public void EmptyRecycleBin() + { + using (new WriteLock(Locker)) + { + Dictionary> entities; + List files; + bool success; + var nodeObjectType = new Guid(Constants.ObjectTypes.Document); + + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + //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); + } + } + Audit(AuditType.Delete, "Empty Content Recycle Bin performed by user", 0, Constants.System.RecycleBinContent); + } + + /// + /// 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. + /// + /// The to copy + /// Id of the Content's new Parent + /// Boolean indicating whether the copy should be related to the original + /// Optional Id of the User copying the Content + /// The newly created object + public IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0) + { + return Copy(content, parentId, relateToOriginal, true, userId); + } + + /// + /// 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. + /// + /// The to copy + /// Id of the Content's new Parent + /// Boolean indicating whether the copy should be related to the original + /// A value indicating whether to recursively copy children. + /// Optional Id of the User copying the Content + /// 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 + + using (new WriteLock(Locker)) + { + var copy = content.DeepCloneWithResetIdentities(); + copy.ParentId = parentId; + + // A copy should never be set to published automatically even if the original was. + copy.ChangePublishedState(PublishedState.Unpublished); + + if (Copying.IsRaisedEventCancelled(new CopyEventArgs(content, copy, parentId), this)) + return null; + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + // Update the create author and last edit author + copy.CreatorId = userId; + copy.WriterId = userId; + + repository.AddOrUpdate(copy); + //add or update a preview + repository.AddOrUpdatePreviewXml(copy, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + uow.Commit(); + + + //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 }); + } + } + } + + if (recursive) + { + //Look for children and copy those as well + var children = GetChildren(content.Id); + foreach (var child in children) + { + //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); + } + } + + 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. + /// + /// The to send to publication + /// Optional Id of the User issueing the send to publication + /// True if sending publication was succesfull otherwise false + public bool SendToPublication(IContent content, int userId = 0) + { + if (SendingToPublish.IsRaisedEventCancelled(new SendToPublishEventArgs(content), this)) + return false; + + //Save before raising event + Save(content, userId); + + SentToPublish.RaiseEvent(new SendToPublishEventArgs(content, false), this); + + Audit(AuditType.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id); + + return true; + } + + /// + /// Rollback an object to a previous version. + /// This will create a new version, which is a copy of all the old data. + /// + /// + /// The way data is stored actually only allows us to rollback on properties + /// and not data like Name and Alias of the Content. + /// + /// Id of the being rolled back + /// Id of the version to rollback to + /// Optional Id of the User issueing the rollback of the Content + /// The newly created object + public IContent Rollback(int id, Guid versionId, int userId = 0) + { + var content = GetByVersion(versionId); + + if (RollingBack.IsRaisedEventCancelled(new RollbackEventArgs(content), this)) + return content; + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + content.WriterId = userId; + content.CreatorId = userId; + content.ChangePublishedState(PublishedState.Unpublished); + + repository.AddOrUpdate(content); + //add or update a preview + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + uow.Commit(); + } + + RolledBack.RaiseEvent(new RollbackEventArgs(content, false), this); + + Audit(AuditType.RollBack, "Content rollback performed by user", content.WriterId, content.Id); + + return content; + } + + /// + /// Sorts a collection of objects by updating the SortOrder according + /// to the ordering of items in the passed in . + /// + /// + /// Using this method will ensure that the Published-state is maintained upon sorting + /// so the cache is updated accordingly - as needed. + /// + /// + /// + /// + /// 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)) + return false; + } + + var shouldBePublished = new List(); + var shouldBeSaved = new List(); + + var asArray = items.ToArray(); + using (new WriteLock(Locker)) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + 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 == 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, c)); + } + + foreach (var content in shouldBePublished) + { + //Create and Save ContentXml DTO + repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + } + + uow.Commit(); + } + } + + 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); + } + + + Audit(AuditType.Sort, "Sorting content performed by user", userId, 0); + + return true; + } + + /// + /// 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) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + repository.RebuildXmlStructures( + content => _entitySerializer.Serialize(this, _dataTypeService, _userService, content), + contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds); + + uow.Commit(); + } + + Audit(AuditType.Publish, "ContentService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, Constants.System.Root); + + } + + #region Internal Methods + + /// + /// Gets a collection of descendants by the first Parent. + /// + /// item to retrieve Descendants from + /// An Enumerable list of objects + internal IEnumerable GetPublishedDescendants(IContent content) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Id != content.Id && x.Path.StartsWith(content.Path) && x.Trashed == false); + var contents = repository.GetByPublishedVersion(query); + + return contents; + } + } + + #endregion + + #region Private Methods + + private void Audit(AuditType type, string message, int userId, int objectId) + { + var uow = UowProvider.GetUnitOfWork(); + using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) + { + auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); + uow.Commit(); + } + } + + //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.GetUnitOfWork()) + { + var xml = _entitySerializer.Serialize(this, _dataTypeService, _userService, 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 + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published + /// + /// A list of publish statues. If the parent document is not valid or cannot be published because it's parent(s) is not published + /// 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) + { + if (content == null) throw new ArgumentNullException("content"); + + var evtMsgs = EventMessagesFactory.Get(); + + using (new WriteLock(Locker)) + { + var result = new List>(); + + //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 or one of its ancestors is not published.", + content.Name, content.Id)); + result.Add(Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedPathNotPublished, evtMsgs))); + return result; + } + + //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 = Query.Builder.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); + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + //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, c)); + //add or update the published xml + repository.AddOrUpdateContentXml(item.Result.ContentItem, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + updated.Add(item.Result.ContentItem); + } + + uow.Commit(); + + } + //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; + } + } + + /// + /// UnPublishes a single object + /// + /// The to publish + /// Optional boolean to avoid having the cache refreshed when calling this Unpublish method. By default this method will update the cache. + /// Optional Id of the User issueing the publishing + /// 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; + + 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)); + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + 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); + repository.DeleteContentXml(content); + + uow.Commit(); + } + //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)); + } + + /// + /// 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 + private Attempt SaveAndPublishDo(IContent content, int userId = 0, bool raiseEvents = true) + { + var evtMsgs = EventMessagesFactory.Get(); + + if (raiseEvents) + { + if (Saving.IsRaisedEventCancelled( + new SaveEventArgs(content, evtMsgs), this)) + { + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent, evtMsgs)); + } + } + + 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 + + //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; + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + 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, c)); + + if (published) + { + //Content Xml + repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + } + + uow.Commit(); + } + + 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)) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + 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, c)); + + uow.Commit(); + } + + 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 repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Alias == contentTypeAlias); + var types = repository.GetByQuery(query); + + if (types.Any() == false) + throw new Exception( + string.Format("No ContentType matching the passed in Alias: '{0}' was found", + contentTypeAlias)); + + var contentType = types.First(); + + if (contentType == null) + throw new Exception(string.Format("ContentType matching the passed in Alias: '{0}' was null", + contentTypeAlias)); + + return contentType; + } + } + + #endregion + + #region Proxy Event Handlers + /// + /// Occurs before publish. + /// + /// Proxy to the real event on the + public static event TypedEventHandler> Publishing + { + add { PublishingStrategy.Publishing += value; } + remove { PublishingStrategy.Publishing -= value; } + } + + /// + /// 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 + /// + public static event TypedEventHandler> Deleting; + + /// + /// Occurs after Delete + /// + public static event TypedEventHandler> Deleted; + + /// + /// Occurs before Delete Versions + /// + public static event TypedEventHandler DeletingVersions; + + /// + /// Occurs after Delete Versions + /// + public static event TypedEventHandler DeletedVersions; + + /// + /// Occurs before Save + /// + public static event TypedEventHandler> Saving; + + /// + /// Occurs after Save + /// + public static event TypedEventHandler> Saved; + + /// + /// Occurs before Create + /// + [Obsolete("Use the Created event instead, the Creating and Created events both offer the same functionality, Creating event has been deprecated.")] + public static event TypedEventHandler> Creating; + + /// + /// Occurs after Create + /// + /// + /// Please note that the Content object has been created, but might not have been saved + /// so it does not have an identity yet (meaning no Id has been set). + /// + public static event TypedEventHandler> Created; + + /// + /// Occurs before Copy + /// + public static event TypedEventHandler> Copying; + + /// + /// Occurs after Copy + /// + public static event TypedEventHandler> Copied; + + /// + /// Occurs before Content is moved to Recycle Bin + /// + public static event TypedEventHandler> Trashing; + + /// + /// Occurs after Content is moved to Recycle Bin + /// + public static event TypedEventHandler> Trashed; + + /// + /// Occurs before Move + /// + public static event TypedEventHandler> Moving; + + /// + /// Occurs after Move + /// + public static event TypedEventHandler> Moved; + + /// + /// Occurs before Rollback + /// + public static event TypedEventHandler> RollingBack; + + /// + /// Occurs after Rollback + /// + public static event TypedEventHandler> RolledBack; + + /// + /// Occurs before Send to Publish + /// + public static event TypedEventHandler> SendingToPublish; + + /// + /// Occurs after Send to Publish + /// + public static event TypedEventHandler> SentToPublish; + + /// + /// Occurs before the Recycle Bin is emptied + /// + public static event TypedEventHandler EmptyingRecycleBin; + + /// + /// Occurs after the Recycle Bin has been Emptied + /// + public static event TypedEventHandler EmptiedRecycleBin; + #endregion + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index c686cf4891..fa688eae39 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -8,577 +8,579 @@ using Umbraco.Core.Publishing; namespace Umbraco.Core.Services { - /// - /// A temporary interface until we are in v8, this is used to return a different result for the same method and this interface gets implemented - /// explicitly. These methods will replace the normal ones in IContentService in v8 and this will be removed. - /// - public interface IContentServiceOperations - { - //TODO: Remove this class in v8 - - //TODO: There's probably more that needs to be added like the EmptyRecycleBin, etc... - - /// - /// Saves a single object - /// - /// The to save - /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - Attempt Save(IContent content, int userId = 0, bool raiseEvents = true); - - /// - /// 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 Save(IEnumerable contents, int userId = 0, bool raiseEvents = true); - - /// - /// 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 Delete(IContent content, int userId = 0); - - /// - /// Publishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// The published status attempt - Attempt Publish(IContent content, int userId = 0); - - /// - /// 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> PublishWithChildren(IContent content, int userId = 0, bool includeUnpublished = false); - - /// - /// 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 SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); - - /// - /// 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 MoveToRecycleBin(IContent content, int userId = 0); - - /// - /// UnPublishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if unpublishing succeeded, otherwise False - Attempt UnPublish(IContent content, int userId = 0); - } - - /// - /// Defines the ContentService, which is an easy access to operations involving - /// - public interface IContentService : IService - { - /// - /// 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 - /// - void RebuildXmlStructures(params int[] contentTypeIds); - - int CountPublished(string contentTypeAlias = null); - int Count(string contentTypeAlias = null); - int CountChildren(int parentId, string contentTypeAlias = null); - int CountDescendants(int parentId, string contentTypeAlias = null); - - /// - /// 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. - /// - /// - void ReplaceContentPermissions(EntityPermissionSet permissionSet); - - /// - /// Assigns a single permission to the current content item for the specified user ids - /// - /// - /// - /// - void AssignContentPermission(IContent entity, char permission, IEnumerable userIds); - - /// - /// Gets the list of permissions for the content item - /// - /// - /// - IEnumerable GetPermissionsForEntity(IContent content); - - bool SendToPublication(IContent content, int userId = 0); - - IEnumerable GetByIds(IEnumerable ids); - - /// - /// Creates an object using the alias of the - /// that this Content should based on. - /// - /// - /// 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. - /// - /// Name of the Content object - /// Id of Parent for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// - IContent CreateContent(string name, int parentId, string contentTypeAlias, int userId = 0); - - /// - /// Creates an object using the alias of the - /// that this Content should based on. - /// - /// - /// 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. - /// - /// Name of the Content object - /// Parent object for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// - IContent CreateContent(string name, IContent parent, string contentTypeAlias, int userId = 0); - - /// - /// Gets an object by Id - /// - /// Id of the Content to retrieve - /// - IContent GetById(int id); - - /// - /// Gets an object by its 'UniqueId' - /// - /// Guid key of the Content to retrieve - /// - IContent GetById(Guid key); - - /// - /// Gets a collection of objects by the Id of the - /// - /// Id of the - /// An Enumerable list of objects - IEnumerable GetContentOfContentType(int id); - - /// - /// Gets a collection of objects by Level - /// - /// The level to retrieve Content from - /// An Enumerable list of objects - IEnumerable GetByLevel(int level); - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// An Enumerable list of objects - IEnumerable GetChildren(int id); - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects - IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects - IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); - - /// - /// Gets a collection of an objects versions by its Id - /// - /// - /// An Enumerable list of objects - IEnumerable GetVersions(int id); - - /// - /// Gets a collection of objects, which reside at the first level / root - /// - /// An Enumerable list of objects - IEnumerable GetRootContent(); - - /// - /// Gets a collection of objects, which has an expiration date greater then today - /// - /// An Enumerable list of objects - IEnumerable GetContentForExpiration(); - - /// - /// Gets a collection of objects, which has a release date greater then today - /// - /// An Enumerable list of objects - IEnumerable GetContentForRelease(); - - /// - /// Gets a collection of an objects, which resides in the Recycle Bin - /// - /// An Enumerable list of objects - IEnumerable GetContentInRecycleBin(); - - /// - /// Saves a single object - /// - /// The to save - /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - void Save(IContent content, int userId = 0, bool raiseEvents = true); - - /// - /// 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. - void Save(IEnumerable contents, int userId = 0, bool raiseEvents = true); - - /// - /// 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 - void DeleteContentOfType(int contentTypeId, int userId = 0); - - /// - /// Permanently deletes versions from an object prior to a specific date. - /// - /// Id of the object to delete versions from - /// Latest version date - /// Optional Id of the User deleting versions of a Content object - void DeleteVersions(int id, DateTime versionDate, int userId = 0); - - /// - /// Permanently deletes a specific version from an object. - /// - /// Id of the object to delete a version from - /// Id of the version to delete - /// Boolean indicating whether to delete versions prior to the versionId - /// Optional Id of the User deleting versions of a Content object - void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0); - - /// - /// 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 - void MoveToRecycleBin(IContent content, int userId = 0); - - /// - /// Moves an object to a new location - /// - /// The to move - /// Id of the Content's new Parent - /// Optional Id of the User moving the Content - void Move(IContent content, int parentId, int userId = 0); - - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - void EmptyRecycleBin(); - - /// - /// Rollback an object to a previous version. - /// This will create a new version, which is a copy of all the old data. - /// - /// Id of the being rolled back - /// Id of the version to rollback to - /// Optional Id of the User issueing the rollback of the Content - /// The newly created object - IContent Rollback(int id, Guid versionId, int userId = 0); - - /// - /// Gets a collection of objects by its name or partial name - /// - /// Id of the Parent to retrieve Children from - /// Full or partial name of the children - /// An Enumerable list of objects - IEnumerable GetChildrenByName(int parentId, string name); - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// An Enumerable list of objects - IEnumerable GetDescendants(int id); - - /// - /// Gets a collection of objects by Parent Id - /// - /// item to retrieve Descendants from - /// An Enumerable list of objects - IEnumerable GetDescendants(IContent content); - - /// - /// Gets a specific version of an item. - /// - /// Id of the version to retrieve - /// An item - IContent GetByVersion(Guid versionId); - - /// - /// Gets the published version of an item - /// - /// Id of the to retrieve version from - /// An item - IContent GetPublishedVersion(int id); - - /// - /// Gets the published version of a item. - /// - /// The content item. - /// The published version, if any; otherwise, null. - IContent GetPublishedVersion(IContent content); - - /// - /// Checks whether an item has any children - /// - /// Id of the - /// True if the content has any children otherwise False - bool HasChildren(int id); - - /// - /// Checks whether an item has any published versions - /// - /// Id of the - /// True if the content has any published version otherwise False - bool HasPublishedVersion(int id); - - /// - /// Re-Publishes all Content - /// - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - bool RePublishAll(int userId = 0); - - /// - /// Publishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - bool Publish(IContent content, int userId = 0); - - /// - /// Publishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// The published status attempt - Attempt PublishWithStatus(IContent content, int userId = 0); - - /// - /// 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 - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use PublishWithChildrenWithStatus instead, that method will provide more detailed information on the outcome and also allows the includeUnpublished flag")] - bool PublishWithChildren(IContent content, int userId = 0); - - /// - /// 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> PublishWithChildrenWithStatus(IContent content, int userId = 0, bool includeUnpublished = false); - - /// - /// UnPublishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if unpublishing succeeded, otherwise False - bool UnPublish(IContent content, int userId = 0); - - /// - /// 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 - [Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")] - [EditorBrowsable(EditorBrowsableState.Never)] - bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); - - /// - /// 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 SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true); - - /// - /// 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 - void Delete(IContent content, int userId = 0); - - /// - /// 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. - /// - /// The to copy - /// Id of the Content's new Parent - /// Boolean indicating whether the copy should be related to the original - /// Optional Id of the User copying the Content - /// The newly created object - IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0); - - /// - /// 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. - /// - /// The to copy - /// Id of the Content's new Parent - /// Boolean indicating whether the copy should be related to the original - /// A value indicating whether to recursively copy children. - /// Optional Id of the User copying the Content - /// The newly created object - IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = 0); - - /// - /// Checks if the passed in can be published based on the anscestors publish state. - /// - /// to check if anscestors are published - /// True if the Content can be published, otherwise False - bool IsPublishable(IContent content); - - /// - /// Gets a collection of objects, which are ancestors of the current content. - /// - /// Id of the to retrieve ancestors for - /// An Enumerable list of objects - IEnumerable GetAncestors(int id); - - /// - /// Gets a collection of objects, which are ancestors of the current content. - /// - /// to retrieve ancestors for - /// An Enumerable list of objects - IEnumerable GetAncestors(IContent content); - - /// - /// Sorts a collection of objects by updating the SortOrder according - /// to the ordering of items in the passed in . - /// - /// - /// Using this method will ensure that the Published-state is maintained upon sorting - /// so the cache is updated accordingly - as needed. - /// - /// - /// - /// - /// True if sorting succeeded, otherwise False - bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true); - - /// - /// Gets the parent of the current content as an item. - /// - /// Id of the to retrieve the parent from - /// Parent object - IContent GetParent(int id); - - /// - /// Gets the parent of the current content as an item. - /// - /// to retrieve the parent from - /// Parent object - IContent GetParent(IContent content); - - /// - /// Creates and saves an object using the alias of the - /// that this Content should based on. - /// - /// - /// 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 - /// - IContent CreateContentWithIdentity(string name, IContent parent, string contentTypeAlias, int userId = 0); - - /// - /// Creates and saves an object using the alias of the - /// that this Content should based on. - /// - /// - /// 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 - /// - IContent CreateContentWithIdentity(string name, int parentId, string contentTypeAlias, int userId = 0); - } + /// + /// A temporary interface until we are in v8, this is used to return a different result for the same method and this interface gets implemented + /// explicitly. These methods will replace the normal ones in IContentService in v8 and this will be removed. + /// + public interface IContentServiceOperations + { + //TODO: Remove this class in v8 + + //TODO: There's probably more that needs to be added like the EmptyRecycleBin, etc... + + /// + /// Saves a single object + /// + /// The to save + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + Attempt Save(IContent content, int userId = 0, bool raiseEvents = true); + + /// + /// 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 Save(IEnumerable contents, int userId = 0, bool raiseEvents = true); + + /// + /// 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 Delete(IContent content, int userId = 0); + + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// The published status attempt + Attempt Publish(IContent content, int userId = 0); + + /// + /// 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> PublishWithChildren(IContent content, int userId = 0, bool includeUnpublished = false); + + /// + /// 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 SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); + + /// + /// 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 MoveToRecycleBin(IContent content, int userId = 0); + + /// + /// UnPublishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if unpublishing succeeded, otherwise False + Attempt UnPublish(IContent content, int userId = 0); + } + + /// + /// Defines the ContentService, which is an easy access to operations involving + /// + public interface IContentService : IService + { + /// + /// 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 + /// + void RebuildXmlStructures(params int[] contentTypeIds); + + int CountPublished(string contentTypeAlias = null); + int Count(string contentTypeAlias = null); + int CountChildren(int parentId, string contentTypeAlias = null); + int CountDescendants(int parentId, string contentTypeAlias = null); + + /// + /// 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. + /// + /// + void ReplaceContentPermissions(EntityPermissionSet permissionSet); + + /// + /// Assigns a single permission to the current content item for the specified user ids + /// + /// + /// + /// + void AssignContentPermission(IContent entity, char permission, IEnumerable userIds); + + /// + /// Gets the list of permissions for the content item + /// + /// + /// + IEnumerable GetPermissionsForEntity(IContent content); + + bool SendToPublication(IContent content, int userId = 0); + + IEnumerable GetByIds(IEnumerable ids); + + /// + /// Creates an object using the alias of the + /// that this Content should based on. + /// + /// + /// 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. + /// + /// Name of the Content object + /// Id of Parent for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + IContent CreateContent(string name, int parentId, string contentTypeAlias, int userId = 0); + + /// + /// Creates an object using the alias of the + /// that this Content should based on. + /// + /// + /// 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. + /// + /// Name of the Content object + /// Parent object for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + IContent CreateContent(string name, IContent parent, string contentTypeAlias, int userId = 0); + + /// + /// Gets an object by Id + /// + /// Id of the Content to retrieve + /// + IContent GetById(int id); + + /// + /// Gets an object by its 'UniqueId' + /// + /// Guid key of the Content to retrieve + /// + IContent GetById(Guid key); + + /// + /// Gets a collection of objects by the Id of the + /// + /// Id of the + /// An Enumerable list of objects + IEnumerable GetContentOfContentType(int id); + + /// + /// Gets a collection of objects by Level + /// + /// The level to retrieve Content from + /// An Enumerable list of objects + IEnumerable GetByLevel(int level); + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// An Enumerable list of objects + IEnumerable GetChildren(int id); + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, + string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + + /// + /// Gets a collection of an objects versions by its Id + /// + /// + /// An Enumerable list of objects + IEnumerable GetVersions(int id); + + /// + /// Gets a collection of objects, which reside at the first level / root + /// + /// An Enumerable list of objects + IEnumerable GetRootContent(); + + /// + /// Gets a collection of objects, which has an expiration date greater then today + /// + /// An Enumerable list of objects + IEnumerable GetContentForExpiration(); + + /// + /// Gets a collection of objects, which has a release date greater then today + /// + /// An Enumerable list of objects + IEnumerable GetContentForRelease(); + + /// + /// Gets a collection of an objects, which resides in the Recycle Bin + /// + /// An Enumerable list of objects + IEnumerable GetContentInRecycleBin(); + + /// + /// Saves a single object + /// + /// The to save + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + void Save(IContent content, int userId = 0, bool raiseEvents = true); + + /// + /// 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. + void Save(IEnumerable contents, int userId = 0, bool raiseEvents = true); + + /// + /// 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 + void DeleteContentOfType(int contentTypeId, int userId = 0); + + /// + /// Permanently deletes versions from an object prior to a specific date. + /// + /// Id of the object to delete versions from + /// Latest version date + /// Optional Id of the User deleting versions of a Content object + void DeleteVersions(int id, DateTime versionDate, int userId = 0); + + /// + /// Permanently deletes a specific version from an object. + /// + /// Id of the object to delete a version from + /// Id of the version to delete + /// Boolean indicating whether to delete versions prior to the versionId + /// Optional Id of the User deleting versions of a Content object + void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0); + + /// + /// 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 + void MoveToRecycleBin(IContent content, int userId = 0); + + /// + /// Moves an object to a new location + /// + /// The to move + /// Id of the Content's new Parent + /// Optional Id of the User moving the Content + void Move(IContent content, int parentId, int userId = 0); + + /// + /// Empties the Recycle Bin by deleting all that resides in the bin + /// + void EmptyRecycleBin(); + + /// + /// Rollback an object to a previous version. + /// This will create a new version, which is a copy of all the old data. + /// + /// Id of the being rolled back + /// Id of the version to rollback to + /// Optional Id of the User issueing the rollback of the Content + /// The newly created object + IContent Rollback(int id, Guid versionId, int userId = 0); + + /// + /// Gets a collection of objects by its name or partial name + /// + /// Id of the Parent to retrieve Children from + /// Full or partial name of the children + /// An Enumerable list of objects + IEnumerable GetChildrenByName(int parentId, string name); + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// An Enumerable list of objects + IEnumerable GetDescendants(int id); + + /// + /// Gets a collection of objects by Parent Id + /// + /// item to retrieve Descendants from + /// An Enumerable list of objects + IEnumerable GetDescendants(IContent content); + + /// + /// Gets a specific version of an item. + /// + /// Id of the version to retrieve + /// An item + IContent GetByVersion(Guid versionId); + + /// + /// Gets the published version of an item + /// + /// Id of the to retrieve version from + /// An item + IContent GetPublishedVersion(int id); + + /// + /// Gets the published version of a item. + /// + /// The content item. + /// The published version, if any; otherwise, null. + IContent GetPublishedVersion(IContent content); + + /// + /// Checks whether an item has any children + /// + /// Id of the + /// True if the content has any children otherwise False + bool HasChildren(int id); + + /// + /// Checks whether an item has any published versions + /// + /// Id of the + /// True if the content has any published version otherwise False + bool HasPublishedVersion(int id); + + /// + /// Re-Publishes all Content + /// + /// Optional Id of the User issueing the publishing + /// True if publishing succeeded, otherwise False + bool RePublishAll(int userId = 0); + + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if publishing succeeded, otherwise False + bool Publish(IContent content, int userId = 0); + + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// The published status attempt + Attempt PublishWithStatus(IContent content, int userId = 0); + + /// + /// 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 + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use PublishWithChildrenWithStatus instead, that method will provide more detailed information on the outcome and also allows the includeUnpublished flag")] + bool PublishWithChildren(IContent content, int userId = 0); + + /// + /// 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> PublishWithChildrenWithStatus(IContent content, int userId = 0, bool includeUnpublished = false); + + /// + /// UnPublishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if unpublishing succeeded, otherwise False + bool UnPublish(IContent content, int userId = 0); + + /// + /// 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 + [Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")] + [EditorBrowsable(EditorBrowsableState.Never)] + bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); + + /// + /// 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 SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true); + + /// + /// 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 + void Delete(IContent content, int userId = 0); + + /// + /// 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. + /// + /// The to copy + /// Id of the Content's new Parent + /// Boolean indicating whether the copy should be related to the original + /// Optional Id of the User copying the Content + /// The newly created object + IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0); + + /// + /// 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. + /// + /// The to copy + /// Id of the Content's new Parent + /// Boolean indicating whether the copy should be related to the original + /// A value indicating whether to recursively copy children. + /// Optional Id of the User copying the Content + /// The newly created object + IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = 0); + + /// + /// Checks if the passed in can be published based on the anscestors publish state. + /// + /// to check if anscestors are published + /// True if the Content can be published, otherwise False + bool IsPublishable(IContent content); + + /// + /// Gets a collection of objects, which are ancestors of the current content. + /// + /// Id of the to retrieve ancestors for + /// An Enumerable list of objects + IEnumerable GetAncestors(int id); + + /// + /// Gets a collection of objects, which are ancestors of the current content. + /// + /// to retrieve ancestors for + /// An Enumerable list of objects + IEnumerable GetAncestors(IContent content); + + /// + /// Sorts a collection of objects by updating the SortOrder according + /// to the ordering of items in the passed in . + /// + /// + /// Using this method will ensure that the Published-state is maintained upon sorting + /// so the cache is updated accordingly - as needed. + /// + /// + /// + /// + /// True if sorting succeeded, otherwise False + bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true); + + /// + /// Gets the parent of the current content as an item. + /// + /// Id of the to retrieve the parent from + /// Parent object + IContent GetParent(int id); + + /// + /// Gets the parent of the current content as an item. + /// + /// to retrieve the parent from + /// Parent object + IContent GetParent(IContent content); + + /// + /// Creates and saves an object using the alias of the + /// that this Content should based on. + /// + /// + /// 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 + /// + IContent CreateContentWithIdentity(string name, IContent parent, string contentTypeAlias, int userId = 0); + + /// + /// Creates and saves an object using the alias of the + /// that this Content should based on. + /// + /// + /// 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 + /// + IContent CreateContentWithIdentity(string name, int parentId, string contentTypeAlias, int userId = 0); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index d104b95ddc..0bb56bb44b 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -7,368 +7,370 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Services { - /// - /// A temporary interface until we are in v8, this is used to return a different result for the same method and this interface gets implemented - /// explicitly. These methods will replace the normal ones in IContentService in v8 and this will be removed. - /// - public interface IMediaServiceOperations - { - //TODO: Remove this class in v8 + /// + /// A temporary interface until we are in v8, this is used to return a different result for the same method and this interface gets implemented + /// explicitly. These methods will replace the normal ones in IContentService in v8 and this will be removed. + /// + public interface IMediaServiceOperations + { + //TODO: Remove this class in v8 - //TODO: There's probably more that needs to be added like the EmptyRecycleBin, etc... + //TODO: There's probably more that needs to be added like the EmptyRecycleBin, etc... - /// - /// Deletes an object by moving it to the Recycle Bin - /// - /// The to delete - /// Id of the User deleting the Media - Attempt MoveToRecycleBin(IMedia media, int userId = 0); + /// + /// Deletes an object by moving it to the Recycle Bin + /// + /// The to delete + /// Id of the User deleting the Media + Attempt MoveToRecycleBin(IMedia media, int userId = 0); - /// - /// Permanently deletes an object - /// - /// - /// Please note that this method will completely remove the Media from the database, - /// but current not from the file system. - /// - /// The to delete - /// Id of the User deleting the Media - Attempt Delete(IMedia media, int userId = 0); + /// + /// Permanently deletes an object + /// + /// + /// Please note that this method will completely remove the Media from the database, + /// but current not from the file system. + /// + /// The to delete + /// Id of the User deleting the Media + Attempt Delete(IMedia media, int userId = 0); - /// - /// Saves a single object - /// - /// The to save - /// Id of the User saving the Media - /// Optional boolean indicating whether or not to raise events. - Attempt Save(IMedia media, int userId = 0, bool raiseEvents = true); + /// + /// Saves a single object + /// + /// The to save + /// Id of the User saving the Media + /// Optional boolean indicating whether or not to raise events. + Attempt Save(IMedia media, int userId = 0, bool raiseEvents = true); - /// - /// Saves a collection of objects - /// - /// Collection of to save - /// Id of the User saving the Media - /// Optional boolean indicating whether or not to raise events. - Attempt Save(IEnumerable medias, int userId = 0, bool raiseEvents = true); - } + /// + /// Saves a collection of objects + /// + /// Collection of to save + /// Id of the User saving the Media + /// Optional boolean indicating whether or not to raise events. + Attempt Save(IEnumerable medias, int userId = 0, bool raiseEvents = true); + } - /// - /// Defines the Media Service, which is an easy access to operations involving - /// - public interface IMediaService : IService - { - /// - /// Rebuilds all xml content in the cmsContentXml table for all media - /// - /// - /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures - /// for all media - /// - void RebuildXmlStructures(params int[] contentTypeIds); + /// + /// Defines the Media Service, which is an easy access to operations involving + /// + public interface IMediaService : IService + { + /// + /// Rebuilds all xml content in the cmsContentXml table for all media + /// + /// + /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures + /// for all media + /// + void RebuildXmlStructures(params int[] contentTypeIds); - int Count(string contentTypeAlias = null); - int CountChildren(int parentId, string contentTypeAlias = null); - int CountDescendants(int parentId, string contentTypeAlias = null); + int Count(string contentTypeAlias = null); + int CountChildren(int parentId, string contentTypeAlias = null); + int CountDescendants(int parentId, string contentTypeAlias = null); - IEnumerable GetByIds(IEnumerable ids); + IEnumerable GetByIds(IEnumerable ids); - /// - /// Creates an object using the alias of the - /// that this Media should based on. - /// - /// - /// Note that using this method will simply return a new IMedia without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects - /// that does not invoke a save operation against the database. - /// - /// Name of the Media object - /// Id of Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// - IMedia CreateMedia(string name, int parentId, string mediaTypeAlias, int userId = 0); + /// + /// Creates an object using the alias of the + /// that this Media should based on. + /// + /// + /// Note that using this method will simply return a new IMedia without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects + /// that does not invoke a save operation against the database. + /// + /// Name of the Media object + /// Id of Parent for the new Media item + /// Alias of the + /// Optional id of the user creating the media item + /// + IMedia CreateMedia(string name, int parentId, string mediaTypeAlias, int userId = 0); - /// - /// Creates an object using the alias of the - /// that this Media should based on. - /// - /// - /// Note that using this method will simply return a new IMedia without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects - /// that does not invoke a save operation against the database. - /// - /// Name of the Media object - /// Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// - IMedia CreateMedia(string name, IMedia parent, string mediaTypeAlias, int userId = 0); + /// + /// Creates an object using the alias of the + /// that this Media should based on. + /// + /// + /// Note that using this method will simply return a new IMedia without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects + /// that does not invoke a save operation against the database. + /// + /// Name of the Media object + /// Parent for the new Media item + /// Alias of the + /// Optional id of the user creating the media item + /// + IMedia CreateMedia(string name, IMedia parent, string mediaTypeAlias, int userId = 0); - /// - /// Gets an object by Id - /// - /// Id of the Content to retrieve - /// - IMedia GetById(int id); + /// + /// Gets an object by Id + /// + /// Id of the Content to retrieve + /// + IMedia GetById(int id); - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// An Enumerable list of objects - IEnumerable GetChildren(int id); + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// An Enumerable list of objects + IEnumerable GetChildren(int id); - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects - IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, + string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects - IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); - /// - /// Gets descendants of a object by its Id - /// - /// Id of the Parent to retrieve descendants from - /// An Enumerable flat list of objects - IEnumerable GetDescendants(int id); + /// + /// Gets descendants of a object by its Id + /// + /// Id of the Parent to retrieve descendants from + /// An Enumerable flat list of objects + IEnumerable GetDescendants(int id); - /// - /// Gets a collection of objects by the Id of the - /// - /// Id of the - /// An Enumerable list of objects - IEnumerable GetMediaOfMediaType(int id); + /// + /// Gets a collection of objects by the Id of the + /// + /// Id of the + /// An Enumerable list of objects + IEnumerable GetMediaOfMediaType(int id); - /// - /// Gets a collection of objects, which reside at the first level / root - /// - /// An Enumerable list of objects - IEnumerable GetRootMedia(); + /// + /// Gets a collection of objects, which reside at the first level / root + /// + /// An Enumerable list of objects + IEnumerable GetRootMedia(); - /// - /// Gets a collection of an objects, which resides in the Recycle Bin - /// - /// An Enumerable list of objects - IEnumerable GetMediaInRecycleBin(); + /// + /// Gets a collection of an objects, which resides in the Recycle Bin + /// + /// An Enumerable list of objects + IEnumerable GetMediaInRecycleBin(); - /// - /// Moves an object to a new location - /// - /// The to move - /// Id of the Media's new Parent - /// Id of the User moving the Media - void Move(IMedia media, int parentId, int userId = 0); + /// + /// Moves an object to a new location + /// + /// The to move + /// Id of the Media's new Parent + /// Id of the User moving the Media + void Move(IMedia media, int parentId, int userId = 0); - /// - /// Deletes an object by moving it to the Recycle Bin - /// - /// The to delete - /// Id of the User deleting the Media - void MoveToRecycleBin(IMedia media, int userId = 0); + /// + /// Deletes an object by moving it to the Recycle Bin + /// + /// The to delete + /// Id of the User deleting the Media + void MoveToRecycleBin(IMedia media, int userId = 0); - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - void EmptyRecycleBin(); + /// + /// Empties the Recycle Bin by deleting all that resides in the bin + /// + void EmptyRecycleBin(); - /// - /// Deletes all media of specified type. All children of deleted media 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 deleting Media - void DeleteMediaOfType(int mediaTypeId, int userId = 0); + /// + /// Deletes all media of specified type. All children of deleted media 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 deleting Media + void DeleteMediaOfType(int mediaTypeId, int userId = 0); - /// - /// Permanently deletes an object - /// - /// - /// Please note that this method will completely remove the Media from the database, - /// but current not from the file system. - /// - /// The to delete - /// Id of the User deleting the Media - void Delete(IMedia media, int userId = 0); - - /// - /// Saves a single object - /// - /// The to save - /// Id of the User saving the Media - /// Optional boolean indicating whether or not to raise events. - void Save(IMedia media, int userId = 0, bool raiseEvents = true); + /// + /// Permanently deletes an object + /// + /// + /// Please note that this method will completely remove the Media from the database, + /// but current not from the file system. + /// + /// The to delete + /// Id of the User deleting the Media + void Delete(IMedia media, int userId = 0); - /// - /// Saves a collection of objects - /// - /// Collection of to save - /// Id of the User saving the Media - /// Optional boolean indicating whether or not to raise events. - void Save(IEnumerable medias, int userId = 0, bool raiseEvents = true); + /// + /// Saves a single object + /// + /// The to save + /// Id of the User saving the Media + /// Optional boolean indicating whether or not to raise events. + void Save(IMedia media, int userId = 0, bool raiseEvents = true); - /// - /// Gets an object by its 'UniqueId' - /// - /// Guid key of the Media to retrieve - /// - IMedia GetById(Guid key); + /// + /// Saves a collection of objects + /// + /// Collection of to save + /// Id of the User saving the Media + /// Optional boolean indicating whether or not to raise events. + void Save(IEnumerable medias, int userId = 0, bool raiseEvents = true); - /// - /// Gets a collection of objects by Level - /// - /// The level to retrieve Media from - /// An Enumerable list of objects - IEnumerable GetByLevel(int level); + /// + /// Gets an object by its 'UniqueId' + /// + /// Guid key of the Media to retrieve + /// + IMedia GetById(Guid key); - /// - /// Gets a specific version of an item. - /// - /// Id of the version to retrieve - /// An item - IMedia GetByVersion(Guid versionId); + /// + /// Gets a collection of objects by Level + /// + /// The level to retrieve Media from + /// An Enumerable list of objects + IEnumerable GetByLevel(int level); - /// - /// Gets a collection of an objects versions by Id - /// - /// - /// An Enumerable list of objects - IEnumerable GetVersions(int id); + /// + /// Gets a specific version of an item. + /// + /// Id of the version to retrieve + /// An item + IMedia GetByVersion(Guid versionId); - /// - /// Checks whether an item has any children - /// - /// Id of the - /// True if the media has any children otherwise False - bool HasChildren(int id); + /// + /// Gets a collection of an objects versions by Id + /// + /// + /// An Enumerable list of objects + IEnumerable GetVersions(int id); - /// - /// Permanently deletes versions from an object prior to a specific date. - /// - /// Id of the object to delete versions from - /// Latest version date - /// Optional Id of the User deleting versions of a Content object - void DeleteVersions(int id, DateTime versionDate, int userId = 0); + /// + /// Checks whether an item has any children + /// + /// Id of the + /// True if the media has any children otherwise False + bool HasChildren(int id); - /// - /// Permanently deletes specific version(s) from an object. - /// - /// Id of the object to delete a version from - /// Id of the version to delete - /// Boolean indicating whether to delete versions prior to the versionId - /// Optional Id of the User deleting versions of a Content object - void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0); + /// + /// Permanently deletes versions from an object prior to a specific date. + /// + /// Id of the object to delete versions from + /// Latest version date + /// Optional Id of the User deleting versions of a Content object + void DeleteVersions(int id, DateTime versionDate, int userId = 0); - /// - /// Gets an object from the path stored in the 'umbracoFile' property. - /// - /// Path of the media item to retrieve (for example: /media/1024/koala_403x328.jpg) - /// - IMedia GetMediaByPath(string mediaPath); + /// + /// Permanently deletes specific version(s) from an object. + /// + /// Id of the object to delete a version from + /// Id of the version to delete + /// Boolean indicating whether to delete versions prior to the versionId + /// Optional Id of the User deleting versions of a Content object + void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0); - /// - /// Gets a collection of objects, which are ancestors of the current media. - /// - /// Id of the to retrieve ancestors for - /// An Enumerable list of objects - IEnumerable GetAncestors(int id); + /// + /// Gets an object from the path stored in the 'umbracoFile' property. + /// + /// Path of the media item to retrieve (for example: /media/1024/koala_403x328.jpg) + /// + IMedia GetMediaByPath(string mediaPath); - /// - /// Gets a collection of objects, which are ancestors of the current media. - /// - /// to retrieve ancestors for - /// An Enumerable list of objects - IEnumerable GetAncestors(IMedia media); + /// + /// Gets a collection of objects, which are ancestors of the current media. + /// + /// Id of the to retrieve ancestors for + /// An Enumerable list of objects + IEnumerable GetAncestors(int id); - /// - /// Gets descendants of a object by its Id - /// - /// The Parent object to retrieve descendants from - /// An Enumerable flat list of objects - IEnumerable GetDescendants(IMedia media); + /// + /// Gets a collection of objects, which are ancestors of the current media. + /// + /// to retrieve ancestors for + /// An Enumerable list of objects + IEnumerable GetAncestors(IMedia media); - /// - /// Gets the parent of the current media as an item. - /// - /// Id of the to retrieve the parent from - /// Parent object - IMedia GetParent(int id); + /// + /// Gets descendants of a object by its Id + /// + /// The Parent object to retrieve descendants from + /// An Enumerable flat list of objects + IEnumerable GetDescendants(IMedia media); - /// - /// Gets the parent of the current media as an item. - /// - /// to retrieve the parent from - /// Parent object - IMedia GetParent(IMedia media); + /// + /// Gets the parent of the current media as an item. + /// + /// Id of the to retrieve the parent from + /// Parent object + IMedia GetParent(int id); - /// - /// Sorts a collection of objects by updating the SortOrder according - /// to the ordering of items in the passed in . - /// - /// - /// - /// - /// True if sorting succeeded, otherwise False - bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true); + /// + /// Gets the parent of the current media as an item. + /// + /// to retrieve the parent from + /// Parent object + IMedia GetParent(IMedia media); - /// - /// Creates an object using the alias of the - /// that this Media should based on. - /// - /// - /// This method returns an object that has been persisted to the database - /// and therefor has an identity. - /// - /// Name of the Media object - /// Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// - IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = 0); + /// + /// Sorts a collection of objects by updating the SortOrder according + /// to the ordering of items in the passed in . + /// + /// + /// + /// + /// True if sorting succeeded, otherwise False + bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true); - /// - /// Creates an object using the alias of the - /// that this Media should based on. - /// - /// - /// This method returns an object that has been persisted to the database - /// and therefor has an identity. - /// - /// Name of the Media object - /// Id of Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// - IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = 0); - } + /// + /// Creates an object using the alias of the + /// that this Media should based on. + /// + /// + /// This method returns an object that has been persisted to the database + /// and therefor has an identity. + /// + /// Name of the Media object + /// Parent for the new Media item + /// Alias of the + /// Optional id of the user creating the media item + /// + IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = 0); + + /// + /// Creates an object using the alias of the + /// that this Media should based on. + /// + /// + /// This method returns an object that has been persisted to the database + /// and therefor has an identity. + /// + /// Name of the Media object + /// Id of Parent for the new Media item + /// Alias of the + /// Optional id of the user creating the media item + /// + IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = 0); + } } diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 20eef54b3c..2177d3b14e 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -8,213 +8,214 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Services { - /// - /// Defines the MemberService, which is an easy access to operations involving (umbraco) members. - /// - public interface IMemberService : IMembershipMemberService - { - /// - /// 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 - /// - void RebuildXmlStructures(params int[] contentTypeIds); + /// + /// Defines the MemberService, which is an easy access to operations involving (umbraco) members. + /// + public interface IMemberService : IMembershipMemberService + { + /// + /// 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 + /// + void RebuildXmlStructures(params int[] contentTypeIds); - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords, - string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = ""); + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = ""); - /// - /// Gets a list of paged objects - /// - /// An can be of type - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// - /// - /// - /// - /// - IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = ""); - - /// - /// Creates an object without persisting it - /// - /// This method is convenient for when you need to add properties to a new Member - /// before persisting it in order to limit the amount of times its saved. - /// Also note that the returned will not have an Id until its saved. - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// Alias of the MemberType the Member should be based on - /// - IMember CreateMember(string username, string email, string name, string memberTypeAlias); + /// + /// Gets a list of paged objects + /// + /// An can be of type + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// + /// Search text filter + /// + IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = ""); - /// - /// Creates an object without persisting it - /// - /// This method is convenient for when you need to add properties to a new Member - /// before persisting it in order to limit the amount of times its saved. - /// Also note that the returned will not have an Id until its saved. - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// MemberType the Member should be based on - /// - IMember CreateMember(string username, string email, string name, IMemberType memberType); + /// + /// Creates an object without persisting it + /// + /// This method is convenient for when you need to add properties to a new Member + /// before persisting it in order to limit the amount of times its saved. + /// Also note that the returned will not have an Id until its saved. + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// Alias of the MemberType the Member should be based on + /// + IMember CreateMember(string username, string email, string name, string memberTypeAlias); - /// - /// Creates and persists a Member - /// - /// Using this method will persist the Member object before its returned - /// meaning that it will have an Id available (unlike the CreateMember method) - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// Alias of the MemberType the Member should be based on - /// - IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias); + /// + /// Creates an object without persisting it + /// + /// This method is convenient for when you need to add properties to a new Member + /// before persisting it in order to limit the amount of times its saved. + /// Also note that the returned will not have an Id until its saved. + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// MemberType the Member should be based on + /// + IMember CreateMember(string username, string email, string name, IMemberType memberType); - /// - /// Creates and persists a Member - /// - /// Using this method will persist the Member object before its returned - /// meaning that it will have an Id available (unlike the CreateMember method) - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// MemberType the Member should be based on - /// - IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType); - - /// - /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method - /// - /// This method exists so that Umbraco developers can use one entry point to create/update - /// Members if they choose to. - /// The Member to save the password for - /// The password to encrypt and save - void SavePassword(IMember member, string password); + /// + /// Creates and persists a Member + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// Alias of the MemberType the Member should be based on + /// + IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias); - /// - /// Gets the count of Members by an optional MemberType alias - /// - /// If no alias is supplied then the count for all Member will be returned - /// Optional alias for the MemberType when counting number of Members - /// with number of Members - int Count(string memberTypeAlias = null); + /// + /// Creates and persists a Member + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// MemberType the Member should be based on + /// + IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType); - /// - /// Checks if a Member with the id exists - /// - /// Id of the Member - /// True if the Member exists otherwise False - bool Exists(int id); - - /// - /// Gets a Member by the unique key - /// - /// The guid key corresponds to the unique id in the database - /// and the user id in the membership provider. - /// Id - /// - IMember GetByKey(Guid id); + /// + /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method + /// + /// This method exists so that Umbraco developers can use one entry point to create/update + /// Members if they choose to. + /// The Member to save the password for + /// The password to encrypt and save + void SavePassword(IMember member, string password); - /// - /// Gets a Member by its integer id - /// - /// Id - /// - IMember GetById(int id); + /// + /// Gets the count of Members by an optional MemberType alias + /// + /// If no alias is supplied then the count for all Member will be returned + /// Optional alias for the MemberType when counting number of Members + /// with number of Members + int Count(string memberTypeAlias = null); - /// - /// Gets all Members for the specified MemberType alias - /// - /// Alias of the MemberType - /// - IEnumerable GetMembersByMemberType(string memberTypeAlias); + /// + /// Checks if a Member with the id exists + /// + /// Id of the Member + /// True if the Member exists otherwise False + bool Exists(int id); - /// - /// Gets all Members for the MemberType id - /// - /// Id of the MemberType - /// - IEnumerable GetMembersByMemberType(int memberTypeId); + /// + /// Gets a Member by the unique key + /// + /// The guid key corresponds to the unique id in the database + /// and the user id in the membership provider. + /// Id + /// + IMember GetByKey(Guid id); - /// - /// Gets all Members within the specified MemberGroup name - /// - /// Name of the MemberGroup - /// - IEnumerable GetMembersByGroup(string memberGroupName); + /// + /// Gets a Member by its integer id + /// + /// Id + /// + IMember GetById(int id); - /// - /// Gets all Members with the ids specified - /// - /// If no Ids are specified all Members will be retrieved - /// Optional list of Member Ids - /// - IEnumerable GetAllMembers(params int[] ids); - - /// - /// Delete Members of the specified MemberType id - /// - /// Id of the MemberType - void DeleteMembersOfType(int memberTypeId); + /// + /// Gets all Members for the specified MemberType alias + /// + /// Alias of the MemberType + /// + IEnumerable GetMembersByMemberType(string memberTypeAlias); - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable FindMembersByDisplayName(string displayNameToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + /// + /// Gets all Members for the MemberType id + /// + /// Id of the MemberType + /// + IEnumerable GetMembersByMemberType(int memberTypeId); - /// - /// Finds Members based on their display name - /// - /// Display name to match - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// The type of match to make as . Default is - /// - IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + /// + /// Gets all Members within the specified MemberGroup name + /// + /// Name of the MemberGroup + /// + IEnumerable GetMembersByGroup(string memberGroupName); - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// The type of match to make as . Default is - /// - IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact); + /// + /// Gets all Members with the ids specified + /// + /// If no Ids are specified all Members will be retrieved + /// Optional list of Member Ids + /// + IEnumerable GetAllMembers(params int[] ids); - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// The type of match to make as . Default is - /// - IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact); + /// + /// Delete Members of the specified MemberType id + /// + /// Id of the MemberType + void DeleteMembersOfType(int memberTypeId); - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// - IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, bool value); + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + IEnumerable FindMembersByDisplayName(string displayNameToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// The type of match to make as . Default is - /// - IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact); - } + /// + /// Finds Members based on their display name + /// + /// Display name to match + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// The type of match to make as . Default is + /// + IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// The type of match to make as . Default is + /// + IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact); + + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// The type of match to make as . Default is + /// + IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact); + + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// + IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, bool value); + + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// The type of match to make as . Default is + /// + IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index accbebfe0b..5af8487b3f 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -22,1316 +22,1318 @@ using Umbraco.Core.Publishing; namespace Umbraco.Core.Services { - /// - /// Represents the Media Service, which is an easy access to operations involving - /// - public class MediaService : RepositoryService, IMediaService, IMediaServiceOperations - { - - //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); - - private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); - private readonly IDataTypeService _dataTypeService; - private readonly IUserService _userService; - - public MediaService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IDataTypeService dataTypeService, IUserService userService) - : base(provider, repositoryFactory, logger, eventMessagesFactory) - { - if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); - if (userService == null) throw new ArgumentNullException("userService"); - _dataTypeService = dataTypeService; - _userService = userService; - } - - /// - /// Creates an object using the alias of the - /// that this Media should based on. - /// - /// - /// Note that using this method will simply return a new IMedia without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects - /// that does not invoke a save operation against the database. - /// - /// Name of the Media object - /// Id of Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// - public IMedia CreateMedia(string name, int parentId, string mediaTypeAlias, int userId = 0) - { - var mediaType = FindMediaTypeByAlias(mediaTypeAlias); - var media = new Models.Media(name, parentId, mediaType); - var parent = GetById(media.ParentId); - media.Path = string.Concat(parent.IfNotNull(x => x.Path, media.ParentId.ToString()), ",", media.Id); - - if (Creating.IsRaisedEventCancelled(new NewEventArgs(media, mediaTypeAlias, parentId), this)) - { - media.WasCancelled = true; - return media; - } - - media.CreatorId = userId; - - Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parentId), this); - - Audit(AuditType.New, string.Format("Media '{0}' was created", name), media.CreatorId, media.Id); - - return media; - } - - /// - /// Creates an object using the alias of the - /// that this Media should based on. - /// - /// - /// Note that using this method will simply return a new IMedia without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects - /// that does not invoke a save operation against the database. - /// - /// Name of the Media object - /// Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// - public IMedia CreateMedia(string name, IMedia parent, string mediaTypeAlias, int userId = 0) - { - if (parent == null) throw new ArgumentNullException("parent"); - - var mediaType = FindMediaTypeByAlias(mediaTypeAlias); - var media = new Models.Media(name, parent, mediaType); - media.Path = string.Concat(parent.Path, ",", media.Id); - - if (Creating.IsRaisedEventCancelled(new NewEventArgs(media, mediaTypeAlias, parent), this)) - { - media.WasCancelled = true; - return media; - } - - media.CreatorId = userId; - - Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parent), this); - - Audit(AuditType.New, string.Format("Media '{0}' was created", name), media.CreatorId, media.Id); - - return media; - } - - /// - /// Creates an object using the alias of the - /// that this Media should based on. - /// - /// - /// This method returns an object that has been persisted to the database - /// and therefor has an identity. - /// - /// Name of the Media object - /// Id of Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// - public IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = 0) - { - var mediaType = FindMediaTypeByAlias(mediaTypeAlias); - var media = new Models.Media(name, parentId, mediaType); - - //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(media, mediaTypeAlias, parentId), this)) - { - media.WasCancelled = true; - return media; - } - - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(media), this)) - { - media.WasCancelled = true; - return media; - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - media.CreatorId = userId; - repository.AddOrUpdate(media); - - repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - } - - uow.Commit(); - } - - Saved.RaiseEvent(new SaveEventArgs(media, false), this); - - Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parentId), this); - - Audit(AuditType.New, string.Format("Media '{0}' was created with Id {1}", name, media.Id), media.CreatorId, media.Id); - - return media; - } - - /// - /// Creates an object using the alias of the - /// that this Media should based on. - /// - /// - /// This method returns an object that has been persisted to the database - /// and therefor has an identity. - /// - /// Name of the Media object - /// Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// - public IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = 0) - { - if (parent == null) throw new ArgumentNullException("parent"); - - var mediaType = FindMediaTypeByAlias(mediaTypeAlias); - var media = new Models.Media(name, parent, mediaType); - - //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(media, mediaTypeAlias, parent), this)) - { - media.WasCancelled = true; - return media; - } - - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(media), this)) - { - media.WasCancelled = true; - return media; - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - media.CreatorId = userId; - repository.AddOrUpdate(media); - repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - } - - uow.Commit(); - } - - Saved.RaiseEvent(new SaveEventArgs(media, false), this); - - Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parent), this); - - Audit(AuditType.New, string.Format("Media '{0}' was created with Id {1}", name, media.Id), media.CreatorId, media.Id); - - return media; - } - - /// - /// Gets an object by Id - /// - /// Id of the Content to retrieve - /// - public IMedia GetById(int id) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - return repository.Get(id); - } - } - - public int Count(string contentTypeAlias = null) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - return repository.Count(contentTypeAlias); - } - } - - public int CountChildren(int parentId, string contentTypeAlias = null) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - return repository.CountChildren(parentId, contentTypeAlias); - } - } - - public int CountDescendants(int parentId, string contentTypeAlias = null) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - return repository.CountDescendants(parentId, contentTypeAlias); - } - } - - /// - /// Gets an object by Id - /// - /// Ids of the Media to retrieve - /// - public IEnumerable GetByIds(IEnumerable ids) - { - if (ids.Any() == false) return Enumerable.Empty(); - - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - return repository.GetAll(ids.ToArray()); - } - } - - /// - /// Gets an object by its 'UniqueId' - /// - /// Guid key of the Media to retrieve - /// - public IMedia GetById(Guid key) - { - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Key == key); - var contents = repository.GetByQuery(query); - return contents.SingleOrDefault(); - } - } - - /// - /// Gets a collection of objects by Level - /// - /// The level to retrieve Media from - /// An Enumerable list of objects - public IEnumerable GetByLevel(int level) - { - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Level == level && !x.Path.StartsWith("-21")); - var contents = repository.GetByQuery(query); - - return contents; - } - } - - /// - /// Gets a specific version of an item. - /// - /// Id of the version to retrieve - /// An item - public IMedia GetByVersion(Guid versionId) - { - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - return repository.GetByVersion(versionId); - } - } - - /// - /// Gets a collection of an objects versions by Id - /// - /// - /// An Enumerable list of objects - public IEnumerable GetVersions(int id) - { - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - var versions = repository.GetAllVersions(id); - return versions; - } - } - - /// - /// Gets a collection of objects, which are ancestors of the current media. - /// - /// Id of the to retrieve ancestors for - /// An Enumerable list of objects - public IEnumerable GetAncestors(int id) - { - var media = GetById(id); - return GetAncestors(media); - } - - /// - /// Gets a collection of objects, which are ancestors of the current media. - /// - /// to retrieve ancestors for - /// An Enumerable list of objects - public IEnumerable GetAncestors(IMedia media) - { - var ids = media.Path.Split(',').Where(x => x != "-1" && x != media.Id.ToString(CultureInfo.InvariantCulture)).Select(int.Parse).ToArray(); - if (ids.Any() == false) - return new List(); - - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - return repository.GetAll(ids); - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// An Enumerable list of objects - public IEnumerable GetChildren(int id) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - var query = Query.Builder.Where(x => x.ParentId == id); - var medias = repository.GetByQuery(query); - - return medias; - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, - string orderBy, Direction orderDirection, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder; - query.Where(x => x.ParentId == id); - - long total; - var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); - - totalChildren = Convert.ToInt32(total); - return medias; - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page index (zero based) - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects - public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - string orderBy, Direction orderDirection, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder; - query.Where(x => x.ParentId == id); - - var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, filter); - - return medias; - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - - var query = Query.Builder; - //if the id is -1, then just get all - if (id != -1) - { - query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); - } - long total; - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); - totalChildren = Convert.ToInt32(total); - return contents; - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Search text filter - /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - - var query = Query.Builder; - //if the id is -1, then just get all - if (id != -1) - { - query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); - } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, filter); - - return contents; - } - } - - /// - /// Gets descendants of a object by its Id - /// - /// Id of the Parent to retrieve descendants from - /// An Enumerable flat list of objects - public IEnumerable GetDescendants(int id) - { - var media = GetById(id); - if (media == null) - { - return Enumerable.Empty(); - } - return GetDescendants(media); - } - - /// - /// Gets descendants of a object by its Id - /// - /// The Parent object to retrieve descendants from - /// An Enumerable flat list of objects - public IEnumerable GetDescendants(IMedia media) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - var pathMatch = media.Path + ","; - var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != media.Id); - var medias = repository.GetByQuery(query); - - return medias; - } - } - - /// - /// Gets the parent of the current media as an item. - /// - /// Id of the to retrieve the parent from - /// Parent object - public IMedia GetParent(int id) - { - var media = GetById(id); - return GetParent(media); - } - - /// - /// Gets the parent of the current media as an item. - /// - /// to retrieve the parent from - /// Parent object - public IMedia GetParent(IMedia media) - { - if (media.ParentId == -1 || media.ParentId == -21) - return null; - - return GetById(media.ParentId); - } - - /// - /// Gets a collection of objects by the Id of the - /// - /// Id of the - /// An Enumerable list of objects - public IEnumerable GetMediaOfMediaType(int id) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - var query = Query.Builder.Where(x => x.ContentTypeId == id); - var medias = repository.GetByQuery(query); - - return medias; - } - } - - /// - /// Gets a collection of objects, which reside at the first level / root - /// - /// An Enumerable list of objects - public IEnumerable GetRootMedia() - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - var query = Query.Builder.Where(x => x.ParentId == -1); - var medias = repository.GetByQuery(query); - - return medias; - } - } - - /// - /// Gets a collection of an objects, which resides in the Recycle Bin - /// - /// An Enumerable list of objects - public IEnumerable GetMediaInRecycleBin() - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - var query = Query.Builder.Where(x => x.Path.Contains("-21")); - var medias = repository.GetByQuery(query); - - return medias; - } - } - - /// - /// Gets an object from the path stored in the 'umbracoFile' property. - /// - /// Path of the media item to retrieve (for example: /media/1024/koala_403x328.jpg) - /// - public IMedia GetMediaByPath(string mediaPath) - { - var umbracoFileValue = mediaPath; - 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) - { - var underscoreIndex = mediaPath.LastIndexOf('_'); - var dotIndex = mediaPath.LastIndexOf('.'); - umbracoFileValue = string.Concat(mediaPath.Substring(0, underscoreIndex), mediaPath.Substring(dotIndex)); - } - - Func createSql = url => new Sql().Select("*") - .From() - .InnerJoin() - .On(left => left.PropertyTypeId, right => right.Id) - .Where(x => x.Alias == "umbracoFile") - .Where(x => x.VarChar == url); - - var sql = createSql(umbracoFileValue); - - using (var uow = UowProvider.GetUnitOfWork()) - { - var propertyDataDto = uow.Database.Fetch(sql).FirstOrDefault(); - - // If the stripped-down url returns null, we try again with the original url. - // Previously, the function would fail on e.g. "my_x_image.jpg" - if (propertyDataDto == null) - { - sql = createSql(mediaPath); - propertyDataDto = uow.Database.Fetch(sql).FirstOrDefault(); - } - - return propertyDataDto == null ? null : GetById(propertyDataDto.NodeId); - } - } - - /// - /// Checks whether an item has any children - /// - /// Id of the - /// True if the media has any children otherwise False - public bool HasChildren(int id) - { - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.ParentId == id); - int count = repository.Count(query); - return count > 0; - } - } - - /// - /// Moves an object to a new location - /// - /// The to move - /// Id of the Media's new Parent - /// Id of the User moving the Media - public void Move(IMedia media, int parentId, int userId = 0) - { - //TODO: This all needs to be on the repo layer in one transaction! - - if (media == null) throw new ArgumentNullException("media"); - - using (new WriteLock(Locker)) - { - //This ensures that the correct method is called if this method is used to Move to recycle bin. - if (parentId == -21) - { - MoveToRecycleBin(media, userId); - return; - } - - var originalPath = media.Path; - - if (Moving.IsRaisedEventCancelled( - new MoveEventArgs( - new MoveEventInfo(media, originalPath, parentId)), this)) - { - return; - } - - media.ParentId = parentId; - if (media.Trashed) - { - media.ChangeTrashedState(false, parentId); - } - Save(media, userId, - //no events! - false); - - //used to track all the moved entities to be given to the event - var moveInfo = new List> - { - new MoveEventInfo(media, originalPath, parentId) - }; - - //Ensure that relevant properties are updated on children - var children = GetChildren(media.Id).ToArray(); - if (children.Any()) - { - var parentPath = media.Path; - var parentLevel = media.Level; - var parentTrashed = media.Trashed; - var updatedDescendants = UpdatePropertiesOnChildren(children, parentPath, parentLevel, parentTrashed, moveInfo); - Save(updatedDescendants, userId, - //no events! - false); - } - - Moved.RaiseEvent(new MoveEventArgs(false, moveInfo.ToArray()), this); - - Audit(AuditType.Move, "Move Media performed by user", userId, media.Id); - } - } - - /// - /// Deletes an object by moving it to the Recycle Bin - /// - /// The to delete - /// Id of the User deleting the Media - public void MoveToRecycleBin(IMedia media, int userId = 0) - { - ((IMediaServiceOperations) this).MoveToRecycleBin(media, userId); - } - - /// - /// Permanently deletes an object - /// - /// - /// Please note that this method will completely remove the Media from the database, - /// but current not from the file system. - /// - /// The to delete - /// Id of the User deleting the Media - Attempt IMediaServiceOperations.Delete(IMedia media, int userId) - { - //TODO: IT would be much nicer to mass delete all in one trans in the repo level! - var evtMsgs = EventMessagesFactory.Get(); - - if (Deleting.IsRaisedEventCancelled( - new DeleteEventArgs(media, evtMsgs), this)) - { - return OperationStatus.Cancelled(evtMsgs); - } - - //Delete children before deleting the 'possible parent' - var children = GetChildren(media.Id); - foreach (var child in children) - { - Delete(child, userId); - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - repository.Delete(media); - uow.Commit(); - - var args = new DeleteEventArgs(media, false, evtMsgs); - Deleted.RaiseEvent(args, this); - - //remove any flagged media files - repository.DeleteMediaFiles(args.MediaFilesToDelete); - } - - Audit(AuditType.Delete, "Delete Media performed by user", userId, media.Id); - - return OperationStatus.Success(evtMsgs); - } - - /// - /// Saves a single object - /// - /// The to save - /// Id of the User saving the Media - /// Optional boolean indicating whether or not to raise events. - Attempt IMediaServiceOperations.Save(IMedia media, int userId, bool raiseEvents) - { - var evtMsgs = EventMessagesFactory.Get(); - - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled( - new SaveEventArgs(media, evtMsgs), - this)) - { - return OperationStatus.Cancelled(evtMsgs); - } - - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - media.CreatorId = userId; - repository.AddOrUpdate(media); - repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - } - - uow.Commit(); - } - - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(media, false, evtMsgs), this); - - Audit(AuditType.Save, "Save Media performed by user", userId, media.Id); - - return OperationStatus.Success(evtMsgs); - } - - /// - /// Saves a collection of objects - /// - /// Collection of to save - /// Id of the User saving the Media - /// Optional boolean indicating whether or not to raise events. - Attempt IMediaServiceOperations.Save(IEnumerable medias, int userId, bool raiseEvents) - { - var asArray = medias.ToArray(); - var evtMsgs = EventMessagesFactory.Get(); - - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled( - new SaveEventArgs(asArray, evtMsgs), - this)) - { - return OperationStatus.Cancelled(evtMsgs); - } - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - foreach (var media in asArray) - { - media.CreatorId = userId; - repository.AddOrUpdate(media); - repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - } - } - - //commit the whole lot in one go - uow.Commit(); - } - - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(asArray, false, evtMsgs), this); - - Audit(AuditType.Save, "Save Media items performed by user", userId, -1); - - return OperationStatus.Success(evtMsgs); - } - - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - public void EmptyRecycleBin() - { - using (new WriteLock(Locker)) - { - Dictionary> entities; - List files; - bool success; - var nodeObjectType = new Guid(Constants.ObjectTypes.Media); - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - //Create a dictionary of ids -> dictionary of property aliases + values - entities = repository.GetEntitiesInRecycleBin() - .ToDictionary( - key => key.Id, - val => (IEnumerable)val.Properties); - - files = ((MediaRepository)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); - } - } - Audit(AuditType.Delete, "Empty Media Recycle Bin performed by user", 0, -21); - } - - /// - /// Deletes all media of specified type. All children of deleted media 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 deleting the media - public void DeleteMediaOfType(int mediaTypeId, int userId = 0) - { - //TODO: This all needs to be done on the repo level in one trans - - using (new WriteLock(Locker)) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - //NOTE What about media that has the contenttype as part of its composition? - //The ContentType has to be removed from the composition somehow as it would otherwise break - //Dbl.check+test that the ContentType's Id is removed from the ContentType2ContentType table - var query = Query.Builder.Where(x => x.ContentTypeId == mediaTypeId); - 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 = Query.Builder.Where(x => x.Path.StartsWith(c.Path)); - var children = repository.GetByQuery(childQuery); - - foreach (var child in children) - { - if (child.ContentType.Id != mediaTypeId) - MoveToRecycleBin(child, userId); - } - - //Permanently delete the content - Delete(content, userId); - } - } - - Audit(AuditType.Delete, "Delete Media items by Type performed by user", userId, -1); - } - } - - /// - /// Deletes an object by moving it to the Recycle Bin - /// - /// The to delete - /// Id of the User deleting the Media - Attempt IMediaServiceOperations.MoveToRecycleBin(IMedia media, int userId) - { - if (media == null) throw new ArgumentNullException("media"); - - var originalPath = media.Path; - - var evtMsgs = EventMessagesFactory.Get(); - - if (Trashing.IsRaisedEventCancelled( - new MoveEventArgs(new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia)), this)) - { - return OperationStatus.Cancelled(evtMsgs); - } - - var moveInfo = new List> - { - new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia) - }; - - //Find Descendants, which will be moved to the recycle bin along with the parent/grandparent. - var descendants = GetDescendants(media).OrderBy(x => x.Level).ToList(); - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - //TODO: This should be part of the repo! - - //Remove 'published' xml from the cmsContentXml table for the unpublished media - uow.Database.Delete("WHERE nodeId = @Id", new { Id = media.Id }); - - media.ChangeTrashedState(true, Constants.System.RecycleBinMedia); - repository.AddOrUpdate(media); - - //Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId - foreach (var descendant in descendants) - { - //Remove 'published' xml from the cmsContentXml table for the unpublished media - uow.Database.Delete("WHERE nodeId = @Id", new { Id = descendant.Id }); - - descendant.ChangeTrashedState(true, descendant.ParentId); - repository.AddOrUpdate(descendant); - - moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); - } - - uow.Commit(); - } - - Trashed.RaiseEvent( - new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), this); - - Audit(AuditType.Move, "Move Media to Recycle Bin performed by user", userId, media.Id); - - return OperationStatus.Success(evtMsgs); - } - - /// - /// Permanently deletes an object as well as all of its Children. - /// - /// - /// Please note that this method will completely remove the Media from the database, - /// as well as associated media files from the file system. - /// - /// The to delete - /// Id of the User deleting the Media - public void Delete(IMedia media, int userId = 0) - { - ((IMediaServiceOperations)this).Delete(media, userId); - } - - - - /// - /// Permanently deletes versions from an object prior to a specific date. - /// This method will never delete the latest version of a content item. - /// - /// Id of the object to delete versions from - /// Latest version date - /// Optional Id of the User deleting versions of a Content object - public void DeleteVersions(int id, DateTime versionDate, int userId = 0) - { - if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, dateToRetain: versionDate), this)) - return; - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - repository.DeleteVersions(id, versionDate); - uow.Commit(); - } - - DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate), this); - - Audit(AuditType.Delete, "Delete Media by version date performed by user", userId, -1); - } - - /// - /// Permanently deletes specific version(s) from an object. - /// This method will never delete the latest version of a content item. - /// - /// Id of the object to delete a version from - /// Id of the version to delete - /// Boolean indicating whether to delete versions prior to the versionId - /// Optional Id of the User deleting versions of a Content object - public void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0) - { - if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, specificVersion: versionId), this)) - return; - - if (deletePriorVersions) - { - var content = GetByVersion(versionId); - DeleteVersions(id, content.UpdateDate, userId); - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - repository.DeleteVersion(versionId); - uow.Commit(); - } - - DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), this); - - Audit(AuditType.Delete, "Delete Media by version performed by user", userId, -1); - } - - /// - /// Saves a single object - /// - /// The to save - /// Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - public void Save(IMedia media, int userId = 0, bool raiseEvents = true) - { - ((IMediaServiceOperations)this).Save (media, userId, raiseEvents); - } - - /// - /// Saves a collection of objects - /// - /// Collection of to save - /// Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - public void Save(IEnumerable medias, int userId = 0, bool raiseEvents = true) - { - ((IMediaServiceOperations)this).Save(medias, userId, raiseEvents); - } - - /// - /// Sorts a collection of objects by updating the SortOrder according - /// to the ordering of items in the passed in . - /// - /// - /// - /// - /// True if sorting succeeded, otherwise False - public bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true) - { - var asArray = items.ToArray(); - - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(asArray), this)) - return false; - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - int i = 0; - foreach (var media in asArray) - { - //If the current sort order equals that of the media - //we don't need to update it, so just increment the sort order - //and continue. - if (media.SortOrder == i) - { - i++; - continue; - } - - media.SortOrder = i; - i++; - - repository.AddOrUpdate(media); - repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - } - } - - uow.Commit(); - } - - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(asArray, false), this); - - Audit(AuditType.Sort, "Sorting Media performed by user", userId, 0); - - return true; - } - - /// - /// Rebuilds all xml content in the cmsContentXml table for all media - /// - /// - /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures - /// for all media - /// - public void RebuildXmlStructures(params int[] contentTypeIds) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - repository.RebuildXmlStructures( - media => _entitySerializer.Serialize(this, _dataTypeService, _userService, media), - contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds); - } - - Audit(AuditType.Publish, "MediaService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1); - } - - /// - /// Updates the Path and Level on a collection of objects - /// based on the Parent's Path and Level. Also change the trashed state if relevant. - /// - /// Collection of objects to update - /// Path of the Parent media - /// Level of the Parent media - /// Indicates whether the Parent is trashed or not - /// Used to track the objects to be used in the move event - /// Collection of updated objects - private IEnumerable UpdatePropertiesOnChildren(IEnumerable children, string parentPath, int parentLevel, bool parentTrashed, ICollection> eventInfo) - { - var list = new List(); - foreach (var child in children) - { - var originalPath = child.Path; - child.Path = string.Concat(parentPath, ",", child.Id); - child.Level = parentLevel + 1; - if (parentTrashed != child.Trashed) - { - child.ChangeTrashedState(parentTrashed, child.ParentId); - } - - eventInfo.Add(new MoveEventInfo(child, originalPath, child.ParentId)); - list.Add(child); - - var grandkids = GetChildren(child.Id).ToArray(); - if (grandkids.Any()) - { - list.AddRange(UpdatePropertiesOnChildren(grandkids, child.Path, child.Level, child.Trashed, eventInfo)); - } - } - return list; - } - - //private void CreateAndSaveMediaXml(XElement xml, int id, UmbracoDatabase db) - //{ - // var poco = new ContentXmlDto { NodeId = id, Xml = xml.ToDataString() }; - // var exists = db.FirstOrDefault("WHERE nodeId = @Id", new { Id = id }) != null; - // int result = exists ? db.Update(poco) : Convert.ToInt32(db.Insert(poco)); - //} - - private IMediaType FindMediaTypeByAlias(string mediaTypeAlias) - { - Mandate.ParameterNotNullOrEmpty(mediaTypeAlias, "mediaTypeAlias"); - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaTypeRepository(uow)) - { - var query = Query.Builder.Where(x => x.Alias == mediaTypeAlias); - var mediaTypes = repository.GetByQuery(query); - - if (mediaTypes.Any() == false) - throw new Exception(string.Format("No MediaType matching the passed in Alias: '{0}' was found", - mediaTypeAlias)); - - var mediaType = mediaTypes.First(); - - if (mediaType == null) - throw new Exception(string.Format("MediaType matching the passed in Alias: '{0}' was null", - mediaTypeAlias)); - - return mediaType; - } - } - - private void Audit(AuditType type, string message, int userId, int objectId) - { - var uow = UowProvider.GetUnitOfWork(); - using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) - { - auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); - uow.Commit(); - } - } - - #region Event Handlers - - /// - /// Occurs before Delete - /// - public static event TypedEventHandler DeletingVersions; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler DeletedVersions; - - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> Deleting; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> Deleted; - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> Saving; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> Saved; - - /// - /// Occurs before Create - /// - [Obsolete("Use the Created event instead, the Creating and Created events both offer the same functionality, Creating event has been deprecated.")] - public static event TypedEventHandler> Creating; - - /// - /// Occurs after Create - /// - /// - /// Please note that the Media object has been created, but not saved - /// so it does not have an identity yet (meaning no Id has been set). - /// - public static event TypedEventHandler> Created; - - /// - /// Occurs before Content is moved to Recycle Bin - /// - public static event TypedEventHandler> Trashing; - - /// - /// Occurs after Content is moved to Recycle Bin - /// - public static event TypedEventHandler> Trashed; - - /// - /// Occurs before Move - /// - public static event TypedEventHandler> Moving; - - /// - /// Occurs after Move - /// - public static event TypedEventHandler> Moved; - - /// - /// Occurs before the Recycle Bin is emptied - /// - public static event TypedEventHandler EmptyingRecycleBin; - - /// - /// Occurs after the Recycle Bin has been Emptied - /// - public static event TypedEventHandler EmptiedRecycleBin; - #endregion - } + /// + /// Represents the Media Service, which is an easy access to operations involving + /// + public class MediaService : RepositoryService, IMediaService, IMediaServiceOperations + { + + //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); + + private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); + private readonly IDataTypeService _dataTypeService; + private readonly IUserService _userService; + + public MediaService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IDataTypeService dataTypeService, IUserService userService) + : base(provider, repositoryFactory, logger, eventMessagesFactory) + { + if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); + if (userService == null) throw new ArgumentNullException("userService"); + _dataTypeService = dataTypeService; + _userService = userService; + } + + /// + /// Creates an object using the alias of the + /// that this Media should based on. + /// + /// + /// Note that using this method will simply return a new IMedia without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects + /// that does not invoke a save operation against the database. + /// + /// Name of the Media object + /// Id of Parent for the new Media item + /// Alias of the + /// Optional id of the user creating the media item + /// + public IMedia CreateMedia(string name, int parentId, string mediaTypeAlias, int userId = 0) + { + var mediaType = FindMediaTypeByAlias(mediaTypeAlias); + var media = new Models.Media(name, parentId, mediaType); + var parent = GetById(media.ParentId); + media.Path = string.Concat(parent.IfNotNull(x => x.Path, media.ParentId.ToString()), ",", media.Id); + + if (Creating.IsRaisedEventCancelled(new NewEventArgs(media, mediaTypeAlias, parentId), this)) + { + media.WasCancelled = true; + return media; + } + + media.CreatorId = userId; + + Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parentId), this); + + Audit(AuditType.New, string.Format("Media '{0}' was created", name), media.CreatorId, media.Id); + + return media; + } + + /// + /// Creates an object using the alias of the + /// that this Media should based on. + /// + /// + /// Note that using this method will simply return a new IMedia without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects + /// that does not invoke a save operation against the database. + /// + /// Name of the Media object + /// Parent for the new Media item + /// Alias of the + /// Optional id of the user creating the media item + /// + public IMedia CreateMedia(string name, IMedia parent, string mediaTypeAlias, int userId = 0) + { + if (parent == null) throw new ArgumentNullException("parent"); + + var mediaType = FindMediaTypeByAlias(mediaTypeAlias); + var media = new Models.Media(name, parent, mediaType); + media.Path = string.Concat(parent.Path, ",", media.Id); + + if (Creating.IsRaisedEventCancelled(new NewEventArgs(media, mediaTypeAlias, parent), this)) + { + media.WasCancelled = true; + return media; + } + + media.CreatorId = userId; + + Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parent), this); + + Audit(AuditType.New, string.Format("Media '{0}' was created", name), media.CreatorId, media.Id); + + return media; + } + + /// + /// Creates an object using the alias of the + /// that this Media should based on. + /// + /// + /// This method returns an object that has been persisted to the database + /// and therefor has an identity. + /// + /// Name of the Media object + /// Id of Parent for the new Media item + /// Alias of the + /// Optional id of the user creating the media item + /// + public IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = 0) + { + var mediaType = FindMediaTypeByAlias(mediaTypeAlias); + var media = new Models.Media(name, parentId, mediaType); + + //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(media, mediaTypeAlias, parentId), this)) + { + media.WasCancelled = true; + return media; + } + + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(media), this)) + { + media.WasCancelled = true; + return media; + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + media.CreatorId = userId; + repository.AddOrUpdate(media); + + repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + { + repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + } + + uow.Commit(); + } + + Saved.RaiseEvent(new SaveEventArgs(media, false), this); + + Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parentId), this); + + Audit(AuditType.New, string.Format("Media '{0}' was created with Id {1}", name, media.Id), media.CreatorId, media.Id); + + return media; + } + + /// + /// Creates an object using the alias of the + /// that this Media should based on. + /// + /// + /// This method returns an object that has been persisted to the database + /// and therefor has an identity. + /// + /// Name of the Media object + /// Parent for the new Media item + /// Alias of the + /// Optional id of the user creating the media item + /// + public IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = 0) + { + if (parent == null) throw new ArgumentNullException("parent"); + + var mediaType = FindMediaTypeByAlias(mediaTypeAlias); + var media = new Models.Media(name, parent, mediaType); + + //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(media, mediaTypeAlias, parent), this)) + { + media.WasCancelled = true; + return media; + } + + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(media), this)) + { + media.WasCancelled = true; + return media; + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + media.CreatorId = userId; + repository.AddOrUpdate(media); + repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + { + repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + } + + uow.Commit(); + } + + Saved.RaiseEvent(new SaveEventArgs(media, false), this); + + Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parent), this); + + Audit(AuditType.New, string.Format("Media '{0}' was created with Id {1}", name, media.Id), media.CreatorId, media.Id); + + return media; + } + + /// + /// Gets an object by Id + /// + /// Id of the Content to retrieve + /// + public IMedia GetById(int id) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + return repository.Get(id); + } + } + + public int Count(string contentTypeAlias = null) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + return repository.Count(contentTypeAlias); + } + } + + public int CountChildren(int parentId, string contentTypeAlias = null) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + return repository.CountChildren(parentId, contentTypeAlias); + } + } + + public int CountDescendants(int parentId, string contentTypeAlias = null) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + return repository.CountDescendants(parentId, contentTypeAlias); + } + } + + /// + /// Gets an object by Id + /// + /// Ids of the Media to retrieve + /// + public IEnumerable GetByIds(IEnumerable ids) + { + if (ids.Any() == false) return Enumerable.Empty(); + + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetAll(ids.ToArray()); + } + } + + /// + /// Gets an object by its 'UniqueId' + /// + /// Guid key of the Media to retrieve + /// + public IMedia GetById(Guid key) + { + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Key == key); + var contents = repository.GetByQuery(query); + return contents.SingleOrDefault(); + } + } + + /// + /// Gets a collection of objects by Level + /// + /// The level to retrieve Media from + /// An Enumerable list of objects + public IEnumerable GetByLevel(int level) + { + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Level == level && !x.Path.StartsWith("-21")); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + /// + /// Gets a specific version of an item. + /// + /// Id of the version to retrieve + /// An item + public IMedia GetByVersion(Guid versionId) + { + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetByVersion(versionId); + } + } + + /// + /// Gets a collection of an objects versions by Id + /// + /// + /// An Enumerable list of objects + public IEnumerable GetVersions(int id) + { + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + var versions = repository.GetAllVersions(id); + return versions; + } + } + + /// + /// Gets a collection of objects, which are ancestors of the current media. + /// + /// Id of the to retrieve ancestors for + /// An Enumerable list of objects + public IEnumerable GetAncestors(int id) + { + var media = GetById(id); + return GetAncestors(media); + } + + /// + /// Gets a collection of objects, which are ancestors of the current media. + /// + /// to retrieve ancestors for + /// An Enumerable list of objects + public IEnumerable GetAncestors(IMedia media) + { + var ids = media.Path.Split(',').Where(x => x != "-1" && x != media.Id.ToString(CultureInfo.InvariantCulture)).Select(int.Parse).ToArray(); + if (ids.Any() == false) + return new List(); + + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetAll(ids); + } + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// An Enumerable list of objects + public IEnumerable GetChildren(int id) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + var query = Query.Builder.Where(x => x.ParentId == id); + var medias = repository.GetByQuery(query); + + return medias; + } + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, + string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder; + query.Where(x => x.ParentId == id); + + long total; + var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, orderBySystemField, filter); + + totalChildren = Convert.ToInt32(total); + return medias; + } + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page index (zero based) + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, + string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder; + query.Where(x => x.ParentId == id); + + var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + + return medias; + } + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + + var query = Query.Builder; + //if the id is -1, then just get all + if (id != -1) + { + query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); + } + long total; + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, orderBySystemField, filter); + totalChildren = Convert.ToInt32(total); + return contents; + } + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + + var query = Query.Builder; + //if the id is -1, then just get all + if (id != -1) + { + query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); + } + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + + return contents; + } + } + + /// + /// Gets descendants of a object by its Id + /// + /// Id of the Parent to retrieve descendants from + /// An Enumerable flat list of objects + public IEnumerable GetDescendants(int id) + { + var media = GetById(id); + if (media == null) + { + return Enumerable.Empty(); + } + return GetDescendants(media); + } + + /// + /// Gets descendants of a object by its Id + /// + /// The Parent object to retrieve descendants from + /// An Enumerable flat list of objects + public IEnumerable GetDescendants(IMedia media) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + var pathMatch = media.Path + ","; + var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != media.Id); + var medias = repository.GetByQuery(query); + + return medias; + } + } + + /// + /// Gets the parent of the current media as an item. + /// + /// Id of the to retrieve the parent from + /// Parent object + public IMedia GetParent(int id) + { + var media = GetById(id); + return GetParent(media); + } + + /// + /// Gets the parent of the current media as an item. + /// + /// to retrieve the parent from + /// Parent object + public IMedia GetParent(IMedia media) + { + if (media.ParentId == -1 || media.ParentId == -21) + return null; + + return GetById(media.ParentId); + } + + /// + /// Gets a collection of objects by the Id of the + /// + /// Id of the + /// An Enumerable list of objects + public IEnumerable GetMediaOfMediaType(int id) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + var query = Query.Builder.Where(x => x.ContentTypeId == id); + var medias = repository.GetByQuery(query); + + return medias; + } + } + + /// + /// Gets a collection of objects, which reside at the first level / root + /// + /// An Enumerable list of objects + public IEnumerable GetRootMedia() + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + var query = Query.Builder.Where(x => x.ParentId == -1); + var medias = repository.GetByQuery(query); + + return medias; + } + } + + /// + /// Gets a collection of an objects, which resides in the Recycle Bin + /// + /// An Enumerable list of objects + public IEnumerable GetMediaInRecycleBin() + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + var query = Query.Builder.Where(x => x.Path.Contains("-21")); + var medias = repository.GetByQuery(query); + + return medias; + } + } + + /// + /// Gets an object from the path stored in the 'umbracoFile' property. + /// + /// Path of the media item to retrieve (for example: /media/1024/koala_403x328.jpg) + /// + public IMedia GetMediaByPath(string mediaPath) + { + var umbracoFileValue = mediaPath; + 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) + { + var underscoreIndex = mediaPath.LastIndexOf('_'); + var dotIndex = mediaPath.LastIndexOf('.'); + umbracoFileValue = string.Concat(mediaPath.Substring(0, underscoreIndex), mediaPath.Substring(dotIndex)); + } + + Func createSql = url => new Sql().Select("*") + .From() + .InnerJoin() + .On(left => left.PropertyTypeId, right => right.Id) + .Where(x => x.Alias == "umbracoFile") + .Where(x => x.VarChar == url); + + var sql = createSql(umbracoFileValue); + + using (var uow = UowProvider.GetUnitOfWork()) + { + var propertyDataDto = uow.Database.Fetch(sql).FirstOrDefault(); + + // If the stripped-down url returns null, we try again with the original url. + // Previously, the function would fail on e.g. "my_x_image.jpg" + if (propertyDataDto == null) + { + sql = createSql(mediaPath); + propertyDataDto = uow.Database.Fetch(sql).FirstOrDefault(); + } + + return propertyDataDto == null ? null : GetById(propertyDataDto.NodeId); + } + } + + /// + /// Checks whether an item has any children + /// + /// Id of the + /// True if the media has any children otherwise False + public bool HasChildren(int id) + { + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ParentId == id); + int count = repository.Count(query); + return count > 0; + } + } + + /// + /// Moves an object to a new location + /// + /// The to move + /// Id of the Media's new Parent + /// Id of the User moving the Media + public void Move(IMedia media, int parentId, int userId = 0) + { + //TODO: This all needs to be on the repo layer in one transaction! + + if (media == null) throw new ArgumentNullException("media"); + + using (new WriteLock(Locker)) + { + //This ensures that the correct method is called if this method is used to Move to recycle bin. + if (parentId == -21) + { + MoveToRecycleBin(media, userId); + return; + } + + var originalPath = media.Path; + + if (Moving.IsRaisedEventCancelled( + new MoveEventArgs( + new MoveEventInfo(media, originalPath, parentId)), this)) + { + return; + } + + media.ParentId = parentId; + if (media.Trashed) + { + media.ChangeTrashedState(false, parentId); + } + Save(media, userId, + //no events! + false); + + //used to track all the moved entities to be given to the event + var moveInfo = new List> + { + new MoveEventInfo(media, originalPath, parentId) + }; + + //Ensure that relevant properties are updated on children + var children = GetChildren(media.Id).ToArray(); + if (children.Any()) + { + var parentPath = media.Path; + var parentLevel = media.Level; + var parentTrashed = media.Trashed; + var updatedDescendants = UpdatePropertiesOnChildren(children, parentPath, parentLevel, parentTrashed, moveInfo); + Save(updatedDescendants, userId, + //no events! + false); + } + + Moved.RaiseEvent(new MoveEventArgs(false, moveInfo.ToArray()), this); + + Audit(AuditType.Move, "Move Media performed by user", userId, media.Id); + } + } + + /// + /// Deletes an object by moving it to the Recycle Bin + /// + /// The to delete + /// Id of the User deleting the Media + public void MoveToRecycleBin(IMedia media, int userId = 0) + { + ((IMediaServiceOperations)this).MoveToRecycleBin(media, userId); + } + + /// + /// Permanently deletes an object + /// + /// + /// Please note that this method will completely remove the Media from the database, + /// but current not from the file system. + /// + /// The to delete + /// Id of the User deleting the Media + Attempt IMediaServiceOperations.Delete(IMedia media, int userId) + { + //TODO: IT would be much nicer to mass delete all in one trans in the repo level! + var evtMsgs = EventMessagesFactory.Get(); + + if (Deleting.IsRaisedEventCancelled( + new DeleteEventArgs(media, evtMsgs), this)) + { + return OperationStatus.Cancelled(evtMsgs); + } + + //Delete children before deleting the 'possible parent' + var children = GetChildren(media.Id); + foreach (var child in children) + { + Delete(child, userId); + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + repository.Delete(media); + uow.Commit(); + + var args = new DeleteEventArgs(media, false, evtMsgs); + Deleted.RaiseEvent(args, this); + + //remove any flagged media files + repository.DeleteMediaFiles(args.MediaFilesToDelete); + } + + Audit(AuditType.Delete, "Delete Media performed by user", userId, media.Id); + + return OperationStatus.Success(evtMsgs); + } + + /// + /// Saves a single object + /// + /// The to save + /// Id of the User saving the Media + /// Optional boolean indicating whether or not to raise events. + Attempt IMediaServiceOperations.Save(IMedia media, int userId, bool raiseEvents) + { + var evtMsgs = EventMessagesFactory.Get(); + + if (raiseEvents) + { + if (Saving.IsRaisedEventCancelled( + new SaveEventArgs(media, evtMsgs), + this)) + { + return OperationStatus.Cancelled(evtMsgs); + } + + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + media.CreatorId = userId; + repository.AddOrUpdate(media); + repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + { + repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + } + + uow.Commit(); + } + + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(media, false, evtMsgs), this); + + Audit(AuditType.Save, "Save Media performed by user", userId, media.Id); + + return OperationStatus.Success(evtMsgs); + } + + /// + /// Saves a collection of objects + /// + /// Collection of to save + /// Id of the User saving the Media + /// Optional boolean indicating whether or not to raise events. + Attempt IMediaServiceOperations.Save(IEnumerable medias, int userId, bool raiseEvents) + { + var asArray = medias.ToArray(); + var evtMsgs = EventMessagesFactory.Get(); + + if (raiseEvents) + { + if (Saving.IsRaisedEventCancelled( + new SaveEventArgs(asArray, evtMsgs), + this)) + { + return OperationStatus.Cancelled(evtMsgs); + } + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + foreach (var media in asArray) + { + media.CreatorId = userId; + repository.AddOrUpdate(media); + repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + { + repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + } + } + + //commit the whole lot in one go + uow.Commit(); + } + + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(asArray, false, evtMsgs), this); + + Audit(AuditType.Save, "Save Media items performed by user", userId, -1); + + return OperationStatus.Success(evtMsgs); + } + + /// + /// Empties the Recycle Bin by deleting all that resides in the bin + /// + public void EmptyRecycleBin() + { + using (new WriteLock(Locker)) + { + Dictionary> entities; + List files; + bool success; + var nodeObjectType = new Guid(Constants.ObjectTypes.Media); + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + //Create a dictionary of ids -> dictionary of property aliases + values + entities = repository.GetEntitiesInRecycleBin() + .ToDictionary( + key => key.Id, + val => (IEnumerable)val.Properties); + + files = ((MediaRepository)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); + } + } + Audit(AuditType.Delete, "Empty Media Recycle Bin performed by user", 0, -21); + } + + /// + /// Deletes all media of specified type. All children of deleted media 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 deleting the media + public void DeleteMediaOfType(int mediaTypeId, int userId = 0) + { + //TODO: This all needs to be done on the repo level in one trans + + using (new WriteLock(Locker)) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + //NOTE What about media that has the contenttype as part of its composition? + //The ContentType has to be removed from the composition somehow as it would otherwise break + //Dbl.check+test that the ContentType's Id is removed from the ContentType2ContentType table + var query = Query.Builder.Where(x => x.ContentTypeId == mediaTypeId); + 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 = Query.Builder.Where(x => x.Path.StartsWith(c.Path)); + var children = repository.GetByQuery(childQuery); + + foreach (var child in children) + { + if (child.ContentType.Id != mediaTypeId) + MoveToRecycleBin(child, userId); + } + + //Permanently delete the content + Delete(content, userId); + } + } + + Audit(AuditType.Delete, "Delete Media items by Type performed by user", userId, -1); + } + } + + /// + /// Deletes an object by moving it to the Recycle Bin + /// + /// The to delete + /// Id of the User deleting the Media + Attempt IMediaServiceOperations.MoveToRecycleBin(IMedia media, int userId) + { + if (media == null) throw new ArgumentNullException("media"); + + var originalPath = media.Path; + + var evtMsgs = EventMessagesFactory.Get(); + + if (Trashing.IsRaisedEventCancelled( + new MoveEventArgs(new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia)), this)) + { + return OperationStatus.Cancelled(evtMsgs); + } + + var moveInfo = new List> + { + new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia) + }; + + //Find Descendants, which will be moved to the recycle bin along with the parent/grandparent. + var descendants = GetDescendants(media).OrderBy(x => x.Level).ToList(); + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + //TODO: This should be part of the repo! + + //Remove 'published' xml from the cmsContentXml table for the unpublished media + uow.Database.Delete("WHERE nodeId = @Id", new { Id = media.Id }); + + media.ChangeTrashedState(true, Constants.System.RecycleBinMedia); + repository.AddOrUpdate(media); + + //Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId + foreach (var descendant in descendants) + { + //Remove 'published' xml from the cmsContentXml table for the unpublished media + uow.Database.Delete("WHERE nodeId = @Id", new { Id = descendant.Id }); + + descendant.ChangeTrashedState(true, descendant.ParentId); + repository.AddOrUpdate(descendant); + + moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); + } + + uow.Commit(); + } + + Trashed.RaiseEvent( + new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), this); + + Audit(AuditType.Move, "Move Media to Recycle Bin performed by user", userId, media.Id); + + return OperationStatus.Success(evtMsgs); + } + + /// + /// Permanently deletes an object as well as all of its Children. + /// + /// + /// Please note that this method will completely remove the Media from the database, + /// as well as associated media files from the file system. + /// + /// The to delete + /// Id of the User deleting the Media + public void Delete(IMedia media, int userId = 0) + { + ((IMediaServiceOperations)this).Delete(media, userId); + } + + + + /// + /// Permanently deletes versions from an object prior to a specific date. + /// This method will never delete the latest version of a content item. + /// + /// Id of the object to delete versions from + /// Latest version date + /// Optional Id of the User deleting versions of a Content object + public void DeleteVersions(int id, DateTime versionDate, int userId = 0) + { + if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, dateToRetain: versionDate), this)) + return; + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + repository.DeleteVersions(id, versionDate); + uow.Commit(); + } + + DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate), this); + + Audit(AuditType.Delete, "Delete Media by version date performed by user", userId, -1); + } + + /// + /// Permanently deletes specific version(s) from an object. + /// This method will never delete the latest version of a content item. + /// + /// Id of the object to delete a version from + /// Id of the version to delete + /// Boolean indicating whether to delete versions prior to the versionId + /// Optional Id of the User deleting versions of a Content object + public void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0) + { + if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, specificVersion: versionId), this)) + return; + + if (deletePriorVersions) + { + var content = GetByVersion(versionId); + DeleteVersions(id, content.UpdateDate, userId); + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + repository.DeleteVersion(versionId); + uow.Commit(); + } + + DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), this); + + Audit(AuditType.Delete, "Delete Media by version performed by user", userId, -1); + } + + /// + /// Saves a single object + /// + /// The to save + /// Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + public void Save(IMedia media, int userId = 0, bool raiseEvents = true) + { + ((IMediaServiceOperations)this).Save(media, userId, raiseEvents); + } + + /// + /// Saves a collection of objects + /// + /// Collection of to save + /// Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + public void Save(IEnumerable medias, int userId = 0, bool raiseEvents = true) + { + ((IMediaServiceOperations)this).Save(medias, userId, raiseEvents); + } + + /// + /// Sorts a collection of objects by updating the SortOrder according + /// to the ordering of items in the passed in . + /// + /// + /// + /// + /// True if sorting succeeded, otherwise False + public bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true) + { + var asArray = items.ToArray(); + + if (raiseEvents) + { + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(asArray), this)) + return false; + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + int i = 0; + foreach (var media in asArray) + { + //If the current sort order equals that of the media + //we don't need to update it, so just increment the sort order + //and continue. + if (media.SortOrder == i) + { + i++; + continue; + } + + media.SortOrder = i; + i++; + + repository.AddOrUpdate(media); + repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + { + repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + } + } + + uow.Commit(); + } + + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(asArray, false), this); + + Audit(AuditType.Sort, "Sorting Media performed by user", userId, 0); + + return true; + } + + /// + /// Rebuilds all xml content in the cmsContentXml table for all media + /// + /// + /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures + /// for all media + /// + public void RebuildXmlStructures(params int[] contentTypeIds) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + repository.RebuildXmlStructures( + media => _entitySerializer.Serialize(this, _dataTypeService, _userService, media), + contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds); + } + + Audit(AuditType.Publish, "MediaService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1); + } + + /// + /// Updates the Path and Level on a collection of objects + /// based on the Parent's Path and Level. Also change the trashed state if relevant. + /// + /// Collection of objects to update + /// Path of the Parent media + /// Level of the Parent media + /// Indicates whether the Parent is trashed or not + /// Used to track the objects to be used in the move event + /// Collection of updated objects + private IEnumerable UpdatePropertiesOnChildren(IEnumerable children, string parentPath, int parentLevel, bool parentTrashed, ICollection> eventInfo) + { + var list = new List(); + foreach (var child in children) + { + var originalPath = child.Path; + child.Path = string.Concat(parentPath, ",", child.Id); + child.Level = parentLevel + 1; + if (parentTrashed != child.Trashed) + { + child.ChangeTrashedState(parentTrashed, child.ParentId); + } + + eventInfo.Add(new MoveEventInfo(child, originalPath, child.ParentId)); + list.Add(child); + + var grandkids = GetChildren(child.Id).ToArray(); + if (grandkids.Any()) + { + list.AddRange(UpdatePropertiesOnChildren(grandkids, child.Path, child.Level, child.Trashed, eventInfo)); + } + } + return list; + } + + //private void CreateAndSaveMediaXml(XElement xml, int id, UmbracoDatabase db) + //{ + // var poco = new ContentXmlDto { NodeId = id, Xml = xml.ToDataString() }; + // var exists = db.FirstOrDefault("WHERE nodeId = @Id", new { Id = id }) != null; + // int result = exists ? db.Update(poco) : Convert.ToInt32(db.Insert(poco)); + //} + + private IMediaType FindMediaTypeByAlias(string mediaTypeAlias) + { + Mandate.ParameterNotNullOrEmpty(mediaTypeAlias, "mediaTypeAlias"); + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaTypeRepository(uow)) + { + var query = Query.Builder.Where(x => x.Alias == mediaTypeAlias); + var mediaTypes = repository.GetByQuery(query); + + if (mediaTypes.Any() == false) + throw new Exception(string.Format("No MediaType matching the passed in Alias: '{0}' was found", + mediaTypeAlias)); + + var mediaType = mediaTypes.First(); + + if (mediaType == null) + throw new Exception(string.Format("MediaType matching the passed in Alias: '{0}' was null", + mediaTypeAlias)); + + return mediaType; + } + } + + private void Audit(AuditType type, string message, int userId, int objectId) + { + var uow = UowProvider.GetUnitOfWork(); + using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) + { + auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); + uow.Commit(); + } + } + + #region Event Handlers + + /// + /// Occurs before Delete + /// + public static event TypedEventHandler DeletingVersions; + + /// + /// Occurs after Delete + /// + public static event TypedEventHandler DeletedVersions; + + /// + /// Occurs before Delete + /// + public static event TypedEventHandler> Deleting; + + /// + /// Occurs after Delete + /// + public static event TypedEventHandler> Deleted; + + /// + /// Occurs before Save + /// + public static event TypedEventHandler> Saving; + + /// + /// Occurs after Save + /// + public static event TypedEventHandler> Saved; + + /// + /// Occurs before Create + /// + [Obsolete("Use the Created event instead, the Creating and Created events both offer the same functionality, Creating event has been deprecated.")] + public static event TypedEventHandler> Creating; + + /// + /// Occurs after Create + /// + /// + /// Please note that the Media object has been created, but not saved + /// so it does not have an identity yet (meaning no Id has been set). + /// + public static event TypedEventHandler> Created; + + /// + /// Occurs before Content is moved to Recycle Bin + /// + public static event TypedEventHandler> Trashing; + + /// + /// Occurs after Content is moved to Recycle Bin + /// + public static event TypedEventHandler> Trashed; + + /// + /// Occurs before Move + /// + public static event TypedEventHandler> Moving; + + /// + /// Occurs after Move + /// + public static event TypedEventHandler> Moved; + + /// + /// Occurs before the Recycle Bin is emptied + /// + public static event TypedEventHandler EmptyingRecycleBin; + + /// + /// Occurs after the Recycle Bin has been Emptied + /// + public static event TypedEventHandler EmptiedRecycleBin; + #endregion + } } diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 4f0647dbf7..6216bdfbe9 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -21,1296 +21,1296 @@ using Umbraco.Core.Security; namespace Umbraco.Core.Services { - /// - /// Represents the MemberService. - /// - public class MemberService : RepositoryService, IMemberService - { - private readonly IMemberGroupService _memberGroupService; - private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); - private readonly IDataTypeService _dataTypeService; - private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); - - public MemberService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService, IDataTypeService dataTypeService) - : base(provider, repositoryFactory, logger, eventMessagesFactory) - { - if (memberGroupService == null) throw new ArgumentNullException("memberGroupService"); - if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); - _memberGroupService = memberGroupService; - _dataTypeService = dataTypeService; - } - - #region IMemberService Implementation - - /// - /// Gets the default MemberType alias - /// - /// By default we'll return the 'writer', but we need to check it exists. If it doesn't we'll - /// return the first type that is not an admin, otherwise if there's only one we will return that one. - /// Alias of the default MemberType - public string GetDefaultMemberType() - { - using (var repository = RepositoryFactory.CreateMemberTypeRepository(UowProvider.GetUnitOfWork())) - { - var types = repository.GetAll(new int[]{}).Select(x => x.Alias).ToArray(); - - if (types.Any() == false) - { - throw new InvalidOperationException("No member types could be resolved"); - } - - if (types.InvariantContains("Member")) - { - return types.First(x => x.InvariantEquals("Member")); - } - - return types.First(); - } - } - - /// - /// Checks if a Member with the username exists - /// - /// Username to check - /// True if the Member exists otherwise False - public bool Exists(string username) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - return repository.Exists(username); - } - } - - /// - /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method - /// - /// This method exists so that Umbraco developers can use one entry point to create/update - /// Members if they choose to. - /// The Member to save the password for - /// The password to encrypt and save - public void SavePassword(IMember member, string password) - { - if (member == null) throw new ArgumentNullException("member"); - - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - if (provider.IsUmbracoMembershipProvider()) - { - provider.ChangePassword(member.Username, "", password); - } - else - { - throw new NotSupportedException("When using a non-Umbraco membership provider you must change the member password by using the MembershipProvider.ChangePassword method"); - } - - //go re-fetch the member and update the properties that may have changed - var result = GetByUsername(member.Username); - - //should never be null but it could have been deleted by another thread. - if (result == null) - return; - - member.RawPasswordValue = result.RawPasswordValue; - member.LastPasswordChangeDate = result.LastPasswordChangeDate; - member.UpdateDate = result.UpdateDate; - } - - /// - /// Checks if a Member with the id exists - /// - /// Id of the Member - /// True if the Member exists otherwise False - public bool Exists(int id) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - return repository.Exists(id); - } - } - - /// - /// Gets a Member by its integer id - /// - /// Id - /// - public IMember GetById(int id) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - return repository.Get(id); - } - } - - /// - /// Gets a Member by the unique key - /// - /// The guid key corresponds to the unique id in the database - /// and the user id in the membership provider. - /// Id - /// - public IMember GetByKey(Guid id) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Key == id); - var member = repository.GetByQuery(query).FirstOrDefault(); - return member; - } - } - - /// - /// Gets all Members for the specified MemberType alias - /// - /// Alias of the MemberType - /// - public IEnumerable GetMembersByMemberType(string memberTypeAlias) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.ContentTypeAlias == memberTypeAlias); - var members = repository.GetByQuery(query); - return members; - } - } - - /// - /// Gets all Members for the MemberType id - /// - /// Id of the MemberType - /// - public IEnumerable GetMembersByMemberType(int memberTypeId) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - repository.Get(memberTypeId); - var query = Query.Builder.Where(x => x.ContentTypeId == memberTypeId); - var members = repository.GetByQuery(query); - return members; - } - } - - /// - /// Gets all Members within the specified MemberGroup name - /// - /// Name of the MemberGroup - /// - public IEnumerable GetMembersByGroup(string memberGroupName) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - return repository.GetByMemberGroup(memberGroupName); - } - } - - /// - /// Gets all Members with the ids specified - /// - /// If no Ids are specified all Members will be retrieved - /// Optional list of Member Ids - /// - public IEnumerable GetAllMembers(params int[] ids) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - return repository.GetAll(ids); - } - } - - /// - /// Delete Members of the specified MemberType id - /// - /// Id of the MemberType - public void DeleteMembersOfType(int memberTypeId) - { - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateMemberRepository(uow); - //TODO: What about content that has the contenttype as part of its composition? - var query = Query.Builder.Where(x => x.ContentTypeId == memberTypeId); - var members = repository.GetByQuery(query).ToArray(); - - if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(members), this)) - return; - - foreach (var member in members) - { - //Permantly delete the member - Delete(member); - } - } - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable FindMembersByDisplayName(string displayNameToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - long total; - var result = FindMembersByDisplayName(displayNameToMatch, Convert.ToInt64(pageIndex), pageSize, out total, matchType); - totalRecords = Convert.ToInt32(total); - return result; - } - - /// - /// Finds Members based on their display name - /// - /// Display name to match - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// The type of match to make as . Default is - /// - public IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - var query = new Query(); - - switch (matchType) - { - case StringPropertyMatchType.Exact: - query.Where(member => member.Name.Equals(displayNameToMatch)); - break; - case StringPropertyMatchType.Contains: - query.Where(member => member.Name.Contains(displayNameToMatch)); - break; - case StringPropertyMatchType.StartsWith: - query.Where(member => member.Name.StartsWith(displayNameToMatch)); - break; - case StringPropertyMatchType.EndsWith: - query.Where(member => member.Name.EndsWith(displayNameToMatch)); - break; - case StringPropertyMatchType.Wildcard: - query.Where(member => member.Name.SqlWildcard(displayNameToMatch, TextColumnType.NVarchar)); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); - } - - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Name", Direction.Ascending); - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable FindByEmail(string emailStringToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - long total; - var result = FindByEmail(emailStringToMatch, Convert.ToInt64(pageIndex), pageSize, out total, matchType); - totalRecords = Convert.ToInt32(total); - return result; - } - - /// - /// Finds a list of objects by a partial email string - /// - /// Partial email string to match - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// The type of match to make as . Default is - /// - public IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - var query = new Query(); - - switch (matchType) - { - case StringPropertyMatchType.Exact: - query.Where(member => member.Email.Equals(emailStringToMatch)); - break; - case StringPropertyMatchType.Contains: - query.Where(member => member.Email.Contains(emailStringToMatch)); - break; - case StringPropertyMatchType.StartsWith: - query.Where(member => member.Email.StartsWith(emailStringToMatch)); - break; - case StringPropertyMatchType.EndsWith: - query.Where(member => member.Email.EndsWith(emailStringToMatch)); - break; - case StringPropertyMatchType.Wildcard: - query.Where(member => member.Email.SqlWildcard(emailStringToMatch, TextColumnType.NVarchar)); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); - } - - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Email", Direction.Ascending); - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable FindByUsername(string login, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - long total; - var result = FindByUsername(login, Convert.ToInt64(pageIndex), pageSize, out total, matchType); - totalRecords = Convert.ToInt32(total); - return result; - } - - /// - /// Finds a list of objects by a partial username - /// - /// Partial username to match - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// The type of match to make as . Default is - /// - public IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - var query = new Query(); - - switch (matchType) - { - case StringPropertyMatchType.Exact: - query.Where(member => member.Username.Equals(login)); - break; - case StringPropertyMatchType.Contains: - query.Where(member => member.Username.Contains(login)); - break; - case StringPropertyMatchType.StartsWith: - query.Where(member => member.Username.StartsWith(login)); - break; - case StringPropertyMatchType.EndsWith: - query.Where(member => member.Username.EndsWith(login)); - break; - case StringPropertyMatchType.Wildcard: - query.Where(member => member.Email.SqlWildcard(login, TextColumnType.NVarchar)); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); - } - - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending); - } - } - - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// The type of match to make as . Default is - /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - IQuery query; - - switch (matchType) - { - case StringPropertyMatchType.Exact: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - (((Member)x).LongStringPropertyValue.SqlEquals(value, TextColumnType.NText) || - ((Member)x).ShortStringPropertyValue.SqlEquals(value, TextColumnType.NVarchar))); - break; - case StringPropertyMatchType.Contains: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - (((Member)x).LongStringPropertyValue.SqlContains(value, TextColumnType.NText) || - ((Member)x).ShortStringPropertyValue.SqlContains(value, TextColumnType.NVarchar))); - break; - case StringPropertyMatchType.StartsWith: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - (((Member)x).LongStringPropertyValue.SqlStartsWith(value, TextColumnType.NText) || - ((Member)x).ShortStringPropertyValue.SqlStartsWith(value, TextColumnType.NVarchar))); - break; - case StringPropertyMatchType.EndsWith: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - (((Member)x).LongStringPropertyValue.SqlEndsWith(value, TextColumnType.NText) || - ((Member)x).ShortStringPropertyValue.SqlEndsWith(value, TextColumnType.NVarchar))); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); - } - - var members = repository.GetByQuery(query); - return members; - } - } - - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// The type of match to make as . Default is - /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - IQuery query; - - switch (matchType) - { - case ValuePropertyMatchType.Exact: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerPropertyValue == value); - break; - case ValuePropertyMatchType.GreaterThan: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerPropertyValue > value); - break; - case ValuePropertyMatchType.LessThan: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerPropertyValue < value); - break; - case ValuePropertyMatchType.GreaterThanOrEqualTo: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerPropertyValue >= value); - break; - case ValuePropertyMatchType.LessThanOrEqualTo: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerPropertyValue <= value); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); - } - - var members = repository.GetByQuery(query); - return members; - } - } - - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, bool value) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - var query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).BoolPropertyValue == value); - - var members = repository.GetByQuery(query); - return members; - } - } - - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// The type of match to make as . Default is - /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - IQuery query; - - switch (matchType) - { - case ValuePropertyMatchType.Exact: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue == value); - break; - case ValuePropertyMatchType.GreaterThan: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue > value); - break; - case ValuePropertyMatchType.LessThan: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue < value); - break; - case ValuePropertyMatchType.GreaterThanOrEqualTo: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue >= value); - break; - case ValuePropertyMatchType.LessThanOrEqualTo: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue <= value); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); - } - - //TODO: Since this is by property value, we need a GetByPropertyQuery on the repo! - var members = repository.GetByQuery(query); - return members; - } - } - - /// - /// Rebuilds all xml content in the cmsContentXml table for all members - /// - /// - /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures - /// for all members = USE WITH CARE! - /// - /// True if publishing succeeded, otherwise False - public void RebuildXmlStructures(params int[] memberTypeIds) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - repository.RebuildXmlStructures( - member => _entitySerializer.Serialize(_dataTypeService, member), - contentTypeIds: memberTypeIds.Length == 0 ? null : memberTypeIds); - } - - Audit(AuditType.Publish, "MemberService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1); - } - - #endregion - - #region IMembershipMemberService Implementation - - /// - /// Gets the total number of Members based on the count type - /// - /// - /// The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members - /// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science - /// but that is how MS have made theirs so we'll follow that principal. - /// - /// to count by - /// with number of Members for passed in type - public int GetCount(MemberCountType countType) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - IQuery query; - - switch (countType) - { - case MemberCountType.All: - query = new Query(); - return repository.Count(query); - case MemberCountType.Online: - var fromDate = DateTime.Now.AddMinutes(-Membership.UserIsOnlineTimeWindow); - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.LastLoginDate && - ((Member)x).DateTimePropertyValue > fromDate); - return repository.GetCountByQuery(query); - case MemberCountType.LockedOut: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.IsLockedOut && - ((Member)x).BoolPropertyValue == true); - return repository.GetCountByQuery(query); - case MemberCountType.Approved: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.IsApproved && - ((Member)x).BoolPropertyValue == true); - return repository.GetCountByQuery(query); - default: - throw new ArgumentOutOfRangeException("countType"); - } - } - - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords) - { - long total; - var result = GetAll(Convert.ToInt64(pageIndex), pageSize, out total); - totalRecords = Convert.ToInt32(total); - return result; - } - - /// - /// Gets a list of paged objects - /// - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// - public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending); - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords, - string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = "") - { - long total; - var result = GetAll(Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, memberTypeAlias, filter); - totalRecords = Convert.ToInt32(total); - return result; - } - - public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = "") - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - if (memberTypeAlias == null) - { - return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filter); - } - var query = new Query().Where(x => x.ContentTypeAlias == memberTypeAlias); - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filter); - } - } - - /// - /// Gets the count of Members by an optional MemberType alias - /// - /// If no alias is supplied then the count for all Member will be returned - /// Optional alias for the MemberType when counting number of Members - /// with number of Members - public int Count(string memberTypeAlias = null) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - return repository.Count(memberTypeAlias); - } - } - - /// - /// Creates an object without persisting it - /// - /// This method is convenient for when you need to add properties to a new Member - /// before persisting it in order to limit the amount of times its saved. - /// Also note that the returned will not have an Id until its saved. - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// Alias of the MemberType the Member should be based on - /// - public IMember CreateMember(string username, string email, string name, string memberTypeAlias) - { - var memberType = FindMemberTypeByAlias(memberTypeAlias); - return CreateMember(username, email, name, memberType); - } - - /// - /// Creates an object without persisting it - /// - /// This method is convenient for when you need to add properties to a new Member - /// before persisting it in order to limit the amount of times its saved. - /// Also note that the returned will not have an Id until its saved. - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// MemberType the Member should be based on - /// - public IMember CreateMember(string username, string email, string name, IMemberType memberType) - { - var member = new Member(name, email.ToLower().Trim(), username, memberType); - - Created.RaiseEvent(new NewEventArgs(member, false, memberType.Alias, -1), this); - - return member; - } - - /// - /// Creates and persists a Member - /// - /// Using this method will persist the Member object before its returned - /// meaning that it will have an Id available (unlike the CreateMember method) - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// Alias of the MemberType the Member should be based on - /// - public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias) - { - var memberType = FindMemberTypeByAlias(memberTypeAlias); - return CreateMemberWithIdentity(username, email, name, memberType); - } - - /// - /// Creates and persists a Member - /// - /// Using this method will persist the Member object before its returned - /// meaning that it will have an Id available (unlike the CreateMember method) - /// Username of the Member to create - /// Email of the Member to create - /// MemberType the Member should be based on - /// - public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType) - { - return CreateMemberWithIdentity(username, email, username, memberType); - } - - /// - /// Creates and persists a Member - /// - /// Using this method will persist the Member object before its returned - /// meaning that it will have an Id available (unlike the CreateMember method) - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// MemberType the Member should be based on - /// - public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType) - { - return CreateMemberWithIdentity(username, email, name, "", memberType); - } - - /// - /// Creates and persists a new - /// - /// An can be of type or - /// Username of the to create - /// Email of the to create - /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database - /// Alias of the Type - /// - IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias) - { - var memberType = FindMemberTypeByAlias(memberTypeAlias); - return CreateMemberWithIdentity(username, email, username, passwordValue, memberType); - } - - /// - /// Creates and persists a Member - /// - /// Using this method will persist the Member object before its returned - /// meaning that it will have an Id available (unlike the CreateMember method) - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database - /// MemberType the Member should be based on - /// - private IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, IMemberType memberType) - { - if (memberType == null) throw new ArgumentNullException("memberType"); - - var member = new Member(name, email.ToLower().Trim(), username, passwordValue, memberType); - - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(member), this)) - { - member.WasCancelled = true; - return member; - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - repository.AddOrUpdate(member); - //insert the xml - repository.AddOrUpdateContentXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); - } - - uow.Commit(); - } - - Saved.RaiseEvent(new SaveEventArgs(member, false), this); - Created.RaiseEvent(new NewEventArgs(member, false, memberType.Alias, -1), this); - - return member; - } - - /// - /// Gets an by its provider key - /// - /// Id to use for retrieval - /// - public IMember GetByProviderKey(object id) - { - var asGuid = id.TryConvertTo(); - if (asGuid.Success) - { - return GetByKey((Guid)id); - } - var asInt = id.TryConvertTo(); - if (asInt.Success) - { - return GetById((int)id); - } - - return null; - } - - /// - /// Get an by email - /// - /// Email to use for retrieval - /// - public IMember GetByEmail(string email) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - var query = Query.Builder.Where(x => x.Email.Equals(email)); - var member = repository.GetByQuery(query).FirstOrDefault(); - - return member; - } - } - - /// - /// Get an by username - /// - /// Username to use for retrieval - /// - public IMember GetByUsername(string username) - { - //TODO: Somewhere in here, whether at this level or the repository level, we need to add - // a caching mechanism since this method is used by all the membership providers and could be - // called quite a bit when dealing with members. - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - var query = Query.Builder.Where(x => x.Username.Equals(username)); - var member = repository.GetByQuery(query).FirstOrDefault(); - - return member; - } - } - - /// - /// Deletes an - /// - /// to Delete - public void Delete(IMember member) - { - if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(member), this)) - return; - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - repository.Delete(member); - uow.Commit(); - - var args = new DeleteEventArgs(member, false); - Deleted.RaiseEvent(args, this); - - //remove any flagged media files - repository.DeleteMediaFiles(args.MediaFilesToDelete); - } - } - - /// - /// Saves an - /// - /// to Save - /// Optional parameter to raise events. - /// Default is True otherwise set to False to not raise events - public void Save(IMember entity, bool raiseEvents = true) - { - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(entity), this)) - { - return; - } - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - repository.AddOrUpdate(entity); - repository.AddOrUpdateContentXml(entity, m => _entitySerializer.Serialize(_dataTypeService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(entity, m => _entitySerializer.Serialize(_dataTypeService, m)); - } - - uow.Commit(); - } - - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(entity, false), this); - } - - /// - /// Saves a list of objects - /// - /// to save - /// Optional parameter to raise events. - /// Default is True otherwise set to False to not raise events - public void Save(IEnumerable entities, bool raiseEvents = true) - { - var asArray = entities.ToArray(); - - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(asArray), this)) - return; - } - using (new WriteLock(Locker)) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - foreach (var member in asArray) - { - repository.AddOrUpdate(member); - repository.AddOrUpdateContentXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); - } - } - - //commit the whole lot in one go - uow.Commit(); - } - - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(asArray, false), this); - } - } - - #endregion - - #region IMembershipRoleService Implementation - - public void AddRole(string roleName) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) - { - repository.CreateIfNotExists(roleName); - } - } - - public IEnumerable GetAllRoles() - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) - { - var result = repository.GetAll(); - return result.Select(x => x.Name).Distinct(); - } - } - - public IEnumerable GetAllRoles(int memberId) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) - { - var result = repository.GetMemberGroupsForMember(memberId); - return result.Select(x => x.Name).Distinct(); - } - } - - public IEnumerable GetAllRoles(string username) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) - { - var result = repository.GetMemberGroupsForMember(username); - return result.Select(x => x.Name).Distinct(); - } - } - - public IEnumerable GetMembersInRole(string roleName) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - return repository.GetByMemberGroup(roleName); - } - } - - public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - return repository.FindMembersInRole(roleName, usernameToMatch, matchType); - } - } - - public bool DeleteRole(string roleName, bool throwIfBeingUsed) - { - using (new WriteLock(Locker)) - { - if (throwIfBeingUsed) - { - var inRole = GetMembersInRole(roleName); - if (inRole.Any()) - { - throw new InvalidOperationException("The role " + roleName + " is currently assigned to members"); - } - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) - { - var qry = new Query().Where(g => g.Name == roleName); - var found = repository.GetByQuery(qry).ToArray(); - - foreach (var memberGroup in found) - { - _memberGroupService.Delete(memberGroup); - } - return found.Any(); - } - } - } - public void AssignRole(string username, string roleName) - { - AssignRoles(new[] { username }, new[] { roleName }); - } - - public void AssignRoles(string[] usernames, string[] roleNames) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) - { - repository.AssignRoles(usernames, roleNames); - } - } - - public void DissociateRole(string username, string roleName) - { - DissociateRoles(new[] { username }, new[] { roleName }); - } - - public void DissociateRoles(string[] usernames, string[] roleNames) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) - { - repository.DissociateRoles(usernames, roleNames); - } - } - - public void AssignRole(int memberId, string roleName) - { - AssignRoles(new[] { memberId }, new[] { roleName }); - } - - public void AssignRoles(int[] memberIds, string[] roleNames) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) - { - repository.AssignRoles(memberIds, roleNames); - } - } - - public void DissociateRole(int memberId, string roleName) - { - DissociateRoles(new[] { memberId }, new[] { roleName }); - } - - public void DissociateRoles(int[] memberIds, string[] roleNames) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) - { - repository.DissociateRoles(memberIds, roleNames); - } - } - - - - #endregion - - private IMemberType FindMemberTypeByAlias(string memberTypeAlias) - { - using (var repository = RepositoryFactory.CreateMemberTypeRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Alias == memberTypeAlias); - var types = repository.GetByQuery(query); - - if (types.Any() == false) - throw new Exception( - string.Format("No MemberType matching the passed in Alias: '{0}' was found", - memberTypeAlias)); - - var contentType = types.First(); - - if (contentType == null) - throw new Exception(string.Format("MemberType matching the passed in Alias: '{0}' was null", - memberTypeAlias)); - - return contentType; - } - } - - private void Audit(AuditType type, string message, int userId, int objectId) - { - var uow = UowProvider.GetUnitOfWork(); - using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) - { - auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); - uow.Commit(); - } - } - - #region Event Handlers - - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> Deleting; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> Deleted; - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> Saving; - - /// - /// Occurs after Create - /// - /// - /// Please note that the Member object has been created, but might not have been saved - /// so it does not have an identity yet (meaning no Id has been set). - /// - public static event TypedEventHandler> Created; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> Saved; - - #endregion - - /// - /// A helper method that will create a basic/generic member for use with a generic membership provider - /// - /// - internal static IMember CreateGenericMembershipProviderMember(string name, string email, string username, string password) - { - var identity = int.MaxValue; - - var memType = new MemberType(-1); - var propGroup = new PropertyGroup - { - Name = "Membership", - Id = --identity - }; - propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext, Constants.Conventions.Member.Comments) - { - Name = Constants.Conventions.Member.CommentsLabel, - SortOrder = 0, - Id = --identity, - Key = identity.ToGuid() - }); - propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer, Constants.Conventions.Member.IsApproved) - { - Name = Constants.Conventions.Member.IsApprovedLabel, - SortOrder = 3, - Id = --identity, - Key = identity.ToGuid() - }); - propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer, Constants.Conventions.Member.IsLockedOut) - { - Name = Constants.Conventions.Member.IsLockedOutLabel, - SortOrder = 4, - Id = --identity, - Key = identity.ToGuid() - }); - propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastLockoutDate) - { - Name = Constants.Conventions.Member.LastLockoutDateLabel, - SortOrder = 5, - Id = --identity, - Key = identity.ToGuid() - }); - propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastLoginDate) - { - Name = Constants.Conventions.Member.LastLoginDateLabel, - SortOrder = 6, - Id = --identity, - Key = identity.ToGuid() - }); - propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastPasswordChangeDate) - { - Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, - SortOrder = 7, - Id = --identity, - Key = identity.ToGuid() - }); - - memType.PropertyGroups.Add(propGroup); - - var member = new Member(name, email, username, password, memType); - - //we've assigned ids to the property types and groups but we also need to assign fake ids to the properties themselves. - foreach (var property in member.Properties) - { - property.Id = --identity; - } - - return member; - } - } + /// + /// Represents the MemberService. + /// + public class MemberService : RepositoryService, IMemberService + { + private readonly IMemberGroupService _memberGroupService; + private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); + private readonly IDataTypeService _dataTypeService; + private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); + + public MemberService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService, IDataTypeService dataTypeService) + : base(provider, repositoryFactory, logger, eventMessagesFactory) + { + if (memberGroupService == null) throw new ArgumentNullException("memberGroupService"); + if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); + _memberGroupService = memberGroupService; + _dataTypeService = dataTypeService; + } + + #region IMemberService Implementation + + /// + /// Gets the default MemberType alias + /// + /// By default we'll return the 'writer', but we need to check it exists. If it doesn't we'll + /// return the first type that is not an admin, otherwise if there's only one we will return that one. + /// Alias of the default MemberType + public string GetDefaultMemberType() + { + using (var repository = RepositoryFactory.CreateMemberTypeRepository(UowProvider.GetUnitOfWork())) + { + var types = repository.GetAll(new int[] { }).Select(x => x.Alias).ToArray(); + + if (types.Any() == false) + { + throw new InvalidOperationException("No member types could be resolved"); + } + + if (types.InvariantContains("Member")) + { + return types.First(x => x.InvariantEquals("Member")); + } + + return types.First(); + } + } + + /// + /// Checks if a Member with the username exists + /// + /// Username to check + /// True if the Member exists otherwise False + public bool Exists(string username) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + return repository.Exists(username); + } + } + + /// + /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method + /// + /// This method exists so that Umbraco developers can use one entry point to create/update + /// Members if they choose to. + /// The Member to save the password for + /// The password to encrypt and save + public void SavePassword(IMember member, string password) + { + if (member == null) throw new ArgumentNullException("member"); + + var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); + if (provider.IsUmbracoMembershipProvider()) + { + provider.ChangePassword(member.Username, "", password); + } + else + { + throw new NotSupportedException("When using a non-Umbraco membership provider you must change the member password by using the MembershipProvider.ChangePassword method"); + } + + //go re-fetch the member and update the properties that may have changed + var result = GetByUsername(member.Username); + + //should never be null but it could have been deleted by another thread. + if (result == null) + return; + + member.RawPasswordValue = result.RawPasswordValue; + member.LastPasswordChangeDate = result.LastPasswordChangeDate; + member.UpdateDate = result.UpdateDate; + } + + /// + /// Checks if a Member with the id exists + /// + /// Id of the Member + /// True if the Member exists otherwise False + public bool Exists(int id) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + return repository.Exists(id); + } + } + + /// + /// Gets a Member by its integer id + /// + /// Id + /// + public IMember GetById(int id) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + return repository.Get(id); + } + } + + /// + /// Gets a Member by the unique key + /// + /// The guid key corresponds to the unique id in the database + /// and the user id in the membership provider. + /// Id + /// + public IMember GetByKey(Guid id) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Key == id); + var member = repository.GetByQuery(query).FirstOrDefault(); + return member; + } + } + + /// + /// Gets all Members for the specified MemberType alias + /// + /// Alias of the MemberType + /// + public IEnumerable GetMembersByMemberType(string memberTypeAlias) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ContentTypeAlias == memberTypeAlias); + var members = repository.GetByQuery(query); + return members; + } + } + + /// + /// Gets all Members for the MemberType id + /// + /// Id of the MemberType + /// + public IEnumerable GetMembersByMemberType(int memberTypeId) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + repository.Get(memberTypeId); + var query = Query.Builder.Where(x => x.ContentTypeId == memberTypeId); + var members = repository.GetByQuery(query); + return members; + } + } + + /// + /// Gets all Members within the specified MemberGroup name + /// + /// Name of the MemberGroup + /// + public IEnumerable GetMembersByGroup(string memberGroupName) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetByMemberGroup(memberGroupName); + } + } + + /// + /// Gets all Members with the ids specified + /// + /// If no Ids are specified all Members will be retrieved + /// Optional list of Member Ids + /// + public IEnumerable GetAllMembers(params int[] ids) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetAll(ids); + } + } + + /// + /// Delete Members of the specified MemberType id + /// + /// Id of the MemberType + public void DeleteMembersOfType(int memberTypeId) + { + using (new WriteLock(Locker)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateMemberRepository(uow); + //TODO: What about content that has the contenttype as part of its composition? + var query = Query.Builder.Where(x => x.ContentTypeId == memberTypeId); + var members = repository.GetByQuery(query).ToArray(); + + if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(members), this)) + return; + + foreach (var member in members) + { + //Permantly delete the member + Delete(member); + } + } + } + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable FindMembersByDisplayName(string displayNameToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + long total; + var result = FindMembersByDisplayName(displayNameToMatch, Convert.ToInt64(pageIndex), pageSize, out total, matchType); + totalRecords = Convert.ToInt32(total); + return result; + } + + /// + /// Finds Members based on their display name + /// + /// Display name to match + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// The type of match to make as . Default is + /// + public IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + var query = new Query(); + + switch (matchType) + { + case StringPropertyMatchType.Exact: + query.Where(member => member.Name.Equals(displayNameToMatch)); + break; + case StringPropertyMatchType.Contains: + query.Where(member => member.Name.Contains(displayNameToMatch)); + break; + case StringPropertyMatchType.StartsWith: + query.Where(member => member.Name.StartsWith(displayNameToMatch)); + break; + case StringPropertyMatchType.EndsWith: + query.Where(member => member.Name.EndsWith(displayNameToMatch)); + break; + case StringPropertyMatchType.Wildcard: + query.Where(member => member.Name.SqlWildcard(displayNameToMatch, TextColumnType.NVarchar)); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } + + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); + } + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable FindByEmail(string emailStringToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + long total; + var result = FindByEmail(emailStringToMatch, Convert.ToInt64(pageIndex), pageSize, out total, matchType); + totalRecords = Convert.ToInt32(total); + return result; + } + + /// + /// Finds a list of objects by a partial email string + /// + /// Partial email string to match + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// The type of match to make as . Default is + /// + public IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + var query = new Query(); + + switch (matchType) + { + case StringPropertyMatchType.Exact: + query.Where(member => member.Email.Equals(emailStringToMatch)); + break; + case StringPropertyMatchType.Contains: + query.Where(member => member.Email.Contains(emailStringToMatch)); + break; + case StringPropertyMatchType.StartsWith: + query.Where(member => member.Email.StartsWith(emailStringToMatch)); + break; + case StringPropertyMatchType.EndsWith: + query.Where(member => member.Email.EndsWith(emailStringToMatch)); + break; + case StringPropertyMatchType.Wildcard: + query.Where(member => member.Email.SqlWildcard(emailStringToMatch, TextColumnType.NVarchar)); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } + + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Email", Direction.Ascending, orderBySystemField: true); + } + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable FindByUsername(string login, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + long total; + var result = FindByUsername(login, Convert.ToInt64(pageIndex), pageSize, out total, matchType); + totalRecords = Convert.ToInt32(total); + return result; + } + + /// + /// Finds a list of objects by a partial username + /// + /// Partial username to match + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// The type of match to make as . Default is + /// + public IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + var query = new Query(); + + switch (matchType) + { + case StringPropertyMatchType.Exact: + query.Where(member => member.Username.Equals(login)); + break; + case StringPropertyMatchType.Contains: + query.Where(member => member.Username.Contains(login)); + break; + case StringPropertyMatchType.StartsWith: + query.Where(member => member.Username.StartsWith(login)); + break; + case StringPropertyMatchType.EndsWith: + query.Where(member => member.Username.EndsWith(login)); + break; + case StringPropertyMatchType.Wildcard: + query.Where(member => member.Email.SqlWildcard(login, TextColumnType.NVarchar)); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } + + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, orderBySystemField: true); + } + } + + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// The type of match to make as . Default is + /// + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + IQuery query; + + switch (matchType) + { + case StringPropertyMatchType.Exact: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + (((Member)x).LongStringPropertyValue.SqlEquals(value, TextColumnType.NText) || + ((Member)x).ShortStringPropertyValue.SqlEquals(value, TextColumnType.NVarchar))); + break; + case StringPropertyMatchType.Contains: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + (((Member)x).LongStringPropertyValue.SqlContains(value, TextColumnType.NText) || + ((Member)x).ShortStringPropertyValue.SqlContains(value, TextColumnType.NVarchar))); + break; + case StringPropertyMatchType.StartsWith: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + (((Member)x).LongStringPropertyValue.SqlStartsWith(value, TextColumnType.NText) || + ((Member)x).ShortStringPropertyValue.SqlStartsWith(value, TextColumnType.NVarchar))); + break; + case StringPropertyMatchType.EndsWith: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + (((Member)x).LongStringPropertyValue.SqlEndsWith(value, TextColumnType.NText) || + ((Member)x).ShortStringPropertyValue.SqlEndsWith(value, TextColumnType.NVarchar))); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } + + var members = repository.GetByQuery(query); + return members; + } + } + + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// The type of match to make as . Default is + /// + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + IQuery query; + + switch (matchType) + { + case ValuePropertyMatchType.Exact: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerPropertyValue == value); + break; + case ValuePropertyMatchType.GreaterThan: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerPropertyValue > value); + break; + case ValuePropertyMatchType.LessThan: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerPropertyValue < value); + break; + case ValuePropertyMatchType.GreaterThanOrEqualTo: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerPropertyValue >= value); + break; + case ValuePropertyMatchType.LessThanOrEqualTo: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerPropertyValue <= value); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } + + var members = repository.GetByQuery(query); + return members; + } + } + + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, bool value) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + var query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).BoolPropertyValue == value); + + var members = repository.GetByQuery(query); + return members; + } + } + + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// The type of match to make as . Default is + /// + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + IQuery query; + + switch (matchType) + { + case ValuePropertyMatchType.Exact: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue == value); + break; + case ValuePropertyMatchType.GreaterThan: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue > value); + break; + case ValuePropertyMatchType.LessThan: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue < value); + break; + case ValuePropertyMatchType.GreaterThanOrEqualTo: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue >= value); + break; + case ValuePropertyMatchType.LessThanOrEqualTo: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue <= value); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } + + //TODO: Since this is by property value, we need a GetByPropertyQuery on the repo! + var members = repository.GetByQuery(query); + return members; + } + } + + /// + /// Rebuilds all xml content in the cmsContentXml table for all members + /// + /// + /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures + /// for all members = USE WITH CARE! + /// + /// True if publishing succeeded, otherwise False + public void RebuildXmlStructures(params int[] memberTypeIds) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + repository.RebuildXmlStructures( + member => _entitySerializer.Serialize(_dataTypeService, member), + contentTypeIds: memberTypeIds.Length == 0 ? null : memberTypeIds); + } + + Audit(AuditType.Publish, "MemberService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1); + } + + #endregion + + #region IMembershipMemberService Implementation + + /// + /// Gets the total number of Members based on the count type + /// + /// + /// The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members + /// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science + /// but that is how MS have made theirs so we'll follow that principal. + /// + /// to count by + /// with number of Members for passed in type + public int GetCount(MemberCountType countType) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + IQuery query; + + switch (countType) + { + case MemberCountType.All: + query = new Query(); + return repository.Count(query); + case MemberCountType.Online: + var fromDate = DateTime.Now.AddMinutes(-Membership.UserIsOnlineTimeWindow); + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.LastLoginDate && + ((Member)x).DateTimePropertyValue > fromDate); + return repository.GetCountByQuery(query); + case MemberCountType.LockedOut: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.IsLockedOut && + ((Member)x).BoolPropertyValue == true); + return repository.GetCountByQuery(query); + case MemberCountType.Approved: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.IsApproved && + ((Member)x).BoolPropertyValue == true); + return repository.GetCountByQuery(query); + default: + throw new ArgumentOutOfRangeException("countType"); + } + } + + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords) + { + long total; + var result = GetAll(Convert.ToInt64(pageIndex), pageSize, out total); + totalRecords = Convert.ToInt32(total); + return result; + } + + /// + /// Gets a list of paged objects + /// + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// + public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, orderBySystemField: true); + } + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = "") + { + long total; + var result = GetAll(Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, orderBySystemField, memberTypeAlias, filter); + totalRecords = Convert.ToInt32(total); + return result; + } + + public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = "") + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + if (memberTypeAlias == null) + { + return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, filter); + } + var query = new Query().Where(x => x.ContentTypeAlias == memberTypeAlias); + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, filter); + } + } + + /// + /// Gets the count of Members by an optional MemberType alias + /// + /// If no alias is supplied then the count for all Member will be returned + /// Optional alias for the MemberType when counting number of Members + /// with number of Members + public int Count(string memberTypeAlias = null) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + return repository.Count(memberTypeAlias); + } + } + + /// + /// Creates an object without persisting it + /// + /// This method is convenient for when you need to add properties to a new Member + /// before persisting it in order to limit the amount of times its saved. + /// Also note that the returned will not have an Id until its saved. + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// Alias of the MemberType the Member should be based on + /// + public IMember CreateMember(string username, string email, string name, string memberTypeAlias) + { + var memberType = FindMemberTypeByAlias(memberTypeAlias); + return CreateMember(username, email, name, memberType); + } + + /// + /// Creates an object without persisting it + /// + /// This method is convenient for when you need to add properties to a new Member + /// before persisting it in order to limit the amount of times its saved. + /// Also note that the returned will not have an Id until its saved. + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// MemberType the Member should be based on + /// + public IMember CreateMember(string username, string email, string name, IMemberType memberType) + { + var member = new Member(name, email.ToLower().Trim(), username, memberType); + + Created.RaiseEvent(new NewEventArgs(member, false, memberType.Alias, -1), this); + + return member; + } + + /// + /// Creates and persists a Member + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// Alias of the MemberType the Member should be based on + /// + public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias) + { + var memberType = FindMemberTypeByAlias(memberTypeAlias); + return CreateMemberWithIdentity(username, email, name, memberType); + } + + /// + /// Creates and persists a Member + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// Username of the Member to create + /// Email of the Member to create + /// MemberType the Member should be based on + /// + public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType) + { + return CreateMemberWithIdentity(username, email, username, memberType); + } + + /// + /// Creates and persists a Member + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// MemberType the Member should be based on + /// + public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType) + { + return CreateMemberWithIdentity(username, email, name, "", memberType); + } + + /// + /// Creates and persists a new + /// + /// An can be of type or + /// Username of the to create + /// Email of the to create + /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database + /// Alias of the Type + /// + IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias) + { + var memberType = FindMemberTypeByAlias(memberTypeAlias); + return CreateMemberWithIdentity(username, email, username, passwordValue, memberType); + } + + /// + /// Creates and persists a Member + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database + /// MemberType the Member should be based on + /// + private IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, IMemberType memberType) + { + if (memberType == null) throw new ArgumentNullException("memberType"); + + var member = new Member(name, email.ToLower().Trim(), username, passwordValue, memberType); + + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(member), this)) + { + member.WasCancelled = true; + return member; + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + repository.AddOrUpdate(member); + //insert the xml + repository.AddOrUpdateContentXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + { + repository.AddOrUpdatePreviewXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); + } + + uow.Commit(); + } + + Saved.RaiseEvent(new SaveEventArgs(member, false), this); + Created.RaiseEvent(new NewEventArgs(member, false, memberType.Alias, -1), this); + + return member; + } + + /// + /// Gets an by its provider key + /// + /// Id to use for retrieval + /// + public IMember GetByProviderKey(object id) + { + var asGuid = id.TryConvertTo(); + if (asGuid.Success) + { + return GetByKey((Guid)id); + } + var asInt = id.TryConvertTo(); + if (asInt.Success) + { + return GetById((int)id); + } + + return null; + } + + /// + /// Get an by email + /// + /// Email to use for retrieval + /// + public IMember GetByEmail(string email) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + var query = Query.Builder.Where(x => x.Email.Equals(email)); + var member = repository.GetByQuery(query).FirstOrDefault(); + + return member; + } + } + + /// + /// Get an by username + /// + /// Username to use for retrieval + /// + public IMember GetByUsername(string username) + { + //TODO: Somewhere in here, whether at this level or the repository level, we need to add + // a caching mechanism since this method is used by all the membership providers and could be + // called quite a bit when dealing with members. + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + var query = Query.Builder.Where(x => x.Username.Equals(username)); + var member = repository.GetByQuery(query).FirstOrDefault(); + + return member; + } + } + + /// + /// Deletes an + /// + /// to Delete + public void Delete(IMember member) + { + if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(member), this)) + return; + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + repository.Delete(member); + uow.Commit(); + + var args = new DeleteEventArgs(member, false); + Deleted.RaiseEvent(args, this); + + //remove any flagged media files + repository.DeleteMediaFiles(args.MediaFilesToDelete); + } + } + + /// + /// Saves an + /// + /// to Save + /// Optional parameter to raise events. + /// Default is True otherwise set to False to not raise events + public void Save(IMember entity, bool raiseEvents = true) + { + if (raiseEvents) + { + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(entity), this)) + { + return; + } + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + repository.AddOrUpdate(entity); + repository.AddOrUpdateContentXml(entity, m => _entitySerializer.Serialize(_dataTypeService, m)); + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + { + repository.AddOrUpdatePreviewXml(entity, m => _entitySerializer.Serialize(_dataTypeService, m)); + } + + uow.Commit(); + } + + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(entity, false), this); + } + + /// + /// Saves a list of objects + /// + /// to save + /// Optional parameter to raise events. + /// Default is True otherwise set to False to not raise events + public void Save(IEnumerable entities, bool raiseEvents = true) + { + var asArray = entities.ToArray(); + + if (raiseEvents) + { + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(asArray), this)) + return; + } + using (new WriteLock(Locker)) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + foreach (var member in asArray) + { + repository.AddOrUpdate(member); + repository.AddOrUpdateContentXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + { + repository.AddOrUpdatePreviewXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); + } + } + + //commit the whole lot in one go + uow.Commit(); + } + + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(asArray, false), this); + } + } + + #endregion + + #region IMembershipRoleService Implementation + + public void AddRole(string roleName) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) + { + repository.CreateIfNotExists(roleName); + } + } + + public IEnumerable GetAllRoles() + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) + { + var result = repository.GetAll(); + return result.Select(x => x.Name).Distinct(); + } + } + + public IEnumerable GetAllRoles(int memberId) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) + { + var result = repository.GetMemberGroupsForMember(memberId); + return result.Select(x => x.Name).Distinct(); + } + } + + public IEnumerable GetAllRoles(string username) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) + { + var result = repository.GetMemberGroupsForMember(username); + return result.Select(x => x.Name).Distinct(); + } + } + + public IEnumerable GetMembersInRole(string roleName) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + return repository.GetByMemberGroup(roleName); + } + } + + public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + return repository.FindMembersInRole(roleName, usernameToMatch, matchType); + } + } + + public bool DeleteRole(string roleName, bool throwIfBeingUsed) + { + using (new WriteLock(Locker)) + { + if (throwIfBeingUsed) + { + var inRole = GetMembersInRole(roleName); + if (inRole.Any()) + { + throw new InvalidOperationException("The role " + roleName + " is currently assigned to members"); + } + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) + { + var qry = new Query().Where(g => g.Name == roleName); + var found = repository.GetByQuery(qry).ToArray(); + + foreach (var memberGroup in found) + { + _memberGroupService.Delete(memberGroup); + } + return found.Any(); + } + } + } + public void AssignRole(string username, string roleName) + { + AssignRoles(new[] { username }, new[] { roleName }); + } + + public void AssignRoles(string[] usernames, string[] roleNames) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) + { + repository.AssignRoles(usernames, roleNames); + } + } + + public void DissociateRole(string username, string roleName) + { + DissociateRoles(new[] { username }, new[] { roleName }); + } + + public void DissociateRoles(string[] usernames, string[] roleNames) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) + { + repository.DissociateRoles(usernames, roleNames); + } + } + + public void AssignRole(int memberId, string roleName) + { + AssignRoles(new[] { memberId }, new[] { roleName }); + } + + public void AssignRoles(int[] memberIds, string[] roleNames) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) + { + repository.AssignRoles(memberIds, roleNames); + } + } + + public void DissociateRole(int memberId, string roleName) + { + DissociateRoles(new[] { memberId }, new[] { roleName }); + } + + public void DissociateRoles(int[] memberIds, string[] roleNames) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) + { + repository.DissociateRoles(memberIds, roleNames); + } + } + + + + #endregion + + private IMemberType FindMemberTypeByAlias(string memberTypeAlias) + { + using (var repository = RepositoryFactory.CreateMemberTypeRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Alias == memberTypeAlias); + var types = repository.GetByQuery(query); + + if (types.Any() == false) + throw new Exception( + string.Format("No MemberType matching the passed in Alias: '{0}' was found", + memberTypeAlias)); + + var contentType = types.First(); + + if (contentType == null) + throw new Exception(string.Format("MemberType matching the passed in Alias: '{0}' was null", + memberTypeAlias)); + + return contentType; + } + } + + private void Audit(AuditType type, string message, int userId, int objectId) + { + var uow = UowProvider.GetUnitOfWork(); + using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) + { + auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); + uow.Commit(); + } + } + + #region Event Handlers + + /// + /// Occurs before Delete + /// + public static event TypedEventHandler> Deleting; + + /// + /// Occurs after Delete + /// + public static event TypedEventHandler> Deleted; + + /// + /// Occurs before Save + /// + public static event TypedEventHandler> Saving; + + /// + /// Occurs after Create + /// + /// + /// Please note that the Member object has been created, but might not have been saved + /// so it does not have an identity yet (meaning no Id has been set). + /// + public static event TypedEventHandler> Created; + + /// + /// Occurs after Save + /// + public static event TypedEventHandler> Saved; + + #endregion + + /// + /// A helper method that will create a basic/generic member for use with a generic membership provider + /// + /// + internal static IMember CreateGenericMembershipProviderMember(string name, string email, string username, string password) + { + var identity = int.MaxValue; + + var memType = new MemberType(-1); + var propGroup = new PropertyGroup + { + Name = "Membership", + Id = --identity + }; + propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext, Constants.Conventions.Member.Comments) + { + Name = Constants.Conventions.Member.CommentsLabel, + SortOrder = 0, + Id = --identity, + Key = identity.ToGuid() + }); + propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer, Constants.Conventions.Member.IsApproved) + { + Name = Constants.Conventions.Member.IsApprovedLabel, + SortOrder = 3, + Id = --identity, + Key = identity.ToGuid() + }); + propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer, Constants.Conventions.Member.IsLockedOut) + { + Name = Constants.Conventions.Member.IsLockedOutLabel, + SortOrder = 4, + Id = --identity, + Key = identity.ToGuid() + }); + propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastLockoutDate) + { + Name = Constants.Conventions.Member.LastLockoutDateLabel, + SortOrder = 5, + Id = --identity, + Key = identity.ToGuid() + }); + propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastLoginDate) + { + Name = Constants.Conventions.Member.LastLoginDateLabel, + SortOrder = 6, + Id = --identity, + Key = identity.ToGuid() + }); + propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastPasswordChangeDate) + { + Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, + SortOrder = 7, + Id = --identity, + Key = identity.ToGuid() + }); + + memType.PropertyGroups.Add(propGroup); + + var member = new Member(name, email, username, password, memType); + + //we've assigned ids to the property types and groups but we also need to assign fake ids to the properties themselves. + foreach (var property in member.Properties) + { + property.Id = --identity; + } + + return member; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index c8ccecdb95..f21371ef2b 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -24,795 +24,795 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Tests.Persistence.Repositories { - [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] - [TestFixture] - public class ContentRepositoryTest : BaseDatabaseFactoryTest - { - [SetUp] - public override void Initialize() - { - base.Initialize(); - - CreateTestData(); - } - - [TearDown] - public override void TearDown() - { - base.TearDown(); - } - - private ContentRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository) - { - TemplateRepository tr; - return CreateRepository(unitOfWork, out contentTypeRepository, out tr); - } - - private ContentRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository, out TemplateRepository templateRepository) - { - templateRepository = new TemplateRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepository = new TagRepository(unitOfWork, CacheHelper, Logger, SqlSyntax); - contentTypeRepository = new ContentTypeRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, templateRepository); - var repository = new ContentRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, contentTypeRepository, templateRepository, tagRepository, Mock.Of()); - return repository; - } - - [Test] - public void Rebuild_Xml_Structures_With_Non_Latest_Version() - { - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - var contentType1 = MockedContentTypes.CreateSimpleContentType("Textpage1", "Textpage1"); - contentTypeRepository.AddOrUpdate(contentType1); - - var allCreated = new List(); - - //create 100 non published - for (var i = 0; i < 100; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType1); - repository.AddOrUpdate(c1); - allCreated.Add(c1); - } - //create 100 published - for (var i = 0; i < 100; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType1); - c1.ChangePublishedState(PublishedState.Published); - repository.AddOrUpdate(c1); - allCreated.Add(c1); - } - unitOfWork.Commit(); - - //now create some versions of this content - this shouldn't affect the xml structures saved - for (int i = 0; i < allCreated.Count; i++) - { - allCreated[i].Name = "blah" + i; - //IMPORTANT testing note here: We need to changed the published state here so that - // it doesn't automatically think this is simply publishing again - this forces the latest - // version to be Saved and not published - allCreated[i].ChangePublishedState(PublishedState.Saved); - repository.AddOrUpdate(allCreated[i]); - } - unitOfWork.Commit(); - - //delete all xml - unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); - Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - - repository.RebuildXmlStructures(media => new XElement("test"), 10); - - Assert.AreEqual(100, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - } - } - - [Test] - public void Rebuild_All_Xml_Structures() - { - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - - var contentType1 = MockedContentTypes.CreateSimpleContentType("Textpage1", "Textpage1"); - contentTypeRepository.AddOrUpdate(contentType1); - var allCreated = new List(); - - for (var i = 0; i < 100; i++) - { - //These will be non-published so shouldn't show up - var c1 = MockedContent.CreateSimpleContent(contentType1); - repository.AddOrUpdate(c1); - allCreated.Add(c1); - } - for (var i = 0; i < 100; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType1); - c1.ChangePublishedState(PublishedState.Published); - repository.AddOrUpdate(c1); - allCreated.Add(c1); - } - unitOfWork.Commit(); - - //now create some versions of this content - this shouldn't affect the xml structures saved - for (int i = 0; i < allCreated.Count; i++) - { - allCreated[i].Name = "blah" + i; - repository.AddOrUpdate(allCreated[i]); - } - unitOfWork.Commit(); - - //delete all xml - unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); - Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - - repository.RebuildXmlStructures(media => new XElement("test"), 10); - - Assert.AreEqual(100, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - } - } - - [Test] - public void Rebuild_All_Xml_Structures_For_Content_Type() - { - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - var contentType1 = MockedContentTypes.CreateSimpleContentType("Textpage1", "Textpage1"); - var contentType2 = MockedContentTypes.CreateSimpleContentType("Textpage2", "Textpage2"); - var contentType3 = MockedContentTypes.CreateSimpleContentType("Textpage3", "Textpage3"); - contentTypeRepository.AddOrUpdate(contentType1); - contentTypeRepository.AddOrUpdate(contentType2); - contentTypeRepository.AddOrUpdate(contentType3); - - var allCreated = new List(); - - for (var i = 0; i < 30; i++) - { - //These will be non-published so shouldn't show up - var c1 = MockedContent.CreateSimpleContent(contentType1); - repository.AddOrUpdate(c1); - allCreated.Add(c1); - } - for (var i = 0; i < 30; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType1); - c1.ChangePublishedState(PublishedState.Published); - repository.AddOrUpdate(c1); - allCreated.Add(c1); - } - for (var i = 0; i < 30; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType2); - c1.ChangePublishedState(PublishedState.Published); - repository.AddOrUpdate(c1); - allCreated.Add(c1); - } - for (var i = 0; i < 30; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType3); - c1.ChangePublishedState(PublishedState.Published); - repository.AddOrUpdate(c1); - allCreated.Add(c1); - } - unitOfWork.Commit(); - - //now create some versions of this content - this shouldn't affect the xml structures saved - for (int i = 0; i < allCreated.Count; i++) - { - allCreated[i].Name = "blah" + i; - repository.AddOrUpdate(allCreated[i]); - } - unitOfWork.Commit(); - - //delete all xml - unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); - Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - - repository.RebuildXmlStructures(media => new XElement("test"), 10, contentTypeIds: new[] { contentType1.Id, contentType2.Id }); - - Assert.AreEqual(60, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - } - } - - [Test] - public void Ensures_Permissions_Are_Set_If_Parent_Entity_Permissions_Exist() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); - contentType.AllowedContentTypes = new List - { - new ContentTypeSort(new Lazy(() => contentType.Id), 0, contentType.Alias) - }; - var parentPage = MockedContent.CreateSimpleContent(contentType); - contentTypeRepository.AddOrUpdate(contentType); - repository.AddOrUpdate(parentPage); - unitOfWork.Commit(); - - // Act - repository.AssignEntityPermission(parentPage, 'A', new int[] { 0 }); - var childPage = MockedContent.CreateSimpleContent(contentType, "child", parentPage); - repository.AddOrUpdate(childPage); - unitOfWork.Commit(); - - // Assert - var permissions = repository.GetPermissionsForEntity(childPage.Id); - Assert.AreEqual(1, permissions.Count()); - Assert.AreEqual("A", permissions.Single().AssignedPermissions.First()); - } - - } - - [Test] - public void Can_Perform_Add_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage"); - Content textpage = MockedContent.CreateSimpleContent(contentType); - - // Act - contentTypeRepository.AddOrUpdate(contentType); - repository.AddOrUpdate(textpage); - unitOfWork.Commit(); - - // Assert - Assert.That(contentType.HasIdentity, Is.True); - Assert.That(textpage.HasIdentity, Is.True); - - } - } - - [Test] - public void Can_Perform_Add_With_Default_Template() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - TemplateRepository templateRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository, out templateRepository)) - { - var template = new Template("hello", "hello"); - templateRepository.AddOrUpdate(template); - unitOfWork.Commit(); - - ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage"); - contentType.AllowedTemplates = Enumerable.Empty(); // because CreateSimple... assigns one - contentType.SetDefaultTemplate(template); - Content textpage = MockedContent.CreateSimpleContent(contentType); - - // Act - - contentTypeRepository.AddOrUpdate(contentType); - repository.AddOrUpdate(textpage); - unitOfWork.Commit(); - - // Assert - Assert.That(textpage.Template, Is.Not.Null); - Assert.That(textpage.Template, Is.EqualTo(contentType.DefaultTemplate)); - } - } - - //Covers issue U4-2791 and U4-2607 - [Test] - public void Can_Save_Content_With_AtSign_In_Name_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); - contentTypeRepository.AddOrUpdate(contentType); - unitOfWork.Commit(); - - var textpage = MockedContent.CreateSimpleContent(contentType, "test@umbraco.org", -1); - var anotherTextpage = MockedContent.CreateSimpleContent(contentType, "@lightgiants", -1); - - // Act - - repository.AddOrUpdate(textpage); - repository.AddOrUpdate(anotherTextpage); - unitOfWork.Commit(); - - // Assert - Assert.That(contentType.HasIdentity, Is.True); - Assert.That(textpage.HasIdentity, Is.True); - - var content = repository.Get(textpage.Id); - Assert.That(content.Name, Is.EqualTo(textpage.Name)); - - var content2 = repository.Get(anotherTextpage.Id); - Assert.That(content2.Name, Is.EqualTo(anotherTextpage.Name)); - } - } - - [Test] - public void Can_Perform_Multiple_Adds_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); - Content textpage = MockedContent.CreateSimpleContent(contentType); - - // Act - contentTypeRepository.AddOrUpdate(contentType); - repository.AddOrUpdate(textpage); - unitOfWork.Commit(); - - Content subpage = MockedContent.CreateSimpleContent(contentType, "Text Page 1", textpage.Id); - repository.AddOrUpdate(subpage); - unitOfWork.Commit(); - - // Assert - Assert.That(contentType.HasIdentity, Is.True); - Assert.That(textpage.HasIdentity, Is.True); - Assert.That(subpage.HasIdentity, Is.True); - Assert.That(textpage.Id, Is.EqualTo(subpage.ParentId)); - } - - } - - - [Test] - public void Can_Verify_Fresh_Entity_Is_Not_Dirty() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var content = repository.Get(NodeDto.NodeIdSeed + 3); - bool dirty = ((Content)content).IsDirty(); - - // Assert - Assert.That(dirty, Is.False); - } - } - - [Test] - public void Can_Perform_Update_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var content = repository.Get(NodeDto.NodeIdSeed + 2); - content.Name = "About 2"; - repository.AddOrUpdate(content); - unitOfWork.Commit(); - var updatedContent = repository.Get(NodeDto.NodeIdSeed + 2); - - // Assert - Assert.That(updatedContent.Id, Is.EqualTo(content.Id)); - Assert.That(updatedContent.Name, Is.EqualTo(content.Name)); - } - - } - - [Test] - public void Can_Update_With_Null_Template() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var content = repository.Get(NodeDto.NodeIdSeed + 2); - content.Template = null; - repository.AddOrUpdate(content); - unitOfWork.Commit(); - var updatedContent = repository.Get(NodeDto.NodeIdSeed + 2); - - // Assert - Assert.That(updatedContent.Template, Is.Null); - } - - } - - [Test] - public void Can_Perform_Delete_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - var contentType = contentTypeRepository.Get(NodeDto.NodeIdSeed); - var content = new Content("Textpage 2 Child Node", NodeDto.NodeIdSeed + 3, contentType); - content.CreatorId = 0; - content.WriterId = 0; - - // Act - repository.AddOrUpdate(content); - unitOfWork.Commit(); - var id = content.Id; - - repository.Delete(content); - unitOfWork.Commit(); - - var content1 = repository.Get(id); - - // Assert - Assert.That(content1, Is.Null); - } - } - - [Test] - public void Can_Perform_Get_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var content = repository.Get(NodeDto.NodeIdSeed + 3); - - // Assert - Assert.That(content.Id, Is.EqualTo(NodeDto.NodeIdSeed + 3)); - Assert.That(content.CreateDate, Is.GreaterThan(DateTime.MinValue)); - Assert.That(content.UpdateDate, Is.GreaterThan(DateTime.MinValue)); - Assert.That(content.ParentId, Is.Not.EqualTo(0)); - Assert.That(content.Name, Is.EqualTo("Text Page 2")); - //Assert.That(content.SortOrder, Is.EqualTo(1)); - Assert.That(content.Version, Is.Not.EqualTo(Guid.Empty)); - Assert.That(content.ContentTypeId, Is.EqualTo(NodeDto.NodeIdSeed)); - Assert.That(content.Path, Is.Not.Empty); - Assert.That(content.Properties.Any(), Is.True); - } - } - - [Test] - public void Can_Perform_GetByQuery_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - var result = repository.GetByQuery(query); - - // Assert - Assert.That(result.Count(), Is.GreaterThanOrEqualTo(2)); - } - } - - [Test] - public void Can_Perform_Get_All_With_Many_Version() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - var result = repository.GetAll().ToArray(); - foreach (var content in result) - { - content.ChangePublishedState(PublishedState.Saved); - repository.AddOrUpdate(content); - } - unitOfWork.Commit(); - foreach (var content in result) - { - content.ChangePublishedState(PublishedState.Published); - repository.AddOrUpdate(content); - } - unitOfWork.Commit(); - - //re-get - - var result2 = repository.GetAll().ToArray(); - - Assert.AreEqual(result.Count(), result2.Count()); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_ForFirstPage_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending); - - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_ForSecondPage_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "Name", Direction.Ascending); - - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Text Page 2")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_WithSinglePage_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "Name", Direction.Ascending); - - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(2)); - Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_WithDescendingOrder_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Descending); - - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Text Page 2")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingSome_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, "Page 2"); - - // Assert - Assert.That(totalRecords, Is.EqualTo(1)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Text Page 2")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingAll_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, "Page"); - - // Assert - Assert.That(totalRecords, Is.EqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); - } - } - - [Test] - public void Can_Perform_GetAll_By_Param_Ids_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var contents = repository.GetAll(NodeDto.NodeIdSeed + 2, NodeDto.NodeIdSeed + 3); - - // Assert - Assert.That(contents, Is.Not.Null); - Assert.That(contents.Any(), Is.True); - Assert.That(contents.Count(), Is.EqualTo(2)); - } - } - - [Test] - public void Can_Perform_GetAll_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var contents = repository.GetAll(); - - // Assert - Assert.That(contents, Is.Not.Null); - Assert.That(contents.Any(), Is.True); - Assert.That(contents.Count(), Is.GreaterThanOrEqualTo(4)); - } - - - } - - [Test] - public void Can_Perform_Exists_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var exists = repository.Exists(NodeDto.NodeIdSeed + 1); - - // Assert - Assert.That(exists, Is.True); - } - - - } - - [Test] - public void Can_Perform_Count_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - int level = 2; - var query = Query.Builder.Where(x => x.Level == level); - var result = repository.Count(query); - - // Assert - Assert.That(result, Is.GreaterThanOrEqualTo(2)); - } - } - - [Test] - public void Can_Verify_Keys_Set() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var textpage = repository.Get(NodeDto.NodeIdSeed + 1); - var subpage = repository.Get(NodeDto.NodeIdSeed + 2); - var trashed = repository.Get(NodeDto.NodeIdSeed + 4); - - // Assert - Assert.That(textpage.Key.ToString().ToUpper(), Is.EqualTo("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0")); - Assert.That(subpage.Key.ToString().ToUpper(), Is.EqualTo("FF11402B-7E53-4654-81A7-462AC2108059")); - Assert.That(trashed.Key, Is.Not.EqualTo(Guid.Empty)); - } - } - - [Test] - public void Can_Get_Content_By_Guid_Key() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Key == new Guid("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0")); - var content = repository.GetByQuery(query).SingleOrDefault(); - - // Assert - Assert.That(content, Is.Not.Null); - Assert.That(content.Id, Is.EqualTo(NodeDto.NodeIdSeed + 1)); - } - - } - - public void CreateTestData() - { - //Create and Save ContentType "umbTextpage" -> (NodeDto.NodeIdSeed) - ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage", "Textpage"); - contentType.Key = new Guid("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"); - ServiceContext.ContentTypeService.Save(contentType); - - //Create and Save Content "Homepage" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 1) - Content textpage = MockedContent.CreateSimpleContent(contentType); - textpage.Key = new Guid("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0"); - ServiceContext.ContentService.Save(textpage, 0); - - //Create and Save Content "Text Page 1" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 2) - Content subpage = MockedContent.CreateSimpleContent(contentType, "Text Page 1", textpage.Id); - subpage.Key = new Guid("FF11402B-7E53-4654-81A7-462AC2108059"); - ServiceContext.ContentService.Save(subpage, 0); - - //Create and Save Content "Text Page 1" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 3) - Content subpage2 = MockedContent.CreateSimpleContent(contentType, "Text Page 2", textpage.Id); - ServiceContext.ContentService.Save(subpage2, 0); - - //Create and Save Content "Text Page Deleted" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 4) - Content trashed = MockedContent.CreateSimpleContent(contentType, "Text Page Deleted", -20); - trashed.Trashed = true; - ServiceContext.ContentService.Save(trashed, 0); - } - } + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] + [TestFixture] + public class ContentRepositoryTest : BaseDatabaseFactoryTest + { + [SetUp] + public override void Initialize() + { + base.Initialize(); + + CreateTestData(); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + private ContentRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository) + { + TemplateRepository tr; + return CreateRepository(unitOfWork, out contentTypeRepository, out tr); + } + + private ContentRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository, out TemplateRepository templateRepository) + { + templateRepository = new TemplateRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, Mock.Of(), Mock.Of(), Mock.Of()); + var tagRepository = new TagRepository(unitOfWork, CacheHelper, Logger, SqlSyntax); + contentTypeRepository = new ContentTypeRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, templateRepository); + var repository = new ContentRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, contentTypeRepository, templateRepository, tagRepository, Mock.Of()); + return repository; + } + + [Test] + public void Rebuild_Xml_Structures_With_Non_Latest_Version() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + var contentType1 = MockedContentTypes.CreateSimpleContentType("Textpage1", "Textpage1"); + contentTypeRepository.AddOrUpdate(contentType1); + + var allCreated = new List(); + + //create 100 non published + for (var i = 0; i < 100; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType1); + repository.AddOrUpdate(c1); + allCreated.Add(c1); + } + //create 100 published + for (var i = 0; i < 100; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType1); + c1.ChangePublishedState(PublishedState.Published); + repository.AddOrUpdate(c1); + allCreated.Add(c1); + } + unitOfWork.Commit(); + + //now create some versions of this content - this shouldn't affect the xml structures saved + for (int i = 0; i < allCreated.Count; i++) + { + allCreated[i].Name = "blah" + i; + //IMPORTANT testing note here: We need to changed the published state here so that + // it doesn't automatically think this is simply publishing again - this forces the latest + // version to be Saved and not published + allCreated[i].ChangePublishedState(PublishedState.Saved); + repository.AddOrUpdate(allCreated[i]); + } + unitOfWork.Commit(); + + //delete all xml + unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); + Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + + repository.RebuildXmlStructures(media => new XElement("test"), 10); + + Assert.AreEqual(100, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + } + } + + [Test] + public void Rebuild_All_Xml_Structures() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + + var contentType1 = MockedContentTypes.CreateSimpleContentType("Textpage1", "Textpage1"); + contentTypeRepository.AddOrUpdate(contentType1); + var allCreated = new List(); + + for (var i = 0; i < 100; i++) + { + //These will be non-published so shouldn't show up + var c1 = MockedContent.CreateSimpleContent(contentType1); + repository.AddOrUpdate(c1); + allCreated.Add(c1); + } + for (var i = 0; i < 100; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType1); + c1.ChangePublishedState(PublishedState.Published); + repository.AddOrUpdate(c1); + allCreated.Add(c1); + } + unitOfWork.Commit(); + + //now create some versions of this content - this shouldn't affect the xml structures saved + for (int i = 0; i < allCreated.Count; i++) + { + allCreated[i].Name = "blah" + i; + repository.AddOrUpdate(allCreated[i]); + } + unitOfWork.Commit(); + + //delete all xml + unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); + Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + + repository.RebuildXmlStructures(media => new XElement("test"), 10); + + Assert.AreEqual(100, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + } + } + + [Test] + public void Rebuild_All_Xml_Structures_For_Content_Type() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + var contentType1 = MockedContentTypes.CreateSimpleContentType("Textpage1", "Textpage1"); + var contentType2 = MockedContentTypes.CreateSimpleContentType("Textpage2", "Textpage2"); + var contentType3 = MockedContentTypes.CreateSimpleContentType("Textpage3", "Textpage3"); + contentTypeRepository.AddOrUpdate(contentType1); + contentTypeRepository.AddOrUpdate(contentType2); + contentTypeRepository.AddOrUpdate(contentType3); + + var allCreated = new List(); + + for (var i = 0; i < 30; i++) + { + //These will be non-published so shouldn't show up + var c1 = MockedContent.CreateSimpleContent(contentType1); + repository.AddOrUpdate(c1); + allCreated.Add(c1); + } + for (var i = 0; i < 30; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType1); + c1.ChangePublishedState(PublishedState.Published); + repository.AddOrUpdate(c1); + allCreated.Add(c1); + } + for (var i = 0; i < 30; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType2); + c1.ChangePublishedState(PublishedState.Published); + repository.AddOrUpdate(c1); + allCreated.Add(c1); + } + for (var i = 0; i < 30; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType3); + c1.ChangePublishedState(PublishedState.Published); + repository.AddOrUpdate(c1); + allCreated.Add(c1); + } + unitOfWork.Commit(); + + //now create some versions of this content - this shouldn't affect the xml structures saved + for (int i = 0; i < allCreated.Count; i++) + { + allCreated[i].Name = "blah" + i; + repository.AddOrUpdate(allCreated[i]); + } + unitOfWork.Commit(); + + //delete all xml + unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); + Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + + repository.RebuildXmlStructures(media => new XElement("test"), 10, contentTypeIds: new[] { contentType1.Id, contentType2.Id }); + + Assert.AreEqual(60, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + } + } + + [Test] + public void Ensures_Permissions_Are_Set_If_Parent_Entity_Permissions_Exist() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); + contentType.AllowedContentTypes = new List + { + new ContentTypeSort(new Lazy(() => contentType.Id), 0, contentType.Alias) + }; + var parentPage = MockedContent.CreateSimpleContent(contentType); + contentTypeRepository.AddOrUpdate(contentType); + repository.AddOrUpdate(parentPage); + unitOfWork.Commit(); + + // Act + repository.AssignEntityPermission(parentPage, 'A', new int[] { 0 }); + var childPage = MockedContent.CreateSimpleContent(contentType, "child", parentPage); + repository.AddOrUpdate(childPage); + unitOfWork.Commit(); + + // Assert + var permissions = repository.GetPermissionsForEntity(childPage.Id); + Assert.AreEqual(1, permissions.Count()); + Assert.AreEqual("A", permissions.Single().AssignedPermissions.First()); + } + + } + + [Test] + public void Can_Perform_Add_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage"); + Content textpage = MockedContent.CreateSimpleContent(contentType); + + // Act + contentTypeRepository.AddOrUpdate(contentType); + repository.AddOrUpdate(textpage); + unitOfWork.Commit(); + + // Assert + Assert.That(contentType.HasIdentity, Is.True); + Assert.That(textpage.HasIdentity, Is.True); + + } + } + + [Test] + public void Can_Perform_Add_With_Default_Template() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + TemplateRepository templateRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository, out templateRepository)) + { + var template = new Template("hello", "hello"); + templateRepository.AddOrUpdate(template); + unitOfWork.Commit(); + + ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage"); + contentType.AllowedTemplates = Enumerable.Empty(); // because CreateSimple... assigns one + contentType.SetDefaultTemplate(template); + Content textpage = MockedContent.CreateSimpleContent(contentType); + + // Act + + contentTypeRepository.AddOrUpdate(contentType); + repository.AddOrUpdate(textpage); + unitOfWork.Commit(); + + // Assert + Assert.That(textpage.Template, Is.Not.Null); + Assert.That(textpage.Template, Is.EqualTo(contentType.DefaultTemplate)); + } + } + + //Covers issue U4-2791 and U4-2607 + [Test] + public void Can_Save_Content_With_AtSign_In_Name_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); + contentTypeRepository.AddOrUpdate(contentType); + unitOfWork.Commit(); + + var textpage = MockedContent.CreateSimpleContent(contentType, "test@umbraco.org", -1); + var anotherTextpage = MockedContent.CreateSimpleContent(contentType, "@lightgiants", -1); + + // Act + + repository.AddOrUpdate(textpage); + repository.AddOrUpdate(anotherTextpage); + unitOfWork.Commit(); + + // Assert + Assert.That(contentType.HasIdentity, Is.True); + Assert.That(textpage.HasIdentity, Is.True); + + var content = repository.Get(textpage.Id); + Assert.That(content.Name, Is.EqualTo(textpage.Name)); + + var content2 = repository.Get(anotherTextpage.Id); + Assert.That(content2.Name, Is.EqualTo(anotherTextpage.Name)); + } + } + + [Test] + public void Can_Perform_Multiple_Adds_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); + Content textpage = MockedContent.CreateSimpleContent(contentType); + + // Act + contentTypeRepository.AddOrUpdate(contentType); + repository.AddOrUpdate(textpage); + unitOfWork.Commit(); + + Content subpage = MockedContent.CreateSimpleContent(contentType, "Text Page 1", textpage.Id); + repository.AddOrUpdate(subpage); + unitOfWork.Commit(); + + // Assert + Assert.That(contentType.HasIdentity, Is.True); + Assert.That(textpage.HasIdentity, Is.True); + Assert.That(subpage.HasIdentity, Is.True); + Assert.That(textpage.Id, Is.EqualTo(subpage.ParentId)); + } + + } + + + [Test] + public void Can_Verify_Fresh_Entity_Is_Not_Dirty() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var content = repository.Get(NodeDto.NodeIdSeed + 3); + bool dirty = ((Content)content).IsDirty(); + + // Assert + Assert.That(dirty, Is.False); + } + } + + [Test] + public void Can_Perform_Update_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var content = repository.Get(NodeDto.NodeIdSeed + 2); + content.Name = "About 2"; + repository.AddOrUpdate(content); + unitOfWork.Commit(); + var updatedContent = repository.Get(NodeDto.NodeIdSeed + 2); + + // Assert + Assert.That(updatedContent.Id, Is.EqualTo(content.Id)); + Assert.That(updatedContent.Name, Is.EqualTo(content.Name)); + } + + } + + [Test] + public void Can_Update_With_Null_Template() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var content = repository.Get(NodeDto.NodeIdSeed + 2); + content.Template = null; + repository.AddOrUpdate(content); + unitOfWork.Commit(); + var updatedContent = repository.Get(NodeDto.NodeIdSeed + 2); + + // Assert + Assert.That(updatedContent.Template, Is.Null); + } + + } + + [Test] + public void Can_Perform_Delete_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + var contentType = contentTypeRepository.Get(NodeDto.NodeIdSeed); + var content = new Content("Textpage 2 Child Node", NodeDto.NodeIdSeed + 3, contentType); + content.CreatorId = 0; + content.WriterId = 0; + + // Act + repository.AddOrUpdate(content); + unitOfWork.Commit(); + var id = content.Id; + + repository.Delete(content); + unitOfWork.Commit(); + + var content1 = repository.Get(id); + + // Assert + Assert.That(content1, Is.Null); + } + } + + [Test] + public void Can_Perform_Get_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var content = repository.Get(NodeDto.NodeIdSeed + 3); + + // Assert + Assert.That(content.Id, Is.EqualTo(NodeDto.NodeIdSeed + 3)); + Assert.That(content.CreateDate, Is.GreaterThan(DateTime.MinValue)); + Assert.That(content.UpdateDate, Is.GreaterThan(DateTime.MinValue)); + Assert.That(content.ParentId, Is.Not.EqualTo(0)); + Assert.That(content.Name, Is.EqualTo("Text Page 2")); + //Assert.That(content.SortOrder, Is.EqualTo(1)); + Assert.That(content.Version, Is.Not.EqualTo(Guid.Empty)); + Assert.That(content.ContentTypeId, Is.EqualTo(NodeDto.NodeIdSeed)); + Assert.That(content.Path, Is.Not.Empty); + Assert.That(content.Properties.Any(), Is.True); + } + } + + [Test] + public void Can_Perform_GetByQuery_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + var result = repository.GetByQuery(query); + + // Assert + Assert.That(result.Count(), Is.GreaterThanOrEqualTo(2)); + } + } + + [Test] + public void Can_Perform_Get_All_With_Many_Version() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + var result = repository.GetAll().ToArray(); + foreach (var content in result) + { + content.ChangePublishedState(PublishedState.Saved); + repository.AddOrUpdate(content); + } + unitOfWork.Commit(); + foreach (var content in result) + { + content.ChangePublishedState(PublishedState.Published); + repository.AddOrUpdate(content); + } + unitOfWork.Commit(); + + //re-get + + var result2 = repository.GetAll().ToArray(); + + Assert.AreEqual(result.Count(), result2.Count()); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_ForFirstPage_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_ForSecondPage_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 2")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithSinglePage_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(2)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithDescendingOrder_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Descending, orderBySystemField: true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 2")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingSome_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, "Page 2"); + + // Assert + Assert.That(totalRecords, Is.EqualTo(1)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 2")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingAll_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, "Page"); + + // Assert + Assert.That(totalRecords, Is.EqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); + } + } + + [Test] + public void Can_Perform_GetAll_By_Param_Ids_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var contents = repository.GetAll(NodeDto.NodeIdSeed + 2, NodeDto.NodeIdSeed + 3); + + // Assert + Assert.That(contents, Is.Not.Null); + Assert.That(contents.Any(), Is.True); + Assert.That(contents.Count(), Is.EqualTo(2)); + } + } + + [Test] + public void Can_Perform_GetAll_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var contents = repository.GetAll(); + + // Assert + Assert.That(contents, Is.Not.Null); + Assert.That(contents.Any(), Is.True); + Assert.That(contents.Count(), Is.GreaterThanOrEqualTo(4)); + } + + + } + + [Test] + public void Can_Perform_Exists_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var exists = repository.Exists(NodeDto.NodeIdSeed + 1); + + // Assert + Assert.That(exists, Is.True); + } + + + } + + [Test] + public void Can_Perform_Count_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + int level = 2; + var query = Query.Builder.Where(x => x.Level == level); + var result = repository.Count(query); + + // Assert + Assert.That(result, Is.GreaterThanOrEqualTo(2)); + } + } + + [Test] + public void Can_Verify_Keys_Set() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var textpage = repository.Get(NodeDto.NodeIdSeed + 1); + var subpage = repository.Get(NodeDto.NodeIdSeed + 2); + var trashed = repository.Get(NodeDto.NodeIdSeed + 4); + + // Assert + Assert.That(textpage.Key.ToString().ToUpper(), Is.EqualTo("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0")); + Assert.That(subpage.Key.ToString().ToUpper(), Is.EqualTo("FF11402B-7E53-4654-81A7-462AC2108059")); + Assert.That(trashed.Key, Is.Not.EqualTo(Guid.Empty)); + } + } + + [Test] + public void Can_Get_Content_By_Guid_Key() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Key == new Guid("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0")); + var content = repository.GetByQuery(query).SingleOrDefault(); + + // Assert + Assert.That(content, Is.Not.Null); + Assert.That(content.Id, Is.EqualTo(NodeDto.NodeIdSeed + 1)); + } + + } + + public void CreateTestData() + { + //Create and Save ContentType "umbTextpage" -> (NodeDto.NodeIdSeed) + ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage", "Textpage"); + contentType.Key = new Guid("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"); + ServiceContext.ContentTypeService.Save(contentType); + + //Create and Save Content "Homepage" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 1) + Content textpage = MockedContent.CreateSimpleContent(contentType); + textpage.Key = new Guid("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0"); + ServiceContext.ContentService.Save(textpage, 0); + + //Create and Save Content "Text Page 1" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 2) + Content subpage = MockedContent.CreateSimpleContent(contentType, "Text Page 1", textpage.Id); + subpage.Key = new Guid("FF11402B-7E53-4654-81A7-462AC2108059"); + ServiceContext.ContentService.Save(subpage, 0); + + //Create and Save Content "Text Page 1" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 3) + Content subpage2 = MockedContent.CreateSimpleContent(contentType, "Text Page 2", textpage.Id); + ServiceContext.ContentService.Save(subpage2, 0); + + //Create and Save Content "Text Page Deleted" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 4) + Content trashed = MockedContent.CreateSimpleContent(contentType, "Text Page Deleted", -20); + trashed.Trashed = true; + ServiceContext.ContentService.Save(trashed, 0); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index 60c49f89cd..4ca987fe26 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -17,547 +17,547 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Tests.Persistence.Repositories { - [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] - [TestFixture] - public class MediaRepositoryTest : BaseDatabaseFactoryTest - { - public MediaRepositoryTest() - { - } - - [SetUp] - public override void Initialize() - { - base.Initialize(); - - CreateTestData(); - } - - private MediaRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out MediaTypeRepository mediaTypeRepository) - { - mediaTypeRepository = new MediaTypeRepository(unitOfWork, CacheHelper, Mock.Of(), SqlSyntax); - var tagRepository = new TagRepository(unitOfWork, CacheHelper, Mock.Of(), SqlSyntax); - var repository = new MediaRepository(unitOfWork, CacheHelper, Mock.Of(), SqlSyntax, mediaTypeRepository, tagRepository, Mock.Of()); - return repository; - } - - [Test] - public void Rebuild_All_Xml_Structures() - { - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - var mediaType = mediaTypeRepository.Get(1032); - - for (var i = 0; i < 100; i++) - { - var image = MockedMedia.CreateMediaImage(mediaType, -1); - repository.AddOrUpdate(image); - } - unitOfWork.Commit(); - - //delete all xml - unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); - Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - - repository.RebuildXmlStructures(media => new XElement("test"), 10); - - Assert.AreEqual(103, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - } - } - - [Test] - public void Rebuild_All_Xml_Structures_For_Content_Type() - { - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - var imageMediaType = mediaTypeRepository.Get(1032); - var fileMediaType = mediaTypeRepository.Get(1033); - var folderMediaType = mediaTypeRepository.Get(1031); - - for (var i = 0; i < 30; i++) - { - var image = MockedMedia.CreateMediaImage(imageMediaType, -1); - repository.AddOrUpdate(image); - } - for (var i = 0; i < 30; i++) - { - var file = MockedMedia.CreateMediaFile(fileMediaType, -1); - repository.AddOrUpdate(file); - } - for (var i = 0; i < 30; i++) - { - var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); - repository.AddOrUpdate(folder); - } - unitOfWork.Commit(); - - //delete all xml - unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); - Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - - repository.RebuildXmlStructures(media => new XElement("test"), 10, contentTypeIds: new[] {1032, 1033}); - - Assert.AreEqual(62, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - } - } - - [Test] - public void Can_Perform_Add_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - var mediaType = mediaTypeRepository.Get(1032); - var image = MockedMedia.CreateMediaImage(mediaType, -1); - - // Act - mediaTypeRepository.AddOrUpdate(mediaType); - repository.AddOrUpdate(image); - unitOfWork.Commit(); - - // Assert - Assert.That(mediaType.HasIdentity, Is.True); - Assert.That(image.HasIdentity, Is.True); - } - } - - [Test] - public void Can_Perform_Multiple_Adds_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - var mediaType = mediaTypeRepository.Get(1032); - var file = MockedMedia.CreateMediaFile(mediaType, -1); - - // Act - repository.AddOrUpdate(file); - unitOfWork.Commit(); - - var image = MockedMedia.CreateMediaImage(mediaType, -1); - repository.AddOrUpdate(image); - unitOfWork.Commit(); - - // Assert - Assert.That(file.HasIdentity, Is.True); - Assert.That(image.HasIdentity, Is.True); - Assert.That(file.Name, Is.EqualTo("Test File")); - Assert.That(image.Name, Is.EqualTo("Test Image")); - Assert.That(file.ContentTypeId, Is.EqualTo(mediaType.Id)); - Assert.That(image.ContentTypeId, Is.EqualTo(mediaType.Id)); - } - } - - [Test] - public void Can_Perform_Multiple_Adds_On_MediaRepository_With_RepositoryResolver() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - var mediaType = mediaTypeRepository.Get(1032); - var file = MockedMedia.CreateMediaFile(mediaType, -1); - - // Act - repository.AddOrUpdate(file); - unitOfWork.Commit(); - - var image = MockedMedia.CreateMediaImage(mediaType, -1); - repository.AddOrUpdate(image); - unitOfWork.Commit(); - - // Assert - Assert.That(file.HasIdentity, Is.True); - Assert.That(image.HasIdentity, Is.True); - Assert.That(file.Name, Is.EqualTo("Test File")); - Assert.That(image.Name, Is.EqualTo("Test Image")); - Assert.That(file.ContentTypeId, Is.EqualTo(mediaType.Id)); - Assert.That(image.ContentTypeId, Is.EqualTo(mediaType.Id)); - } - } - - [Test] - public void Can_Verify_Fresh_Entity_Is_Not_Dirty() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - // Act - var media = repository.Get(NodeDto.NodeIdSeed + 1); - bool dirty = ((ICanBeDirty) media).IsDirty(); - - // Assert - Assert.That(dirty, Is.False); - } - } - - [Test] - public void Can_Perform_Update_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - // Act - var content = repository.Get(NodeDto.NodeIdSeed + 2); - content.Name = "Test File Updated"; - repository.AddOrUpdate(content); - unitOfWork.Commit(); - - var updatedContent = repository.Get(NodeDto.NodeIdSeed + 2); - - // Assert - Assert.That(updatedContent.Id, Is.EqualTo(content.Id)); - Assert.That(updatedContent.Name, Is.EqualTo(content.Name)); - } - } - - [Test] - public void Can_Perform_Delete_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - // Act - var media = repository.Get(NodeDto.NodeIdSeed + 2); - repository.Delete(media); - unitOfWork.Commit(); - - var deleted = repository.Get(NodeDto.NodeIdSeed + 2); - var exists = repository.Exists(NodeDto.NodeIdSeed + 2); - - // Assert - Assert.That(deleted, Is.Null); - Assert.That(exists, Is.False); - } - } - - [Test] - public void Can_Perform_Get_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - // Act - var media = repository.Get(NodeDto.NodeIdSeed + 1); - - // Assert - Assert.That(media.Id, Is.EqualTo(NodeDto.NodeIdSeed + 1)); - Assert.That(media.CreateDate, Is.GreaterThan(DateTime.MinValue)); - Assert.That(media.UpdateDate, Is.GreaterThan(DateTime.MinValue)); - Assert.That(media.ParentId, Is.Not.EqualTo(0)); - Assert.That(media.Name, Is.EqualTo("Test Image")); - Assert.That(media.SortOrder, Is.EqualTo(0)); - Assert.That(media.Version, Is.Not.EqualTo(Guid.Empty)); - Assert.That(media.ContentTypeId, Is.EqualTo(1032)); - Assert.That(media.Path, Is.Not.Empty); - Assert.That(media.Properties.Any(), Is.True); - } - } - - [Test] - public void Can_Perform_GetByQuery_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - var result = repository.GetByQuery(query); - - // Assert - Assert.That(result.Count(), Is.GreaterThanOrEqualTo(2)); //There should be two entities on level 2: File and Media - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_ForFirstPage_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending); - - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Test Image")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_ForSecondPage_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "SortOrder", Direction.Ascending); - - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Test File")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_WithSinglePage_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "SortOrder", Direction.Ascending); - - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(2)); - Assert.That(result.First().Name, Is.EqualTo("Test Image")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_WithDescendingOrder_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Descending); - - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Test File")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_WitAlternateOrder_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending); - - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Test File")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingSome_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, "File"); - - // Assert - Assert.That(totalRecords, Is.EqualTo(1)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Test File")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingAll_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, "Test"); - - // Assert - Assert.That(totalRecords, Is.EqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Test Image")); - } - } - - [Test] - public void Can_Perform_GetAll_By_Param_Ids_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - // Act - var medias = repository.GetAll(NodeDto.NodeIdSeed + 1, NodeDto.NodeIdSeed + 2); - - // Assert - Assert.That(medias, Is.Not.Null); - Assert.That(medias.Any(), Is.True); - Assert.That(medias.Count(), Is.EqualTo(2)); - } - } - - [Test] - public void Can_Perform_GetAll_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - // Act - var medias = repository.GetAll(); - - // Assert - Assert.That(medias, Is.Not.Null); - Assert.That(medias.Any(), Is.True); - Assert.That(medias.Count(), Is.GreaterThanOrEqualTo(3)); - } - } - - [Test] - public void Can_Perform_Exists_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - // Act - var exists = repository.Exists(NodeDto.NodeIdSeed + 1); - var existsToo = repository.Exists(NodeDto.NodeIdSeed + 1); - var doesntExists = repository.Exists(NodeDto.NodeIdSeed + 5); - - // Assert - Assert.That(exists, Is.True); - Assert.That(existsToo, Is.True); - Assert.That(doesntExists, Is.False); - } - } - - [Test] - public void Can_Perform_Count_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - // Act - int level = 2; - var query = Query.Builder.Where(x => x.Level == level); - var result = repository.Count(query); - - // Assert - Assert.That(result, Is.GreaterThanOrEqualTo(2)); - } - } - - [TearDown] - public override void TearDown() - { - base.TearDown(); - } - - public void CreateTestData() - { - //Create and Save folder-Media -> (NodeDto.NodeIdSeed) - var folderMediaType = ServiceContext.ContentTypeService.GetMediaType(1031); - var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); - ServiceContext.MediaService.Save(folder, 0); - - //Create and Save image-Media -> (NodeDto.NodeIdSeed + 1) - var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); - var image = MockedMedia.CreateMediaImage(imageMediaType, folder.Id); - ServiceContext.MediaService.Save(image, 0); - - //Create and Save file-Media -> (NodeDto.NodeIdSeed + 2) - var fileMediaType = ServiceContext.ContentTypeService.GetMediaType(1033); - var file = MockedMedia.CreateMediaFile(fileMediaType, folder.Id); - ServiceContext.MediaService.Save(file, 0); - } - } + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] + [TestFixture] + public class MediaRepositoryTest : BaseDatabaseFactoryTest + { + public MediaRepositoryTest() + { + } + + [SetUp] + public override void Initialize() + { + base.Initialize(); + + CreateTestData(); + } + + private MediaRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out MediaTypeRepository mediaTypeRepository) + { + mediaTypeRepository = new MediaTypeRepository(unitOfWork, CacheHelper, Mock.Of(), SqlSyntax); + var tagRepository = new TagRepository(unitOfWork, CacheHelper, Mock.Of(), SqlSyntax); + var repository = new MediaRepository(unitOfWork, CacheHelper, Mock.Of(), SqlSyntax, mediaTypeRepository, tagRepository, Mock.Of()); + return repository; + } + + [Test] + public void Rebuild_All_Xml_Structures() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + var mediaType = mediaTypeRepository.Get(1032); + + for (var i = 0; i < 100; i++) + { + var image = MockedMedia.CreateMediaImage(mediaType, -1); + repository.AddOrUpdate(image); + } + unitOfWork.Commit(); + + //delete all xml + unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); + Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + + repository.RebuildXmlStructures(media => new XElement("test"), 10); + + Assert.AreEqual(103, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + } + } + + [Test] + public void Rebuild_All_Xml_Structures_For_Content_Type() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + var imageMediaType = mediaTypeRepository.Get(1032); + var fileMediaType = mediaTypeRepository.Get(1033); + var folderMediaType = mediaTypeRepository.Get(1031); + + for (var i = 0; i < 30; i++) + { + var image = MockedMedia.CreateMediaImage(imageMediaType, -1); + repository.AddOrUpdate(image); + } + for (var i = 0; i < 30; i++) + { + var file = MockedMedia.CreateMediaFile(fileMediaType, -1); + repository.AddOrUpdate(file); + } + for (var i = 0; i < 30; i++) + { + var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); + repository.AddOrUpdate(folder); + } + unitOfWork.Commit(); + + //delete all xml + unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); + Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + + repository.RebuildXmlStructures(media => new XElement("test"), 10, contentTypeIds: new[] { 1032, 1033 }); + + Assert.AreEqual(62, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + } + } + + [Test] + public void Can_Perform_Add_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + var mediaType = mediaTypeRepository.Get(1032); + var image = MockedMedia.CreateMediaImage(mediaType, -1); + + // Act + mediaTypeRepository.AddOrUpdate(mediaType); + repository.AddOrUpdate(image); + unitOfWork.Commit(); + + // Assert + Assert.That(mediaType.HasIdentity, Is.True); + Assert.That(image.HasIdentity, Is.True); + } + } + + [Test] + public void Can_Perform_Multiple_Adds_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + var mediaType = mediaTypeRepository.Get(1032); + var file = MockedMedia.CreateMediaFile(mediaType, -1); + + // Act + repository.AddOrUpdate(file); + unitOfWork.Commit(); + + var image = MockedMedia.CreateMediaImage(mediaType, -1); + repository.AddOrUpdate(image); + unitOfWork.Commit(); + + // Assert + Assert.That(file.HasIdentity, Is.True); + Assert.That(image.HasIdentity, Is.True); + Assert.That(file.Name, Is.EqualTo("Test File")); + Assert.That(image.Name, Is.EqualTo("Test Image")); + Assert.That(file.ContentTypeId, Is.EqualTo(mediaType.Id)); + Assert.That(image.ContentTypeId, Is.EqualTo(mediaType.Id)); + } + } + + [Test] + public void Can_Perform_Multiple_Adds_On_MediaRepository_With_RepositoryResolver() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + var mediaType = mediaTypeRepository.Get(1032); + var file = MockedMedia.CreateMediaFile(mediaType, -1); + + // Act + repository.AddOrUpdate(file); + unitOfWork.Commit(); + + var image = MockedMedia.CreateMediaImage(mediaType, -1); + repository.AddOrUpdate(image); + unitOfWork.Commit(); + + // Assert + Assert.That(file.HasIdentity, Is.True); + Assert.That(image.HasIdentity, Is.True); + Assert.That(file.Name, Is.EqualTo("Test File")); + Assert.That(image.Name, Is.EqualTo("Test Image")); + Assert.That(file.ContentTypeId, Is.EqualTo(mediaType.Id)); + Assert.That(image.ContentTypeId, Is.EqualTo(mediaType.Id)); + } + } + + [Test] + public void Can_Verify_Fresh_Entity_Is_Not_Dirty() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + // Act + var media = repository.Get(NodeDto.NodeIdSeed + 1); + bool dirty = ((ICanBeDirty)media).IsDirty(); + + // Assert + Assert.That(dirty, Is.False); + } + } + + [Test] + public void Can_Perform_Update_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + // Act + var content = repository.Get(NodeDto.NodeIdSeed + 2); + content.Name = "Test File Updated"; + repository.AddOrUpdate(content); + unitOfWork.Commit(); + + var updatedContent = repository.Get(NodeDto.NodeIdSeed + 2); + + // Assert + Assert.That(updatedContent.Id, Is.EqualTo(content.Id)); + Assert.That(updatedContent.Name, Is.EqualTo(content.Name)); + } + } + + [Test] + public void Can_Perform_Delete_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + // Act + var media = repository.Get(NodeDto.NodeIdSeed + 2); + repository.Delete(media); + unitOfWork.Commit(); + + var deleted = repository.Get(NodeDto.NodeIdSeed + 2); + var exists = repository.Exists(NodeDto.NodeIdSeed + 2); + + // Assert + Assert.That(deleted, Is.Null); + Assert.That(exists, Is.False); + } + } + + [Test] + public void Can_Perform_Get_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + // Act + var media = repository.Get(NodeDto.NodeIdSeed + 1); + + // Assert + Assert.That(media.Id, Is.EqualTo(NodeDto.NodeIdSeed + 1)); + Assert.That(media.CreateDate, Is.GreaterThan(DateTime.MinValue)); + Assert.That(media.UpdateDate, Is.GreaterThan(DateTime.MinValue)); + Assert.That(media.ParentId, Is.Not.EqualTo(0)); + Assert.That(media.Name, Is.EqualTo("Test Image")); + Assert.That(media.SortOrder, Is.EqualTo(0)); + Assert.That(media.Version, Is.Not.EqualTo(Guid.Empty)); + Assert.That(media.ContentTypeId, Is.EqualTo(1032)); + Assert.That(media.Path, Is.Not.Empty); + Assert.That(media.Properties.Any(), Is.True); + } + } + + [Test] + public void Can_Perform_GetByQuery_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + var result = repository.GetByQuery(query); + + // Assert + Assert.That(result.Count(), Is.GreaterThanOrEqualTo(2)); //There should be two entities on level 2: File and Media + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_ForFirstPage_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, orderBySystemField: true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Test Image")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_ForSecondPage_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "SortOrder", Direction.Ascending, orderBySystemField: true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Test File")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithSinglePage_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "SortOrder", Direction.Ascending, orderBySystemField: true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(2)); + Assert.That(result.First().Name, Is.EqualTo("Test Image")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithDescendingOrder_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Descending, orderBySystemField: true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Test File")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WitAlternateOrder_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Test File")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingSome_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true, "File"); + + // Assert + Assert.That(totalRecords, Is.EqualTo(1)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Test File")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingAll_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true, "Test"); + + // Assert + Assert.That(totalRecords, Is.EqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Test Image")); + } + } + + [Test] + public void Can_Perform_GetAll_By_Param_Ids_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + // Act + var medias = repository.GetAll(NodeDto.NodeIdSeed + 1, NodeDto.NodeIdSeed + 2); + + // Assert + Assert.That(medias, Is.Not.Null); + Assert.That(medias.Any(), Is.True); + Assert.That(medias.Count(), Is.EqualTo(2)); + } + } + + [Test] + public void Can_Perform_GetAll_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + // Act + var medias = repository.GetAll(); + + // Assert + Assert.That(medias, Is.Not.Null); + Assert.That(medias.Any(), Is.True); + Assert.That(medias.Count(), Is.GreaterThanOrEqualTo(3)); + } + } + + [Test] + public void Can_Perform_Exists_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + // Act + var exists = repository.Exists(NodeDto.NodeIdSeed + 1); + var existsToo = repository.Exists(NodeDto.NodeIdSeed + 1); + var doesntExists = repository.Exists(NodeDto.NodeIdSeed + 5); + + // Assert + Assert.That(exists, Is.True); + Assert.That(existsToo, Is.True); + Assert.That(doesntExists, Is.False); + } + } + + [Test] + public void Can_Perform_Count_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + // Act + int level = 2; + var query = Query.Builder.Where(x => x.Level == level); + var result = repository.Count(query); + + // Assert + Assert.That(result, Is.GreaterThanOrEqualTo(2)); + } + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + public void CreateTestData() + { + //Create and Save folder-Media -> (NodeDto.NodeIdSeed) + var folderMediaType = ServiceContext.ContentTypeService.GetMediaType(1031); + var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); + ServiceContext.MediaService.Save(folder, 0); + + //Create and Save image-Media -> (NodeDto.NodeIdSeed + 1) + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + var image = MockedMedia.CreateMediaImage(imageMediaType, folder.Id); + ServiceContext.MediaService.Save(image, 0); + + //Create and Save file-Media -> (NodeDto.NodeIdSeed + 2) + var fileMediaType = ServiceContext.ContentTypeService.GetMediaType(1033); + var file = MockedMedia.CreateMediaFile(fileMediaType, folder.Id); + ServiceContext.MediaService.Save(file, 0); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index 6c56c76ef7..2f9ecce9b2 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -20,124 +20,124 @@ using Version = Lucene.Net.Util.Version; namespace Umbraco.Tests.UmbracoExamine { - /// - /// Used internally by test classes to initialize a new index from the template - /// - internal static class IndexInitializer + /// + /// Used internally by test classes to initialize a new index from the template + /// + internal static class IndexInitializer + { + public static UmbracoContentIndexer GetUmbracoIndexer( + Directory luceneDir, + Analyzer analyzer = null, + IDataService dataService = null, + IContentService contentService = null, + IMediaService mediaService = null, + IDataTypeService dataTypeService = null, + IMemberService memberService = null, + IUserService userService = null) { - public static UmbracoContentIndexer GetUmbracoIndexer( - Directory luceneDir, - Analyzer analyzer = null, - IDataService dataService = null, - IContentService contentService = null, - IMediaService mediaService = null, - IDataTypeService dataTypeService = null, - IMemberService memberService = null, - IUserService userService = null) - { - if (dataService == null) - { - dataService = new TestDataService(); - } - if (contentService == null) - { - contentService = Mock.Of(); - } - if (userService == null) - { - userService = Mock.Of(x => x.GetProfileById(It.IsAny()) == Mock.Of(p => p.Id == (object)0 && p.Name == "admin")); - } - if (mediaService == null) - { - long totalRecs; + if (dataService == null) + { + dataService = new TestDataService(); + } + if (contentService == null) + { + contentService = Mock.Of(); + } + if (userService == null) + { + userService = Mock.Of(x => x.GetProfileById(It.IsAny()) == Mock.Of(p => p.Id == (object)0 && p.Name == "admin")); + } + if (mediaService == null) + { + long totalRecs; - var allRecs = dataService.MediaService.GetLatestMediaByXpath("//node") - .Root - .Elements() - .Select(x => Mock.Of( - m => - m.Id == (int) x.Attribute("id") && - m.ParentId == (int) x.Attribute("parentID") && - m.Level == (int) x.Attribute("level") && - m.CreatorId == 0 && - m.SortOrder == (int) x.Attribute("sortOrder") && - m.CreateDate == (DateTime) x.Attribute("createDate") && - m.UpdateDate == (DateTime) x.Attribute("updateDate") && - m.Name == (string) x.Attribute("nodeName") && - m.Path == (string) x.Attribute("path") && - m.Properties == new PropertyCollection() && - m.ContentType == Mock.Of(mt => - mt.Alias == (string) x.Attribute("nodeTypeAlias") && - mt.Id == (int) x.Attribute("nodeType")))) - .ToArray(); - - - mediaService = Mock.Of( - x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny()) - == - allRecs); - } - if (dataTypeService == null) - { - dataTypeService = Mock.Of(); - } - - if (memberService == null) - { - memberService = Mock.Of(); - } - - if (analyzer == null) - { - analyzer = new StandardAnalyzer(Version.LUCENE_29); - } - - var indexSet = new IndexSet(); - var indexCriteria = indexSet.ToIndexCriteria(dataService, UmbracoContentIndexer.IndexFieldPolicies); - - var i = new UmbracoContentIndexer(indexCriteria, - luceneDir, //custom lucene directory - dataService, - contentService, - mediaService, - dataTypeService, - userService, - analyzer, - false); - - //i.IndexSecondsInterval = 1; - - i.IndexingError += IndexingError; - - return i; - } - public static UmbracoExamineSearcher GetUmbracoSearcher(Directory luceneDir, Analyzer analyzer = null) - { - if (analyzer == null) - { - analyzer = new StandardAnalyzer(Version.LUCENE_29); - } - return new UmbracoExamineSearcher(luceneDir, analyzer); - } - - public static LuceneSearcher GetLuceneSearcher(Directory luceneDir) - { - return new LuceneSearcher(luceneDir, new StandardAnalyzer(Version.LUCENE_29)); - } - - public static MultiIndexSearcher GetMultiSearcher(Directory pdfDir, Directory simpleDir, Directory conventionDir, Directory cwsDir) - { - var i = new MultiIndexSearcher(new[] { pdfDir, simpleDir, conventionDir, cwsDir }, new StandardAnalyzer(Version.LUCENE_29)); - return i; - } + var allRecs = dataService.MediaService.GetLatestMediaByXpath("//node") + .Root + .Elements() + .Select(x => Mock.Of( + m => + m.Id == (int)x.Attribute("id") && + m.ParentId == (int)x.Attribute("parentID") && + m.Level == (int)x.Attribute("level") && + m.CreatorId == 0 && + m.SortOrder == (int)x.Attribute("sortOrder") && + m.CreateDate == (DateTime)x.Attribute("createDate") && + m.UpdateDate == (DateTime)x.Attribute("updateDate") && + m.Name == (string)x.Attribute("nodeName") && + m.Path == (string)x.Attribute("path") && + m.Properties == new PropertyCollection() && + m.ContentType == Mock.Of(mt => + mt.Alias == (string)x.Attribute("nodeTypeAlias") && + mt.Id == (int)x.Attribute("nodeType")))) + .ToArray(); - internal static void IndexingError(object sender, IndexingErrorEventArgs e) - { - throw new ApplicationException(e.Message, e.InnerException); - } + mediaService = Mock.Of( + x => x.GetPagedDescendants( + It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()) + == + allRecs); + } + if (dataTypeService == null) + { + dataTypeService = Mock.Of(); + } + if (memberService == null) + { + memberService = Mock.Of(); + } + if (analyzer == null) + { + analyzer = new StandardAnalyzer(Version.LUCENE_29); + } + + var indexSet = new IndexSet(); + var indexCriteria = indexSet.ToIndexCriteria(dataService, UmbracoContentIndexer.IndexFieldPolicies); + + var i = new UmbracoContentIndexer(indexCriteria, + luceneDir, //custom lucene directory + dataService, + contentService, + mediaService, + dataTypeService, + userService, + analyzer, + false); + + //i.IndexSecondsInterval = 1; + + i.IndexingError += IndexingError; + + return i; } + public static UmbracoExamineSearcher GetUmbracoSearcher(Directory luceneDir, Analyzer analyzer = null) + { + if (analyzer == null) + { + analyzer = new StandardAnalyzer(Version.LUCENE_29); + } + return new UmbracoExamineSearcher(luceneDir, analyzer); + } + + public static LuceneSearcher GetLuceneSearcher(Directory luceneDir) + { + return new LuceneSearcher(luceneDir, new StandardAnalyzer(Version.LUCENE_29)); + } + + public static MultiIndexSearcher GetMultiSearcher(Directory pdfDir, Directory simpleDir, Directory conventionDir, Directory cwsDir) + { + var i = new MultiIndexSearcher(new[] { pdfDir, simpleDir, conventionDir, cwsDir }, new StandardAnalyzer(Version.LUCENE_29)); + return i; + } + + + internal static void IndexingError(object sender, IndexingErrorEventArgs e) + { + throw new ApplicationException(e.Message, e.InnerException); + } + + + } } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 2cf7127707..2688dec37a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -25,489 +25,491 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { - /** internal method process the saving of data and post processing the result */ - function saveContentItem(content, action, files) { - return umbRequestHelper.postSaveContent({ - restApiUrl: umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"), - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatContentPostData(c, a); - } - }); - } + /** internal method process the saving of data and post processing the result */ + function saveContentItem(content, action, files) { + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"), + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatContentPostData(c, a); + } + }); + } - return { - - getRecycleBin: function() { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for content recycle bin'); - }, + return { - /** - * @ngdoc method - * @name umbraco.resources.contentResource#sort - * @methodOf umbraco.resources.contentResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
-         * var ids = [123,34533,2334,23434];
-         * contentResource.sort({ parentId: 1244, sortedIds: ids })
-         *    .then(function() {
-         *        $scope.complete = true;
-         *    });
-         * 
- * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ - sort: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.sortedIds) { - throw "args.sortedIds cannot be null"; - } + getRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetRecycleBin")), + 'Failed to retrieve data for content recycle bin'); + }, - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort content'); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#sort + * @methodOf umbraco.resources.contentResource + * + * @description + * Sorts all children below a given parent node id, based on a collection of node-ids + * + * ##usage + *
+	 * var ids = [123,34533,2334,23434];
+	 * contentResource.sort({ parentId: 1244, sortedIds: ids })
+	 *    .then(function() {
+	 *        $scope.complete = true;
+	 *    });
+	 * 
+ * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ + sort: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.sortedIds) { + throw "args.sortedIds cannot be null"; + } - /** - * @ngdoc method - * @name umbraco.resources.contentResource#move - * @methodOf umbraco.resources.contentResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
-         * contentResource.move({ parentId: 1244, id: 123 })
-         *    .then(function() {
-         *        alert("node was moved");
-         *    }, function(err){
-         *      alert("node didnt move:" + err.data.Message); 
-         *    });
-         * 
- * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"), + { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), + 'Failed to sort content'); + }, - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move content'); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#move + * @methodOf umbraco.resources.contentResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
+	 * contentResource.move({ parentId: 1244, id: 123 })
+	 *    .then(function() {
+	 *        alert("node was moved");
+	 *    }, function(err){
+	 *      alert("node didnt move:" + err.data.Message); 
+	 *    });
+	 * 
+ * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } - /** - * @ngdoc method - * @name umbraco.resources.contentResource#copy - * @methodOf umbraco.resources.contentResource - * - * @description - * Copies a node underneath a new parentId - * - * ##usage - *
-         * contentResource.copy({ parentId: 1244, id: 123 })
-         *    .then(function() {
-         *        alert("node was copied");
-         *    }, function(err){
-         *      alert("node wasnt copy:" + err.data.Message); 
-         *    });
-         * 
- * @param {Object} args arguments object - * @param {Int} args.id the ID of the node to copy - * @param {Int} args.parentId the ID of the parent node to copy to - * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api - * @returns {Promise} resourcePromise object. - * - */ - copy: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move content'); + }, - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"), - args), - 'Failed to copy content'); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#copy + * @methodOf umbraco.resources.contentResource + * + * @description + * Copies a node underneath a new parentId + * + * ##usage + *
+	 * contentResource.copy({ parentId: 1244, id: 123 })
+	 *    .then(function() {
+	 *        alert("node was copied");
+	 *    }, function(err){
+	 *      alert("node wasnt copy:" + err.data.Message); 
+	 *    });
+	 * 
+ * @param {Object} args arguments object + * @param {Int} args.id the ID of the node to copy + * @param {Int} args.parentId the ID of the parent node to copy to + * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api + * @returns {Promise} resourcePromise object. + * + */ + copy: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } - /** - * @ngdoc method - * @name umbraco.resources.contentResource#unPublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Unpublishes a content item with a given Id - * - * ##usage - *
-         * contentResource.unPublish(1234)
-         *    .then(function() {
-         *        alert("node was unpulished");
-         *    }, function(err){
-         *      alert("node wasnt unpublished:" + err.data.Message); 
-         *    });
-         * 
- * @param {Int} id the ID of the node to unpublish - * @returns {Promise} resourcePromise object. - * - */ - unPublish: function (id) { - if (!id) { - throw "id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostUnPublish", - [{ id: id }])), - 'Failed to publish content with id ' + id); - }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#emptyRecycleBin - * @methodOf umbraco.resources.contentResource - * - * @description - * Empties the content recycle bin - * - * ##usage - *
-         * contentResource.emptyRecycleBin()
-         *    .then(function() {
-         *        alert('its empty!');
-         *    });
-         * 
- * - * @returns {Promise} resourcePromise object. - * - */ - emptyRecycleBin: function() { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "EmptyRecycleBin")), - 'Failed to empty the recycle bin'); - }, + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"), + args), + 'Failed to copy content'); + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#deleteById - * @methodOf umbraco.resources.contentResource - * - * @description - * Deletes a content item with a given id - * - * ##usage - *
-         * contentResource.deleteById(1234)
-         *    .then(function() {
-         *        alert('its gone!');
-         *    });
-         * 
- * - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteById: function(id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#unPublish + * @methodOf umbraco.resources.contentResource + * + * @description + * Unpublishes a content item with a given Id + * + * ##usage + *
+	 * contentResource.unPublish(1234)
+	 *    .then(function() {
+	 *        alert("node was unpulished");
+	 *    }, function(err){
+	 *      alert("node wasnt unpublished:" + err.data.Message); 
+	 *    });
+	 * 
+ * @param {Int} id the ID of the node to unpublish + * @returns {Promise} resourcePromise object. + * + */ + unPublish: function (id) { + if (!id) { + throw "id cannot be null"; + } - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getById - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets a content item with a given id - * - * ##usage - *
-         * contentResource.getById(1234)
-         *    .then(function(content) {
-         *        var myDoc = content; 
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {Int} id id of content item to return - * @returns {Promise} resourcePromise object containing the content item. - * - */ - getById: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve data for content id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getByIds - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets an array of content items, given a collection of ids - * - * ##usage - *
-         * contentResource.getByIds( [1234,2526,28262])
-         *    .then(function(contentArray) {
-         *        var myDoc = contentArray; 
-         *        alert('they are here!');
-         *    });
-         * 
- * - * @param {Array} ids ids of content items to return as an array - * @returns {Promise} resourcePromise object containing the content items array. - * - */ - getByIds: function (ids) { - - var idQuery = ""; - _.each(ids, function(item) { - idQuery += "ids=" + item + "&"; - }); + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostUnPublish", + [{ id: id }])), + 'Failed to publish content with id ' + id); + }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#emptyRecycleBin + * @methodOf umbraco.resources.contentResource + * + * @description + * Empties the content recycle bin + * + * ##usage + *
+	 * contentResource.emptyRecycleBin()
+	 *    .then(function() {
+	 *        alert('its empty!');
+	 *    });
+	 * 
+ * + * @returns {Promise} resourcePromise object. + * + */ + emptyRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); + }, - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for content with multiple ids'); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#deleteById + * @methodOf umbraco.resources.contentResource + * + * @description + * Deletes a content item with a given id + * + * ##usage + *
+	 * contentResource.deleteById(1234)
+	 *    .then(function() {
+	 *        alert('its gone!');
+	 *    });
+	 * 
+ * + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete item ' + id); + }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getScaffold - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. - * - * - Parent Id must be provided so umbraco knows where to store the content - * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold - * - * The scaffold is used to build editors for content that has not yet been populated with data. - * - * ##usage - *
-         * contentResource.getScaffold(1234, 'homepage')
-         *    .then(function(scaffold) {
-         *        var myDoc = scaffold;
-         *        myDoc.name = "My new document"; 
-         *
-         *        contentResource.publish(myDoc, true)
-         *            .then(function(content){
-         *                alert("Retrieved, updated and published again");
-         *            });
-         *    });
-         * 
- * - * @param {Int} parentId id of content item to return - * @param {String} alias contenttype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the content scaffold. - * - */ - getScaffold: function (parentId, alias) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty content item type ' + alias); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getById + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets a content item with a given id + * + * ##usage + *
+	 * contentResource.getById(1234)
+	 *    .then(function(content) {
+	 *        var myDoc = content; 
+	 *        alert('its here!');
+	 *    });
+	 * 
+ * + * @param {Int} id id of content item to return + * @returns {Promise} resourcePromise object containing the content item. + * + */ + getById: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve data for content id ' + id); + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getNiceUrl - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a url, given a node ID - * - * ##usage - *
-         * contentResource.getNiceUrl(id)
-         *    .then(function(url) {
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {Int} id Id of node to return the public url to - * @returns {Promise} resourcePromise object containing the url. - * - */ - getNiceUrl: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetNiceUrl",[{id: id}])), - 'Failed to retrieve url for id:' + id); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getByIds + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets an array of content items, given a collection of ids + * + * ##usage + *
+	 * contentResource.getByIds( [1234,2526,28262])
+	 *    .then(function(contentArray) {
+	 *        var myDoc = contentArray; 
+	 *        alert('they are here!');
+	 *    });
+	 * 
+ * + * @param {Array} ids ids of content items to return as an array + * @returns {Promise} resourcePromise object containing the content items array. + * + */ + getByIds: function (ids) { - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getChildren - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets children of a content item with a given id - * - * ##usage - *
-         * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
-         *    .then(function(contentArray) {
-         *        var children = contentArray; 
-         *        alert('they are here!');
-         *    });
-         * 
- * - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getChildren: function (parentId, options) { + var idQuery = ""; + _.each(ids, function (item) { + idQuery += "ids=" + item + "&"; + }); - var defaults = { - pageSize: 0, - pageNumber: 0, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder" - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetByIds", + idQuery)), + 'Failed to retrieve data for content with multiple ids'); + }, - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetChildren", - [ - { id: parentId }, - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { filter: options.filter } - ])), - 'Failed to retrieve children for content item ' + parentId); - }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#hasPermission - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns true/false given a permission char to check against a nodeID - * for the current user - * - * ##usage - *
-         * contentResource.hasPermission('p',1234)
-         *    .then(function() {
-         *        alert('You are allowed to publish this item');
-         *    });
-         * 
- * - * @param {String} permission char representing the permission to check - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ - checkPermission: function(permission, id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "HasPermission", - [{ permissionToCheck: permission },{ nodeId: id }])), - 'Failed to check permission for item ' + id); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getScaffold + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. + * + * - Parent Id must be provided so umbraco knows where to store the content + * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold + * + * The scaffold is used to build editors for content that has not yet been populated with data. + * + * ##usage + *
+	 * contentResource.getScaffold(1234, 'homepage')
+	 *    .then(function(scaffold) {
+	 *        var myDoc = scaffold;
+	 *        myDoc.name = "My new document"; 
+	 *
+	 *        contentResource.publish(myDoc, true)
+	 *            .then(function(content){
+	 *                alert("Retrieved, updated and published again");
+	 *            });
+	 *    });
+	 * 
+ * + * @param {Int} parentId id of content item to return + * @param {String} alias contenttype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the content scaffold. + * + */ + getScaffold: function (parentId, alias) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty content item type ' + alias); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getNiceUrl + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a url, given a node ID + * + * ##usage + *
+	 * contentResource.getNiceUrl(id)
+	 *    .then(function(url) {
+	 *        alert('its here!');
+	 *    });
+	 * 
+ * + * @param {Int} id Id of node to return the public url to + * @returns {Promise} resourcePromise object containing the url. + * + */ + getNiceUrl: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetNiceUrl", [{ id: id }])), + 'Failed to retrieve url for id:' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getChildren + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets children of a content item with a given id + * + * ##usage + *
+	 * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
+	 *    .then(function(contentArray) {
+	 *        var children = contentArray; 
+	 *        alert('they are here!');
+	 *    });
+	 * 
+ * + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getChildren: function (parentId, options) { + + var defaults = { + pageSize: 0, + pageNumber: 0, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + orderBySystemField: true + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetChildren", + [ + { id: parentId }, + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: options.orderBySystemField }, + { filter: options.filter } + ])), + 'Failed to retrieve children for content item ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#hasPermission + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns true/false given a permission char to check against a nodeID + * for the current user + * + * ##usage + *
+	 * contentResource.hasPermission('p',1234)
+	 *    .then(function() {
+	 *        alert('You are allowed to publish this item');
+	 *    });
+	 * 
+ * + * @param {String} permission char representing the permission to check + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ + checkPermission: function (permission, id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "HasPermission", + [{ permissionToCheck: permission }, { nodeId: id }])), + 'Failed to check permission for item ' + id); + }, getPermissions: function (nodeIds) { return umbRequestHelper.resourcePromise( @@ -519,140 +521,140 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { 'Failed to get permissions'); }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#save - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
-         * contentResource.getById(1234)
-         *    .then(function(content) {
-         *          content.name = "I want a new name!";
-         *          contentResource.save(content, false)
-         *            .then(function(content){
-         *                alert("Retrieved, updated and saved again");
-         *            });
-         *    });
-         * 
- * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - save: function (content, isNew, files) { - return saveContentItem(content, "save" + (isNew ? "New" : ""), files); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#save + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
+	 * contentResource.getById(1234)
+	 *    .then(function(content) {
+	 *          content.name = "I want a new name!";
+	 *          contentResource.save(content, false)
+	 *            .then(function(content){
+	 *                alert("Retrieved, updated and saved again");
+	 *            });
+	 *    });
+	 * 
+ * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + save: function (content, isNew, files) { + return saveContentItem(content, "save" + (isNew ? "New" : ""), files); + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#publish - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
-         * contentResource.getById(1234)
-         *    .then(function(content) {
-         *          content.name = "I want a new name, and be published!";
-         *          contentResource.publish(content, false)
-         *            .then(function(content){
-         *                alert("Retrieved, updated and published again");
-         *            });
-         *    });
-         * 
- * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - publish: function (content, isNew, files) { - return saveContentItem(content, "publish" + (isNew ? "New" : ""), files); - }, - - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#sendToPublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves changes made to a content item, and notifies any subscribers about a pending publication - * - * ##usage - *
-         * contentResource.getById(1234)
-         *    .then(function(content) {
-         *          content.name = "I want a new name, and be published!";
-         *          contentResource.sendToPublish(content, false)
-         *            .then(function(content){
-         *                alert("Retrieved, updated and notication send off");
-         *            });
-         *    });
-         * 
- * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - sendToPublish: function (content, isNew, files) { - return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#publishByid - * @methodOf umbraco.resources.contentResource - * - * @description - * Publishes a content item with a given ID - * - * ##usage - *
-         * contentResource.publishById(1234)
-         *    .then(function(content) {
-         *        alert("published");
-         *    });
-         * 
- * - * @param {Int} id The ID of the conten to publish - * @returns {Promise} resourcePromise object containing the published content item. - * - */ - publishById: function(id){ - - if (!id) { - throw "id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostPublishById", - [{ id: id }])), - 'Failed to publish content with id ' + id); - - } + /** + * @ngdoc method + * @name umbraco.resources.contentResource#publish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
+	 * contentResource.getById(1234)
+	 *    .then(function(content) {
+	 *          content.name = "I want a new name, and be published!";
+	 *          contentResource.publish(content, false)
+	 *            .then(function(content){
+	 *                alert("Retrieved, updated and published again");
+	 *            });
+	 *    });
+	 * 
+ * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + publish: function (content, isNew, files) { + return saveContentItem(content, "publish" + (isNew ? "New" : ""), files); + }, - }; + /** + * @ngdoc method + * @name umbraco.resources.contentResource#sendToPublish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item, and notifies any subscribers about a pending publication + * + * ##usage + *
+	 * contentResource.getById(1234)
+	 *    .then(function(content) {
+	 *          content.name = "I want a new name, and be published!";
+	 *          contentResource.sendToPublish(content, false)
+	 *            .then(function(content){
+	 *                alert("Retrieved, updated and notication send off");
+	 *            });
+	 *    });
+	 * 
+ * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + sendToPublish: function (content, isNew, files) { + return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#publishByid + * @methodOf umbraco.resources.contentResource + * + * @description + * Publishes a content item with a given ID + * + * ##usage + *
+	 * contentResource.publishById(1234)
+	 *    .then(function(content) {
+	 *        alert("published");
+	 *    });
+	 * 
+ * + * @param {Int} id The ID of the conten to publish + * @returns {Promise} resourcePromise object containing the published content item. + * + */ + publishById: function (id) { + + if (!id) { + throw "id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostPublishById", + [{ id: id }])), + 'Failed to publish content with id ' + id); + + } + + + }; } angular.module('umbraco.resources').factory('contentResource', contentResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index 9a2310299f..887ed661a7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -4,470 +4,472 @@ * @description Loads in data for media **/ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { - - /** internal method process the saving of data and post processing the result */ - function saveMediaItem(content, action, files) { - return umbRequestHelper.postSaveContent({ - restApiUrl: umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "PostSave"), - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatMediaPostData(c, a); - } - }); - } - return { - - getRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for media recycle bin'); - }, + /** internal method process the saving of data and post processing the result */ + function saveMediaItem(content, action, files) { + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "PostSave"), + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatMediaPostData(c, a); + } + }); + } - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#sort - * @methodOf umbraco.resources.mediaResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
-         * var ids = [123,34533,2334,23434];
-         * mediaResource.sort({ sortedIds: ids })
-         *    .then(function() {
-         *        $scope.complete = true;
-         *    });
-         * 
- * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ - sort: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.sortedIds) { - throw "args.sortedIds cannot be null"; - } + return { - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort media'); - }, + getRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRecycleBin")), + 'Failed to retrieve data for media recycle bin'); + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#move - * @methodOf umbraco.resources.mediaResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
-         * mediaResource.move({ parentId: 1244, id: 123 })
-         *    .then(function() {
-         *        alert("node was moved");
-         *    }, function(err){
-         *      alert("node didnt move:" + err.data.Message); 
-         *    });
-         * 
- * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#sort + * @methodOf umbraco.resources.mediaResource + * + * @description + * Sorts all children below a given parent node id, based on a collection of node-ids + * + * ##usage + *
+	 * var ids = [123,34533,2334,23434];
+	 * mediaResource.sort({ sortedIds: ids })
+	 *    .then(function() {
+	 *        $scope.complete = true;
+	 *    });
+	 * 
+ * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ + sort: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.sortedIds) { + throw "args.sortedIds cannot be null"; + } - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move media'); - }, + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), + { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), + 'Failed to sort media'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#move + * @methodOf umbraco.resources.mediaResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
+	 * mediaResource.move({ parentId: 1244, id: 123 })
+	 *    .then(function() {
+	 *        alert("node was moved");
+	 *    }, function(err){
+	 *      alert("node didnt move:" + err.data.Message); 
+	 *    });
+	 * 
+ * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move media'); + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets a media item with a given id - * - * ##usage - *
-         * mediaResource.getById(1234)
-         *    .then(function(media) {
-         *        var myMedia = media; 
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {Int} id id of media item to return - * @returns {Promise} resourcePromise object containing the media item. - * - */ - getById: function (id) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve data for media id ' + id); - }, + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets a media item with a given id + * + * ##usage + *
+	 * mediaResource.getById(1234)
+	 *    .then(function(media) {
+	 *        var myMedia = media; 
+	 *        alert('its here!');
+	 *    });
+	 * 
+ * + * @param {Int} id id of media item to return + * @returns {Promise} resourcePromise object containing the media item. + * + */ + getById: function (id) { - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#deleteById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Deletes a media item with a given id - * - * ##usage - *
-         * mediaResource.deleteById(1234)
-         *    .then(function() {
-         *        alert('its gone!');
-         *    });
-         * 
- * - * @param {Int} id id of media item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteById: function(id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); - }, + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve data for media id ' + id); + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getByIds - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets an array of media items, given a collection of ids - * - * ##usage - *
-         * mediaResource.getByIds( [1234,2526,28262])
-         *    .then(function(mediaArray) {
-         *        var myDoc = contentArray; 
-         *        alert('they are here!');
-         *    });
-         * 
- * - * @param {Array} ids ids of media items to return as an array - * @returns {Promise} resourcePromise object containing the media items array. - * - */ - getByIds: function (ids) { - - var idQuery = ""; - _.each(ids, function(item) { - idQuery += "ids=" + item + "&"; - }); + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#deleteById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Deletes a media item with a given id + * + * ##usage + *
+	 * mediaResource.deleteById(1234)
+	 *    .then(function() {
+	 *        alert('its gone!');
+	 *    });
+	 * 
+ * + * @param {Int} id id of media item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete item ' + id); + }, - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for media ids ' + ids); - }, + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getByIds + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets an array of media items, given a collection of ids + * + * ##usage + *
+	 * mediaResource.getByIds( [1234,2526,28262])
+	 *    .then(function(mediaArray) {
+	 *        var myDoc = contentArray; 
+	 *        alert('they are here!');
+	 *    });
+	 * 
+ * + * @param {Array} ids ids of media items to return as an array + * @returns {Promise} resourcePromise object containing the media items array. + * + */ + getByIds: function (ids) { - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getScaffold - * @methodOf umbraco.resources.mediaResource - * - * @description - * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. - * - * - Parent Id must be provided so umbraco knows where to store the media - * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold - * - * The scaffold is used to build editors for media that has not yet been populated with data. - * - * ##usage - *
-         * mediaResource.getScaffold(1234, 'folder')
-         *    .then(function(scaffold) {
-         *        var myDoc = scaffold;
-         *        myDoc.name = "My new media item"; 
-         *
-         *        mediaResource.save(myDoc, true)
-         *            .then(function(media){
-         *                alert("Retrieved, updated and saved again");
-         *            });
-         *    });
-         * 
- * - * @param {Int} parentId id of media item to return - * @param {String} alias mediatype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the media scaffold. - * - */ - getScaffold: function (parentId, alias) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty media item type ' + alias); + var idQuery = ""; + _.each(ids, function (item) { + idQuery += "ids=" + item + "&"; + }); - }, + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetByIds", + idQuery)), + 'Failed to retrieve data for media ids ' + ids); + }, - rootMedia: function () { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRootMedia")), - 'Failed to retrieve data for root media'); + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getScaffold + * @methodOf umbraco.resources.mediaResource + * + * @description + * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. + * + * - Parent Id must be provided so umbraco knows where to store the media + * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold + * + * The scaffold is used to build editors for media that has not yet been populated with data. + * + * ##usage + *
+	 * mediaResource.getScaffold(1234, 'folder')
+	 *    .then(function(scaffold) {
+	 *        var myDoc = scaffold;
+	 *        myDoc.name = "My new media item"; 
+	 *
+	 *        mediaResource.save(myDoc, true)
+	 *            .then(function(media){
+	 *                alert("Retrieved, updated and saved again");
+	 *            });
+	 *    });
+	 * 
+ * + * @param {Int} parentId id of media item to return + * @param {String} alias mediatype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the media scaffold. + * + */ + getScaffold: function (parentId, alias) { - }, + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty media item type ' + alias); - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildren - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets children of a media item with a given id - * - * ##usage - *
-         * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
-         *    .then(function(contentArray) {
-         *        var children = contentArray; 
-         *        alert('they are here!');
-         *    });
-         * 
- * - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getChildren: function (parentId, options) { + }, - var defaults = { - pageSize: 0, - pageNumber: 0, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder" - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } + rootMedia: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildren", - [ - { id: parentId }, - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { filter: options.filter } - ])), - 'Failed to retrieve children for media item ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#save - * @methodOf umbraco.resources.mediaResource - * - * @description - * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation - * if the media item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
-         * mediaResource.getById(1234)
-         *    .then(function(media) {
-         *          media.name = "I want a new name!";
-         *          mediaResource.save(media, false)
-         *            .then(function(media){
-         *                alert("Retrieved, updated and saved again");
-         *            });
-         *    });
-         * 
- * - * @param {Object} media The media item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the media item - * @returns {Promise} resourcePromise object containing the saved media item. - * - */ - save: function (media, isNew, files) { - return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); - }, + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRootMedia")), + 'Failed to retrieve data for root media'); - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#addFolder - * @methodOf umbraco.resources.mediaResource - * - * @description - * Shorthand for adding a media item of the type "Folder" under a given parent ID - * - * ##usage - *
-         * mediaResource.addFolder("My gallery", 1234)
-         *    .then(function(folder) {
-         *        alert('New folder');
-         *    });
-         * 
- * - * @param {string} name Name of the folder to create - * @param {int} parentId Id of the media item to create the folder underneath - * @returns {Promise} resourcePromise object. - * - */ - addFolder: function(name, parentId){ - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper - .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), - { - name: name, - parentId: parentId - }), - 'Failed to add folder'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildFolders - * @methodOf umbraco.resources.mediaResource - * - * @description - * Retrieves all media children with types used as folders. - * Uses the convention of looking for media items with mediaTypes ending in - * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, - * - * ##usage - *
-         * mediaResource.getChildFolders(1234)
-         *    .then(function(data) {
-         *        alert('folders');
-         *    });
-         * 
- * - * @param {int} parentId Id of the media item to query for child folders - * @returns {Promise} resourcePromise object. - * - */ - getChildFolders: function(parentId){ - if(!parentId){ - parentId = -1; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildFolders", - [ - { id: parentId } - ])), - 'Failed to retrieve child folders for media item ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#emptyRecycleBin - * @methodOf umbraco.resources.mediaResource - * - * @description - * Empties the media recycle bin - * - * ##usage - *
-         * mediaResource.emptyRecycleBin()
-         *    .then(function() {
-         *        alert('its empty!');
-         *    });
-         * 
- * - * @returns {Promise} resourcePromise object. - * - */ - emptyRecycleBin: function() { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "EmptyRecycleBin")), - 'Failed to empty the recycle bin'); - } - }; + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildren + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets children of a media item with a given id + * + * ##usage + *
+	 * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
+	 *    .then(function(contentArray) {
+	 *        var children = contentArray; 
+	 *        alert('they are here!');
+	 *    });
+	 * 
+ * + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getChildren: function (parentId, options) { + + var defaults = { + pageSize: 0, + pageNumber: 0, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + orderBySystemField: true + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildren", + [ + { id: parentId }, + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: options.orderBySystemField }, + { filter: options.filter } + ])), + 'Failed to retrieve children for media item ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#save + * @methodOf umbraco.resources.mediaResource + * + * @description + * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation + * if the media item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
+	 * mediaResource.getById(1234)
+	 *    .then(function(media) {
+	 *          media.name = "I want a new name!";
+	 *          mediaResource.save(media, false)
+	 *            .then(function(media){
+	 *                alert("Retrieved, updated and saved again");
+	 *            });
+	 *    });
+	 * 
+ * + * @param {Object} media The media item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the media item + * @returns {Promise} resourcePromise object containing the saved media item. + * + */ + save: function (media, isNew, files) { + return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#addFolder + * @methodOf umbraco.resources.mediaResource + * + * @description + * Shorthand for adding a media item of the type "Folder" under a given parent ID + * + * ##usage + *
+	 * mediaResource.addFolder("My gallery", 1234)
+	 *    .then(function(folder) {
+	 *        alert('New folder');
+	 *    });
+	 * 
+ * + * @param {string} name Name of the folder to create + * @param {int} parentId Id of the media item to create the folder underneath + * @returns {Promise} resourcePromise object. + * + */ + addFolder: function (name, parentId) { + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper + .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), + { + name: name, + parentId: parentId + }), + 'Failed to add folder'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildFolders + * @methodOf umbraco.resources.mediaResource + * + * @description + * Retrieves all media children with types used as folders. + * Uses the convention of looking for media items with mediaTypes ending in + * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, + * + * ##usage + *
+	 * mediaResource.getChildFolders(1234)
+	 *    .then(function(data) {
+	 *        alert('folders');
+	 *    });
+	 * 
+ * + * @param {int} parentId Id of the media item to query for child folders + * @returns {Promise} resourcePromise object. + * + */ + getChildFolders: function (parentId) { + if (!parentId) { + parentId = -1; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildFolders", + [ + { id: parentId } + ])), + 'Failed to retrieve child folders for media item ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#emptyRecycleBin + * @methodOf umbraco.resources.mediaResource + * + * @description + * Empties the media recycle bin + * + * ##usage + *
+	 * mediaResource.emptyRecycleBin()
+	 *    .then(function() {
+	 *        alert('its empty!');
+	 *    });
+	 * 
+ * + * @returns {Promise} resourcePromise object. + * + */ + emptyRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); + } + }; } angular.module('umbraco.resources').factory('mediaResource', mediaResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js index 42db4f6366..c231a4944c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js @@ -4,230 +4,232 @@ * @description Loads in data for members **/ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { - - /** internal method process the saving of data and post processing the result */ - function saveMember(content, action, files) { - - return umbRequestHelper.postSaveContent({ - restApiUrl: umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "PostSave"), - content: content, - action: action, - files: files, - dataFormatter: function(c, a) { - return umbDataFormatter.formatMemberPostData(c, a); - } - }); - } - return { - - getPagedResults: function (memberTypeAlias, options) { + /** internal method process the saving of data and post processing the result */ + function saveMember(content, action, files) { - if (memberTypeAlias === 'all-members') { - memberTypeAlias = null; - } + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "PostSave"), + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatMemberPostData(c, a); + } + }); + } - var defaults = { - pageSize: 25, - pageNumber: 1, - filter: '', - orderDirection: "Ascending", - orderBy: "LoginName" - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } + return { - var params = [ - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { filter: options.filter } - ]; - if (memberTypeAlias != null) { - params.push({ memberTypeAlias: memberTypeAlias }); - } + getPagedResults: function (memberTypeAlias, options) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetPagedResults", - params)), - 'Failed to retrieve member paged result'); - }, - - getListNode: function (listName) { + if (memberTypeAlias === 'all-members') { + memberTypeAlias = null; + } - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetListNodeDisplay", - [{ listName: listName }])), - 'Failed to retrieve data for member list ' + listName); - }, + var defaults = { + pageSize: 25, + pageNumber: 1, + filter: '', + orderDirection: "Ascending", + orderBy: "LoginName", + orderBySystemField: true + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } - /** - * @ngdoc method - * @name umbraco.resources.memberResource#getByKey - * @methodOf umbraco.resources.memberResource - * - * @description - * Gets a member item with a given key - * - * ##usage - *
-         * memberResource.getByKey("0000-0000-000-00000-000")
-         *    .then(function(member) {
-         *        var mymember = member; 
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {Guid} key key of member item to return - * @returns {Promise} resourcePromise object containing the member item. - * - */ - getByKey: function (key) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetByKey", - [{ key: key }])), - 'Failed to retrieve data for member id ' + key); - }, + var params = [ + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: options.orderBySystemField }, + { filter: options.filter } + ]; + if (memberTypeAlias != null) { + params.push({ memberTypeAlias: memberTypeAlias }); + } - /** - * @ngdoc method - * @name umbraco.resources.memberResource#deleteByKey - * @methodOf umbraco.resources.memberResource - * - * @description - * Deletes a member item with a given key - * - * ##usage - *
-         * memberResource.deleteByKey("0000-0000-000-00000-000")
-         *    .then(function() {
-         *        alert('its gone!');
-         *    });
-         * 
- * - * @param {Guid} key id of member item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteByKey: function (key) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "DeleteByKey", - [{ key: key }])), - 'Failed to delete item ' + key); - }, + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetPagedResults", + params)), + 'Failed to retrieve member paged result'); + }, - /** - * @ngdoc method - * @name umbraco.resources.memberResource#getScaffold - * @methodOf umbraco.resources.memberResource - * - * @description - * Returns a scaffold of an empty member item, given the id of the member item to place it underneath and the member type alias. - * - * - Member Type alias must be provided so umbraco knows which properties to put on the member scaffold - * - * The scaffold is used to build editors for member that has not yet been populated with data. - * - * ##usage - *
-         * memberResource.getScaffold('client')
-         *    .then(function(scaffold) {
-         *        var myDoc = scaffold;
-         *        myDoc.name = "My new member item"; 
-         *
-         *        memberResource.save(myDoc, true)
-         *            .then(function(member){
-         *                alert("Retrieved, updated and saved again");
-         *            });
-         *    });
-         * 
- * - * @param {String} alias membertype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the member scaffold. - * - */ - getScaffold: function (alias) { - - if (alias) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }])), - 'Failed to retrieve data for empty member item type ' + alias); - } - else { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetEmpty")), - 'Failed to retrieve data for empty member item type ' + alias); - } + getListNode: function (listName) { - }, - - /** - * @ngdoc method - * @name umbraco.resources.memberResource#save - * @methodOf umbraco.resources.memberResource - * - * @description - * Saves changes made to a member, if the member is new, the isNew paramater must be passed to force creation - * if the member needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
-         * memberResource.getBykey("23234-sd8djsd-3h8d3j-sdh8d")
-         *    .then(function(member) {
-         *          member.name = "Bob";
-         *          memberResource.save(member, false)
-         *            .then(function(member){
-         *                alert("Retrieved, updated and saved again");
-         *            });
-         *    });
-         * 
- * - * @param {Object} media The member item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the media item - * @returns {Promise} resourcePromise object containing the saved media item. - * - */ - save: function (member, isNew, files) { - return saveMember(member, "save" + (isNew ? "New" : ""), files); - } - }; + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetListNodeDisplay", + [{ listName: listName }])), + 'Failed to retrieve data for member list ' + listName); + }, + + /** + * @ngdoc method + * @name umbraco.resources.memberResource#getByKey + * @methodOf umbraco.resources.memberResource + * + * @description + * Gets a member item with a given key + * + * ##usage + *
+	 * memberResource.getByKey("0000-0000-000-00000-000")
+	 *    .then(function(member) {
+	 *        var mymember = member; 
+	 *        alert('its here!');
+	 *    });
+	 * 
+ * + * @param {Guid} key key of member item to return + * @returns {Promise} resourcePromise object containing the member item. + * + */ + getByKey: function (key) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetByKey", + [{ key: key }])), + 'Failed to retrieve data for member id ' + key); + }, + + /** + * @ngdoc method + * @name umbraco.resources.memberResource#deleteByKey + * @methodOf umbraco.resources.memberResource + * + * @description + * Deletes a member item with a given key + * + * ##usage + *
+	 * memberResource.deleteByKey("0000-0000-000-00000-000")
+	 *    .then(function() {
+	 *        alert('its gone!');
+	 *    });
+	 * 
+ * + * @param {Guid} key id of member item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteByKey: function (key) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "DeleteByKey", + [{ key: key }])), + 'Failed to delete item ' + key); + }, + + /** + * @ngdoc method + * @name umbraco.resources.memberResource#getScaffold + * @methodOf umbraco.resources.memberResource + * + * @description + * Returns a scaffold of an empty member item, given the id of the member item to place it underneath and the member type alias. + * + * - Member Type alias must be provided so umbraco knows which properties to put on the member scaffold + * + * The scaffold is used to build editors for member that has not yet been populated with data. + * + * ##usage + *
+	 * memberResource.getScaffold('client')
+	 *    .then(function(scaffold) {
+	 *        var myDoc = scaffold;
+	 *        myDoc.name = "My new member item"; 
+	 *
+	 *        memberResource.save(myDoc, true)
+	 *            .then(function(member){
+	 *                alert("Retrieved, updated and saved again");
+	 *            });
+	 *    });
+	 * 
+ * + * @param {String} alias membertype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the member scaffold. + * + */ + getScaffold: function (alias) { + + if (alias) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }])), + 'Failed to retrieve data for empty member item type ' + alias); + } + else { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetEmpty")), + 'Failed to retrieve data for empty member item type ' + alias); + } + + }, + + /** + * @ngdoc method + * @name umbraco.resources.memberResource#save + * @methodOf umbraco.resources.memberResource + * + * @description + * Saves changes made to a member, if the member is new, the isNew paramater must be passed to force creation + * if the member needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
+	 * memberResource.getBykey("23234-sd8djsd-3h8d3j-sdh8d")
+	 *    .then(function(member) {
+	 *          member.name = "Bob";
+	 *          memberResource.save(member, false)
+	 *            .then(function(member){
+	 *                alert("Retrieved, updated and saved again");
+	 *            });
+	 *    });
+	 * 
+ * + * @param {Object} media The member item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the media item + * @returns {Promise} resourcePromise object containing the saved media item. + * + */ + save: function (member, isNew, files) { + return saveMember(member, "save" + (isNew ? "New" : ""), files); + } + }; } angular.module('umbraco.resources').factory('memberResource', memberResource); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js index 1d2d361f28..de4902bdd1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js @@ -44,8 +44,9 @@ return listViewHelper.setSortingDirection(col, direction, $scope.options); } - function sort(field, allow) { + function sort(field, allow, isSystem) { if (allow) { + $scope.options.orderBySystemField = isSystem; listViewHelper.setSorting(field, allow, $scope.options); $scope.getContent($scope.contentId); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 59ea03aa91..fcb738a358 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -1,64 +1,64 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookieStore, notificationsService, iconHelper, dialogService, editorState, localizationService, $location, appState, $timeout, $q, mediaResource, listViewHelper, userService) { - //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content - // that isn't created yet, if we continue this will use the parent id in the route params which isn't what - // we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove - // the list view tab entirely when it's new. - if ($routeParams.create) { - $scope.isNew = true; - return; - } + //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content + // that isn't created yet, if we continue this will use the parent id in the route params which isn't what + // we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove + // the list view tab entirely when it's new. + if ($routeParams.create) { + $scope.isNew = true; + return; + } - //Now we need to check if this is for media, members or content because that will depend on the resources we use - var contentResource, getContentTypesCallback, getListResultsCallback, deleteItemCallback, getIdCallback, createEditUrlCallback; + //Now we need to check if this is for media, members or content because that will depend on the resources we use + var contentResource, getContentTypesCallback, getListResultsCallback, deleteItemCallback, getIdCallback, createEditUrlCallback; - //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) - if (($scope.model.config.entityType && $scope.model.config.entityType === "member") || (appState.getSectionState("currentSection") === "member")) { - $scope.entityType = "member"; - contentResource = $injector.get('memberResource'); - getContentTypesCallback = $injector.get('memberTypeResource').getTypes; - getListResultsCallback = contentResource.getPagedResults; - deleteItemCallback = contentResource.deleteByKey; - getIdCallback = function(selected) { - var selectedKey = getItemKey(selected.id); - return selectedKey; - }; - createEditUrlCallback = function(item) { - return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.key + "?page=" + $scope.options.pageNumber + "&listName=" + $scope.contentId; - }; - } - else { - //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) - if (($scope.model.config.entityType && $scope.model.config.entityType === "media") || (appState.getSectionState("currentSection") === "media")) { - $scope.entityType = "media"; - contentResource = $injector.get('mediaResource'); - getContentTypesCallback = $injector.get('mediaTypeResource').getAllowedTypes; - } - else { - $scope.entityType = "content"; - contentResource = $injector.get('contentResource'); - getContentTypesCallback = $injector.get('contentTypeResource').getAllowedTypes; - } - getListResultsCallback = contentResource.getChildren; - deleteItemCallback = contentResource.deleteById; - getIdCallback = function(selected) { - return selected.id; - }; - createEditUrlCallback = function(item) { - return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.id + "?page=" + $scope.options.pageNumber; - }; - } + //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) + if (($scope.model.config.entityType && $scope.model.config.entityType === "member") || (appState.getSectionState("currentSection") === "member")) { + $scope.entityType = "member"; + contentResource = $injector.get('memberResource'); + getContentTypesCallback = $injector.get('memberTypeResource').getTypes; + getListResultsCallback = contentResource.getPagedResults; + deleteItemCallback = contentResource.deleteByKey; + getIdCallback = function (selected) { + var selectedKey = getItemKey(selected.id); + return selectedKey; + }; + createEditUrlCallback = function (item) { + return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.key + "?page=" + $scope.options.pageNumber + "&listName=" + $scope.contentId; + }; + } + else { + //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) + if (($scope.model.config.entityType && $scope.model.config.entityType === "media") || (appState.getSectionState("currentSection") === "media")) { + $scope.entityType = "media"; + contentResource = $injector.get('mediaResource'); + getContentTypesCallback = $injector.get('mediaTypeResource').getAllowedTypes; + } + else { + $scope.entityType = "content"; + contentResource = $injector.get('contentResource'); + getContentTypesCallback = $injector.get('contentTypeResource').getAllowedTypes; + } + getListResultsCallback = contentResource.getChildren; + deleteItemCallback = contentResource.deleteById; + getIdCallback = function (selected) { + return selected.id; + }; + createEditUrlCallback = function (item) { + return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.id + "?page=" + $scope.options.pageNumber; + }; + } + + $scope.pagination = []; + $scope.isNew = false; + $scope.actionInProgress = false; + $scope.selection = []; + $scope.folders = []; + $scope.listViewResultSet = { + totalPages: 0, + items: [] + }; - $scope.pagination = []; - $scope.isNew = false; - $scope.actionInProgress = false; - $scope.selection = []; - $scope.folders = []; - $scope.listViewResultSet = { - totalPages: 0, - items: [] - }; - $scope.currentNodePermissions = {} //Just ensure we do have an editorState @@ -135,433 +135,444 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie } - $scope.options = { - displayAtTabNumber: $scope.model.config.displayAtTabNumber ? $scope.model.config.displayAtTabNumber : 1, - pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10, - pageNumber: ($routeParams.page && Number($routeParams.page) != NaN && Number($routeParams.page) > 0) ? $routeParams.page : 1, - filter: '', - orderBy: ($scope.model.config.orderBy ? $scope.model.config.orderBy : 'VersionDate').trim(), - orderDirection: $scope.model.config.orderDirection ? $scope.model.config.orderDirection.trim() : "desc", - includeProperties: $scope.model.config.includeProperties ? $scope.model.config.includeProperties : [ - { alias: 'updateDate', header: 'Last edited', isSystem: 1 }, - { alias: 'updater', header: 'Last edited by', isSystem: 1 } - ], - layout: { - layouts: $scope.model.config.layouts, - activeLayout: listViewHelper.getLayout($routeParams.id, $scope.model.config.layouts) - }, - allowBulkPublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkPublish, - allowBulkUnpublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkUnpublish, - allowBulkCopy: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkCopy, - allowBulkMove: $scope.model.config.bulkActionPermissions.allowBulkMove, - allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete - }; + $scope.options = { + displayAtTabNumber: $scope.model.config.displayAtTabNumber ? $scope.model.config.displayAtTabNumber : 1, + pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10, + pageNumber: ($routeParams.page && Number($routeParams.page) != NaN && Number($routeParams.page) > 0) ? $routeParams.page : 1, + filter: '', + orderBy: ($scope.model.config.orderBy ? $scope.model.config.orderBy : 'VersionDate').trim(), + orderDirection: $scope.model.config.orderDirection ? $scope.model.config.orderDirection.trim() : "desc", + orderBySystemField: true, + includeProperties: $scope.model.config.includeProperties ? $scope.model.config.includeProperties : [ + { alias: 'updateDate', header: 'Last edited', isSystem: 1 }, + { alias: 'updater', header: 'Last edited by', isSystem: 1 } + ], + layout: { + layouts: $scope.model.config.layouts, + activeLayout: listViewHelper.getLayout($routeParams.id, $scope.model.config.layouts) + }, + allowBulkPublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkPublish, + allowBulkUnpublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkUnpublish, + allowBulkCopy: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkCopy, + allowBulkMove: $scope.model.config.bulkActionPermissions.allowBulkMove, + allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete + }; - //update all of the system includeProperties to enable sorting - _.each($scope.options.includeProperties, function(e, i) { + //update all of the system includeProperties to enable sorting + _.each($scope.options.includeProperties, function (e, i) { - if (e.isSystem) { - //NOTE: special case for contentTypeAlias, it's a system property that cannot be sorted - // to do that, we'd need to update the base query for content to include the content type alias column - // which requires another join and would be slower. BUT We are doing this for members so not sure it makes a diff? - if (e.alias != "contentTypeAlias") { - e.allowSorting = true; - } + //NOTE: special case for contentTypeAlias, it's a system property that cannot be sorted + // to do that, we'd need to update the base query for content to include the content type alias column + // which requires another join and would be slower. BUT We are doing this for members so not sure it makes a diff? + if (e.alias != "contentTypeAlias") { + e.allowSorting = true; + } - //localize the header - var key = getLocalizedKey(e.alias); - localizationService.localize(key).then(function (v) { - e.header = v; - }); - } + // Another special case for lasted edited data/update date for media, again this field isn't available on the base table so we can't sort by it + if (e.isSystem && $scope.entityType == "media") { + e.allowSorting = e.alias != 'updateDate'; + } + + // Another special case for members, only fields on the base table (cmsMember) can be used for sorting + if (e.isSystem && $scope.entityType == "member") { + e.allowSorting = e.alias == 'username' || e.alias == 'email'; + } + if (e.isSystem) { + + //localize the header + var key = getLocalizedKey(e.alias); + localizationService.localize(key).then(function (v) { + e.header = v; + }); + } + }); + + $scope.selectLayout = function (selectedLayout) { + $scope.options.layout.activeLayout = listViewHelper.setLayout($routeParams.id, selectedLayout, $scope.model.config.layouts); + }; + + function showNotificationsAndReset(err, reload, successMsg) { + + //check if response is ysod + if (err.status && err.status >= 500) { + + // Open ysod overlay + $scope.ysodOverlay = { + view: "ysod", + error: err, + show: true + }; + } + + $timeout(function () { + $scope.bulkStatus = ""; + $scope.actionInProgress = false; + }, 500); + + if (reload === true) { + $scope.reloadView($scope.contentId); + } + + if (err.data && angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + else if (successMsg) { + notificationsService.success("Done", successMsg); + } + } + + $scope.next = function (pageNumber) { + $scope.options.pageNumber = pageNumber; + $scope.reloadView($scope.contentId); + }; + + $scope.goToPage = function (pageNumber) { + $scope.options.pageNumber = pageNumber; + $scope.reloadView($scope.contentId); + }; + + $scope.prev = function (pageNumber) { + $scope.options.pageNumber = pageNumber; + $scope.reloadView($scope.contentId); + }; + + + /*Loads the search results, based on parameters set in prev,next,sort and so on*/ + /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state + with simple values */ + + $scope.reloadView = function (id) { + + $scope.viewLoaded = false; + + listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); + + getListResultsCallback(id, $scope.options).then(function (data) { + + $scope.actionInProgress = false; + $scope.listViewResultSet = data; + + //update all values for display + if ($scope.listViewResultSet.items) { + _.each($scope.listViewResultSet.items, function (e, index) { + setPropertyValues(e); }); - - $scope.selectLayout = function(selectedLayout) { - $scope.options.layout.activeLayout = listViewHelper.setLayout($routeParams.id, selectedLayout, $scope.model.config.layouts); - }; - - function showNotificationsAndReset(err, reload, successMsg) { - - //check if response is ysod - if(err.status && err.status >= 500) { - - // Open ysod overlay - $scope.ysodOverlay = { - view : "ysod", - error : err, - show : true - }; - } - - $timeout(function() { - $scope.bulkStatus = ""; - $scope.actionInProgress = false; - }, 500); - - if (reload === true) { - $scope.reloadView($scope.contentId); - } - - if (err.data && angular.isArray(err.data.notifications)) { - for (var i = 0; i < err.data.notifications.length; i++) { - notificationsService.showNotification(err.data.notifications[i]); - } - } - else if (successMsg) { - notificationsService.success("Done", successMsg); - } - } - - $scope.next = function(pageNumber) { - $scope.options.pageNumber = pageNumber; - $scope.reloadView($scope.contentId); - }; - - $scope.goToPage = function(pageNumber) { - $scope.options.pageNumber = pageNumber; - $scope.reloadView($scope.contentId); - }; - - $scope.prev = function(pageNumber) { - $scope.options.pageNumber = pageNumber; - $scope.reloadView($scope.contentId); - }; - - - /*Loads the search results, based on parameters set in prev,next,sort and so on*/ - /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state - with simple values */ - - $scope.reloadView = function(id) { - - $scope.viewLoaded = false; - - listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); - - getListResultsCallback(id, $scope.options).then(function(data) { - - $scope.actionInProgress = false; - $scope.listViewResultSet = data; - - //update all values for display - if ($scope.listViewResultSet.items) { - _.each($scope.listViewResultSet.items, function(e, index) { - setPropertyValues(e); - }); - } - - if ($scope.entityType === 'media') { - - mediaResource.getChildFolders($scope.contentId) - .then(function(folders) { - $scope.folders = folders; - $scope.viewLoaded = true; - }); - - } else { - $scope.viewLoaded = true; - } - - //NOTE: This might occur if we are requesting a higher page number than what is actually available, for example - // if you have more than one page and you delete all items on the last page. In this case, we need to reset to the last - // available page and then re-load again - if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) { - $scope.options.pageNumber = $scope.listViewResultSet.totalPages; - - //reload! - $scope.reloadView(id); - } - - }); - }; - - var searchListView = _.debounce(function(){ - $scope.$apply(function() { - makeSearch(); - }); - }, 500); - - $scope.forceSearch = function (ev) { - //13: enter - switch (ev.keyCode) { - case 13: - makeSearch(); - break; - } - }; - - $scope.enterSearch = function() { - $scope.viewLoaded = false; - searchListView(); - }; - - function makeSearch() { - if ($scope.options.filter !== null && $scope.options.filter !== undefined) { - $scope.options.pageNumber = 1; - //$scope.actionInProgress = true; - $scope.reloadView($scope.contentId); - } - } - - $scope.isAnythingSelected = function() { - if ($scope.selection.length === 0) { - return false; - } else { - return true; - } - }; - - $scope.selectedItemsCount = function() { - return $scope.selection.length; - }; - - $scope.clearSelection = function() { - listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); - }; - - $scope.getIcon = function(entry) { - return iconHelper.convertFromLegacyIcon(entry.icon); - }; - - function serial(selected, fn, getStatusMsg, index) { - return fn(selected, index).then(function (content) { - index++; - $scope.bulkStatus = getStatusMsg(index, selected.length); - return index < selected.length ? serial(selected, fn, getStatusMsg, index) : content; - }, function (err) { - var reload = index > 0; - showNotificationsAndReset(err, reload); - return err; - }); - } - - function applySelected(fn, getStatusMsg, getSuccessMsg, confirmMsg) { - var selected = $scope.selection; - if (selected.length === 0) - return; - if (confirmMsg && !confirm(confirmMsg)) - return; - - $scope.actionInProgress = true; - $scope.bulkStatus = getStatusMsg(0, selected.length); - - serial(selected, fn, getStatusMsg, 0).then(function (result) { - // executes once the whole selection has been processed - // in case of an error (caught by serial), result will be the error - if (!(result.data && angular.isArray(result.data.notifications))) - showNotificationsAndReset(result, true, getSuccessMsg(selected.length)); - }); - } - - $scope.delete = function () { - applySelected( - function (selected, index) { return deleteItemCallback(getIdCallback(selected[index])); }, - function (count, total) { return "Deleted " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Deleted " + total + " item" + (total > 1 ? "s" : ""); }, - "Sure you want to delete?"); - }; - - $scope.publish = function () { - applySelected( - function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); }, - function (count, total) { return "Published " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Published " + total + " item" + (total > 1 ? "s" : ""); }); - }; - - $scope.unpublish = function() { - applySelected( - function (selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); }, - function (count, total) { return "Unpublished " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Unpublished " + total + " item" + (total > 1 ? "s" : ""); }); - }; - - $scope.move = function() { - $scope.moveDialog = {}; - $scope.moveDialog.title = "Move"; - $scope.moveDialog.section = $scope.entityType; - $scope.moveDialog.currentNode = $scope.contentId; - $scope.moveDialog.view = "move"; - $scope.moveDialog.show = true; - - $scope.moveDialog.submit = function(model) { - - if(model.target) { - performMove(model.target); - } - - $scope.moveDialog.show = false; - $scope.moveDialog = null; - }; - - $scope.moveDialog.close = function(oldModel) { - $scope.moveDialog.show = false; - $scope.moveDialog = null; - }; - - }; - - function performMove(target) { - - applySelected( - function(selected, index) {return contentResource.move({parentId: target.id, id: getIdCallback(selected[index])}); }, - function(count, total) {return "Moved " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function(total) {return "Moved " + total + " item" + (total > 1 ? "s" : ""); }); - } - - $scope.copy = function() { - $scope.copyDialog = {}; - $scope.copyDialog.title = "Copy"; - $scope.copyDialog.section = $scope.entityType; - $scope.copyDialog.currentNode = $scope.contentId; - $scope.copyDialog.view = "copy"; - $scope.copyDialog.show = true; - - $scope.copyDialog.submit = function(model) { - if(model.target) { - performCopy(model.target, model.relateToOriginal); - } - - $scope.copyDialog.show = false; - $scope.copyDialog = null; - }; - - $scope.copyDialog.close = function(oldModel) { - $scope.copyDialog.show = false; - $scope.copyDialog = null; - }; - - }; - - function performCopy(target, relateToOriginal) { - applySelected( - function(selected, index) {return contentResource.copy({parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal}); }, - function(count, total) {return "Copied " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function(total) {return "Copied " + total + " item" + (total > 1 ? "s" : ""); }); - } - - function getCustomPropertyValue(alias, properties) { - var value = ''; - var index = 0; - var foundAlias = false; - for (var i = 0; i < properties.length; i++) { - if (properties[i].alias == alias) { - foundAlias = true; - break; - } - index++; - } - - if (foundAlias) { - value = properties[index].value; - } - - return value; - } - - /** This ensures that the correct value is set for each item in a row, we don't want to call a function during interpolation or ng-bind as performance is really bad that way */ - function setPropertyValues(result) { - - //set the edit url - result.editPath = createEditUrlCallback(result); - - _.each($scope.options.includeProperties, function (e, i) { - - var alias = e.alias; - - // First try to pull the value directly from the alias (e.g. updatedBy) - var value = result[alias]; - - // If this returns an object, look for the name property of that (e.g. owner.name) - if (value === Object(value)) { - value = value['name']; - } - - // If we've got nothing yet, look at a user defined property - if (typeof value === 'undefined') { - value = getCustomPropertyValue(alias, result.properties); - } - - // If we have a date, format it - if (isDate(value)) { - value = value.substring(0, value.length - 3); - } - - // set what we've got on the result - result[alias] = value; - }); - - - } - - function isDate(val) { - if (angular.isString(val)) { - return val.match(/^(\d{4})\-(\d{2})\-(\d{2})\ (\d{2})\:(\d{2})\:(\d{2})$/); - } - return false; - } - - function initView() { - //default to root id if the id is undefined - var id = $routeParams.id; - if(id === undefined){ - id = -1; - } - - $scope.listViewAllowedTypes = getContentTypesCallback(id); - - $scope.contentId = id; - $scope.isTrashed = id === "-20" || id === "-21"; - - $scope.options.allowBulkPublish = $scope.options.allowBulkPublish && !$scope.isTrashed; - $scope.options.allowBulkUnpublish = $scope.options.allowBulkUnpublish && !$scope.isTrashed; - - $scope.options.bulkActionsAllowed = $scope.options.allowBulkPublish || - $scope.options.allowBulkUnpublish || - $scope.options.allowBulkCopy || - $scope.options.allowBulkMove || - $scope.options.allowBulkDelete; - - $scope.reloadView($scope.contentId); - } - - function getLocalizedKey(alias) { - - switch (alias) { - case "sortOrder": - return "general_sort"; - case "updateDate": - return "content_updateDate"; - case "updater": - return "content_updatedBy"; - case "createDate": - return "content_createDate"; - case "owner": - return "content_createBy"; - case "published": - return "content_isPublished"; - case "contentTypeAlias": - //TODO: Check for members - return $scope.entityType === "content" ? "content_documentType" : "content_mediatype"; - case "email": - return "general_email"; - case "username": - return "general_username"; - } - return alias; - } - - function getItemKey(itemId) { - for (var i = 0; i < $scope.listViewResultSet.items.length; i++) { - var item = $scope.listViewResultSet.items[i]; - if (item.id === itemId) { - return item.key; - } - } - } - - //GO! - initView(); + } + + if ($scope.entityType === 'media') { + + mediaResource.getChildFolders($scope.contentId) + .then(function (folders) { + $scope.folders = folders; + $scope.viewLoaded = true; + }); + + } else { + $scope.viewLoaded = true; + } + + //NOTE: This might occur if we are requesting a higher page number than what is actually available, for example + // if you have more than one page and you delete all items on the last page. In this case, we need to reset to the last + // available page and then re-load again + if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) { + $scope.options.pageNumber = $scope.listViewResultSet.totalPages; + + //reload! + $scope.reloadView(id); + } + + }); + }; + + var searchListView = _.debounce(function () { + $scope.$apply(function () { + makeSearch(); + }); + }, 500); + + $scope.forceSearch = function (ev) { + //13: enter + switch (ev.keyCode) { + case 13: + makeSearch(); + break; + } + }; + + $scope.enterSearch = function () { + $scope.viewLoaded = false; + searchListView(); + }; + + function makeSearch() { + if ($scope.options.filter !== null && $scope.options.filter !== undefined) { + $scope.options.pageNumber = 1; + //$scope.actionInProgress = true; + $scope.reloadView($scope.contentId); + } + } + + $scope.isAnythingSelected = function () { + if ($scope.selection.length === 0) { + return false; + } else { + return true; + } + }; + + $scope.selectedItemsCount = function () { + return $scope.selection.length; + }; + + $scope.clearSelection = function () { + listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); + }; + + $scope.getIcon = function (entry) { + return iconHelper.convertFromLegacyIcon(entry.icon); + }; + + function serial(selected, fn, getStatusMsg, index) { + return fn(selected, index).then(function (content) { + index++; + $scope.bulkStatus = getStatusMsg(index, selected.length); + return index < selected.length ? serial(selected, fn, getStatusMsg, index) : content; + }, function (err) { + var reload = index > 0; + showNotificationsAndReset(err, reload); + return err; + }); + } + + function applySelected(fn, getStatusMsg, getSuccessMsg, confirmMsg) { + var selected = $scope.selection; + if (selected.length === 0) + return; + if (confirmMsg && !confirm(confirmMsg)) + return; + + $scope.actionInProgress = true; + $scope.bulkStatus = getStatusMsg(0, selected.length); + + serial(selected, fn, getStatusMsg, 0).then(function (result) { + // executes once the whole selection has been processed + // in case of an error (caught by serial), result will be the error + if (!(result.data && angular.isArray(result.data.notifications))) + showNotificationsAndReset(result, true, getSuccessMsg(selected.length)); + }); + } + + $scope.delete = function () { + applySelected( + function (selected, index) { return deleteItemCallback(getIdCallback(selected[index])); }, + function (count, total) { return "Deleted " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Deleted " + total + " item" + (total > 1 ? "s" : ""); }, + "Sure you want to delete?"); + }; + + $scope.publish = function () { + applySelected( + function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); }, + function (count, total) { return "Published " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Published " + total + " item" + (total > 1 ? "s" : ""); }); + }; + + $scope.unpublish = function () { + applySelected( + function (selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); }, + function (count, total) { return "Unpublished " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Unpublished " + total + " item" + (total > 1 ? "s" : ""); }); + }; + + $scope.move = function () { + $scope.moveDialog = {}; + $scope.moveDialog.title = "Move"; + $scope.moveDialog.section = $scope.entityType; + $scope.moveDialog.currentNode = $scope.contentId; + $scope.moveDialog.view = "move"; + $scope.moveDialog.show = true; + + $scope.moveDialog.submit = function (model) { + + if (model.target) { + performMove(model.target); + } + + $scope.moveDialog.show = false; + $scope.moveDialog = null; + }; + + $scope.moveDialog.close = function (oldModel) { + $scope.moveDialog.show = false; + $scope.moveDialog = null; + }; + + }; + + function performMove(target) { + + applySelected( + function (selected, index) { return contentResource.move({ parentId: target.id, id: getIdCallback(selected[index]) }); }, + function (count, total) { return "Moved " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Moved " + total + " item" + (total > 1 ? "s" : ""); }); + } + + $scope.copy = function () { + $scope.copyDialog = {}; + $scope.copyDialog.title = "Copy"; + $scope.copyDialog.section = $scope.entityType; + $scope.copyDialog.currentNode = $scope.contentId; + $scope.copyDialog.view = "copy"; + $scope.copyDialog.show = true; + + $scope.copyDialog.submit = function (model) { + if (model.target) { + performCopy(model.target, model.relateToOriginal); + } + + $scope.copyDialog.show = false; + $scope.copyDialog = null; + }; + + $scope.copyDialog.close = function (oldModel) { + $scope.copyDialog.show = false; + $scope.copyDialog = null; + }; + + }; + + function performCopy(target, relateToOriginal) { + applySelected( + function (selected, index) { return contentResource.copy({ parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal }); }, + function (count, total) { return "Copied " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Copied " + total + " item" + (total > 1 ? "s" : ""); }); + } + + function getCustomPropertyValue(alias, properties) { + var value = ''; + var index = 0; + var foundAlias = false; + for (var i = 0; i < properties.length; i++) { + if (properties[i].alias == alias) { + foundAlias = true; + break; + } + index++; + } + + if (foundAlias) { + value = properties[index].value; + } + + return value; + } + + /** This ensures that the correct value is set for each item in a row, we don't want to call a function during interpolation or ng-bind as performance is really bad that way */ + function setPropertyValues(result) { + + //set the edit url + result.editPath = createEditUrlCallback(result); + + _.each($scope.options.includeProperties, function (e, i) { + + var alias = e.alias; + + // First try to pull the value directly from the alias (e.g. updatedBy) + var value = result[alias]; + + // If this returns an object, look for the name property of that (e.g. owner.name) + if (value === Object(value)) { + value = value['name']; + } + + // If we've got nothing yet, look at a user defined property + if (typeof value === 'undefined') { + value = getCustomPropertyValue(alias, result.properties); + } + + // If we have a date, format it + if (isDate(value)) { + value = value.substring(0, value.length - 3); + } + + // set what we've got on the result + result[alias] = value; + }); + + + } + + function isDate(val) { + if (angular.isString(val)) { + return val.match(/^(\d{4})\-(\d{2})\-(\d{2})\ (\d{2})\:(\d{2})\:(\d{2})$/); + } + return false; + } + + function initView() { + //default to root id if the id is undefined + var id = $routeParams.id; + if (id === undefined) { + id = -1; + } + + $scope.listViewAllowedTypes = getContentTypesCallback(id); + + $scope.contentId = id; + $scope.isTrashed = id === "-20" || id === "-21"; + + $scope.options.allowBulkPublish = $scope.options.allowBulkPublish && !$scope.isTrashed; + $scope.options.allowBulkUnpublish = $scope.options.allowBulkUnpublish && !$scope.isTrashed; + + $scope.options.bulkActionsAllowed = $scope.options.allowBulkPublish || + $scope.options.allowBulkUnpublish || + $scope.options.allowBulkCopy || + $scope.options.allowBulkMove || + $scope.options.allowBulkDelete; + + $scope.reloadView($scope.contentId); + } + + function getLocalizedKey(alias) { + + switch (alias) { + case "sortOrder": + return "general_sort"; + case "updateDate": + return "content_updateDate"; + case "updater": + return "content_updatedBy"; + case "createDate": + return "content_createDate"; + case "owner": + return "content_createBy"; + case "published": + return "content_isPublished"; + case "contentTypeAlias": + //TODO: Check for members + return $scope.entityType === "content" ? "content_documentType" : "content_mediatype"; + case "email": + return "general_email"; + case "username": + return "general_username"; + } + return alias; + } + + function getItemKey(itemId) { + for (var i = 0; i < $scope.listViewResultSet.items.length; i++) { + var item = $scope.listViewResultSet.items[i]; + if (item.id === itemId) { + return item.key; + } + } + } + + //GO! + initView(); } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index a65821cfa0..ff811ce4b2 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -38,183 +38,186 @@ using Notification = Umbraco.Web.Models.ContentEditing.Notification; namespace Umbraco.Web.Editors { - /// - /// The API controller used for editing content - /// - /// - /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting - /// access to ALL of the methods on this controller will need access to the content application. - /// - [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content)] - public class ContentController : ContentControllerBase - { - /// - /// Constructor - /// - public ContentController() - : this(UmbracoContext.Current) - { - } + /// + /// The API controller used for editing content + /// + /// + /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting + /// access to ALL of the methods on this controller will need access to the content application. + /// + [PluginController("UmbracoApi")] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content)] + public class ContentController : ContentControllerBase + { + /// + /// Constructor + /// + public ContentController() + : this(UmbracoContext.Current) + { + } - /// - /// Constructor - /// - /// - public ContentController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - } + /// + /// Constructor + /// + /// + public ContentController(UmbracoContext umbracoContext) + : base(umbracoContext) + { + } - /// - /// Return content for the specified ids - /// - /// - /// - [FilterAllowedOutgoingContent(typeof(IEnumerable))] - public IEnumerable GetByIds([FromUri]int[] ids) - { - var foundContent = Services.ContentService.GetByIds(ids); - return foundContent.Select(Mapper.Map); - } + /// + /// Return content for the specified ids + /// + /// + /// + [FilterAllowedOutgoingContent(typeof(IEnumerable))] + public IEnumerable GetByIds([FromUri]int[] ids) + { + var foundContent = Services.ContentService.GetByIds(ids); + return foundContent.Select(Mapper.Map); + } - /// - /// Returns an item to be used to display the recycle bin for content - /// - /// - public ContentItemDisplay GetRecycleBin() - { - var display = new ContentItemDisplay - { - Id = Constants.System.RecycleBinContent, - Alias = "recycleBin", - ParentId = -1, - Name = Services.TextService.Localize("general/recycleBin"), - ContentTypeAlias = "recycleBin", - CreateDate = DateTime.Now, - IsContainer = true, - Path = "-1," + Constants.System.RecycleBinContent - }; + /// + /// Returns an item to be used to display the recycle bin for content + /// + /// + public ContentItemDisplay GetRecycleBin() + { + var display = new ContentItemDisplay + { + Id = Constants.System.RecycleBinContent, + Alias = "recycleBin", + ParentId = -1, + Name = Services.TextService.Localize("general/recycleBin"), + ContentTypeAlias = "recycleBin", + CreateDate = DateTime.Now, + IsContainer = true, + Path = "-1," + Constants.System.RecycleBinContent + }; - TabsAndPropertiesResolver.AddListView(display, "content", Services.DataTypeService, Services.TextService); + TabsAndPropertiesResolver.AddListView(display, "content", Services.DataTypeService, Services.TextService); - return display; - } + return display; + } - /// - /// Gets the content json for the content id - /// - /// - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetById(int id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - if (foundContent == null) - { - HandleContentNotFound(id); - } - - var content = Mapper.Map(foundContent); - return content; - } + /// + /// Gets the content json for the content id + /// + /// + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForContent("id")] + public ContentItemDisplay GetById(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + if (foundContent == null) + { + HandleContentNotFound(id); + } - [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetWithTreeDefinition(int id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - if (foundContent == null) - { - HandleContentNotFound(id); - } + var content = Mapper.Map(foundContent); + return content; + } - var content = Mapper.Map(foundContent); - return content; - } + [EnsureUserPermissionForContent("id")] + public ContentItemDisplay GetWithTreeDefinition(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + if (foundContent == null) + { + HandleContentNotFound(id); + } - /// - /// Gets an empty content item for the - /// - /// - /// - /// - /// If this is a container type, we'll remove the umbContainerView tab for a new item since - /// it cannot actually list children if it doesn't exist yet. - /// - [OutgoingEditorModelEvent] - public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId) - { - var contentType = Services.ContentTypeService.GetContentType(contentTypeAlias); - if (contentType == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } + var content = Mapper.Map(foundContent); + return content; + } - var emptyContent = Services.ContentService.CreateContent("", parentId, contentType.Alias, UmbracoUser.Id); - var mapped = Mapper.Map(emptyContent); + /// + /// Gets an empty content item for the + /// + /// + /// + /// + /// If this is a container type, we'll remove the umbContainerView tab for a new item since + /// it cannot actually list children if it doesn't exist yet. + /// + [OutgoingEditorModelEvent] + public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId) + { + var contentType = Services.ContentTypeService.GetContentType(contentTypeAlias); + if (contentType == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } - //remove this tab if it exists: umbContainerView - var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); - mapped.Tabs = mapped.Tabs.Except(new[] {containerTab}); - return mapped; - } + var emptyContent = Services.ContentService.CreateContent("", parentId, contentType.Alias, UmbracoUser.Id); + var mapped = Mapper.Map(emptyContent); - /// - /// Gets the Url for a given node ID - /// - /// - /// - public HttpResponseMessage GetNiceUrl(int id) - { - var url = Umbraco.NiceUrl(id); - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(url, Encoding.UTF8, "application/json"); - return response; - } + //remove this tab if it exists: umbContainerView + var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); + mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); + return mapped; + } - /// - /// Gets the children for the content id passed in - /// - /// - [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren( - int id, - int pageNumber = 0, //TODO: This should be '1' as it's not the index - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - string filter = "") - { - long totalChildren; - IContent[] children; - if (pageNumber > 0 && pageSize > 0) - { - children = Services.ContentService.GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren, orderBy, orderDirection, filter).ToArray(); - } - else - { - children = Services.ContentService.GetChildren(id).ToArray(); - totalChildren = children.Length; - } + /// + /// Gets the Url for a given node ID + /// + /// + /// + public HttpResponseMessage GetNiceUrl(int id) + { + var url = Umbraco.NiceUrl(id); + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(url, Encoding.UTF8, "application/json"); + return response; + } - if (totalChildren == 0) - { - return new PagedResult>(0, 0, 0); - } + /// + /// Gets the children for the content id passed in + /// + /// + [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren( + int id, + int pageNumber = 0, //TODO: This should be '1' as it's not the index + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + long totalChildren; + IContent[] children; + if (pageNumber > 0 && pageSize > 0) + { + children = Services.ContentService + .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren + , orderBy, orderDirection, orderBySystemField, filter).ToArray(); + } + else + { + children = Services.ContentService.GetChildren(id).ToArray(); + totalChildren = children.Length; + } - var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); - pagedResult.Items = children - .Select(Mapper.Map>); + if (totalChildren == 0) + { + return new PagedResult>(0, 0, 0); + } - return pagedResult; - } + var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); + pagedResult.Items = children + .Select(Mapper.Map>); - [Obsolete("Dont use this, it is incorrectly named, use HasPermission instead")] - public bool GetHasPermission(string permissionToCheck, int nodeId) - { - return HasPermission(permissionToCheck, nodeId); - } + return pagedResult; + } + + [Obsolete("Dont use this, it is incorrectly named, use HasPermission instead")] + public bool GetHasPermission(string permissionToCheck, int nodeId) + { + return HasPermission(permissionToCheck, nodeId); + } /// /// Returns permissions for all nodes passed in for the current user @@ -229,574 +232,574 @@ namespace Umbraco.Web.Editors .ToDictionary(x => x.EntityId, x => x.AssignedPermissions); } - [HttpGet] - public bool HasPermission(string permissionToCheck, int nodeId) - { - var p = Services.UserService.GetPermissions(Security.CurrentUser, nodeId).FirstOrDefault(); - if (p != null && p.AssignedPermissions.Contains(permissionToCheck.ToString(CultureInfo.InvariantCulture))) - { - return true; - } + [HttpGet] + public bool HasPermission(string permissionToCheck, int nodeId) + { + var p = Services.UserService.GetPermissions(Security.CurrentUser, nodeId).FirstOrDefault(); + if (p != null && p.AssignedPermissions.Contains(permissionToCheck.ToString(CultureInfo.InvariantCulture))) + { + return true; + } - return false; - } - - /// - /// Saves content - /// - /// - [FileUploadCleanupFilter] - [ContentPostValidate] - public ContentItemDisplay PostSave( - [ModelBinder(typeof(ContentItemBinder))] - ContentItemSave contentItem) - { - //If we've reached here it means: - // * Our model has been bound - // * and validated - // * any file attachments have been saved to their temporary location for us to use - // * we have a reference to the DTO object and the persisted object - // * Permissions are valid - - MapPropertyValues(contentItem); + return false; + } - //We need to manually check the validation results here because: - // * We still need to save the entity even if there are validation value errors - // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) - // then we cannot continue saving, we can only display errors - // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display - // a message indicating this - if (ModelState.IsValid == false) - { - if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) && IsCreatingAction(contentItem.Action)) - { - //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! - // add the modelstate to the outgoing object and throw a validation message - var forDisplay = Mapper.Map(contentItem.PersistedContent); - forDisplay.Errors = ModelState.ToErrorDictionary(); - throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); - - } + /// + /// Saves content + /// + /// + [FileUploadCleanupFilter] + [ContentPostValidate] + public ContentItemDisplay PostSave( + [ModelBinder(typeof(ContentItemBinder))] + ContentItemSave contentItem) + { + //If we've reached here it means: + // * Our model has been bound + // * and validated + // * any file attachments have been saved to their temporary location for us to use + // * we have a reference to the DTO object and the persisted object + // * Permissions are valid - //if the model state is not valid we cannot publish so change it to save - switch (contentItem.Action) - { - case ContentSaveAction.Publish: - contentItem.Action = ContentSaveAction.Save; - break; - case ContentSaveAction.PublishNew: - contentItem.Action = ContentSaveAction.SaveNew; - break; - } - } + MapPropertyValues(contentItem); - //initialize this to successful - var publishStatus = Attempt.Succeed(); - var wasCancelled = false; + //We need to manually check the validation results here because: + // * We still need to save the entity even if there are validation value errors + // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) + // then we cannot continue saving, we can only display errors + // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display + // a message indicating this + if (ModelState.IsValid == false) + { + if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) && IsCreatingAction(contentItem.Action)) + { + //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! + // add the modelstate to the outgoing object and throw a validation message + var forDisplay = Mapper.Map(contentItem.PersistedContent); + forDisplay.Errors = ModelState.ToErrorDictionary(); + throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); - if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew) - { - //save the item - var saveResult = Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id); + } - wasCancelled = saveResult.Success == false && saveResult.Result.StatusType == OperationStatusType.FailedCancelledByEvent; - } - else if (contentItem.Action == ContentSaveAction.SendPublish || contentItem.Action == ContentSaveAction.SendPublishNew) - { - var sendResult = Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id); - wasCancelled = sendResult == false; - } - else - { - //publish the item and check if it worked, if not we will show a diff msg below - publishStatus = Services.ContentService.SaveAndPublishWithStatus(contentItem.PersistedContent, Security.CurrentUser.Id); - wasCancelled = publishStatus.Result.StatusType == PublishStatusType.FailedCancelledByEvent; - } + //if the model state is not valid we cannot publish so change it to save + switch (contentItem.Action) + { + case ContentSaveAction.Publish: + contentItem.Action = ContentSaveAction.Save; + break; + case ContentSaveAction.PublishNew: + contentItem.Action = ContentSaveAction.SaveNew; + break; + } + } - //return the updated model - var display = Mapper.Map(contentItem.PersistedContent); + //initialize this to successful + var publishStatus = Attempt.Succeed(); + var wasCancelled = false; - //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 - HandleInvalidModelState(display); + if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew) + { + //save the item + var saveResult = Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id); - //put the correct msgs in - switch (contentItem.Action) - { - case ContentSaveAction.Save: - case ContentSaveAction.SaveNew: - if (wasCancelled == false) - { - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentSavedHeader"), - Services.TextService.Localize("speechBubbles/editContentSavedText")); - } - else - { - AddCancelMessage(display); - } - break; - case ContentSaveAction.SendPublish: - case ContentSaveAction.SendPublishNew: - if (wasCancelled == false) - { - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentSendToPublish"), - Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); - } - else - { - AddCancelMessage(display); - } - break; - case ContentSaveAction.Publish: - case ContentSaveAction.PublishNew: - ShowMessageForPublishStatus(publishStatus.Result, display); - break; - } + wasCancelled = saveResult.Success == false && saveResult.Result.StatusType == OperationStatusType.FailedCancelledByEvent; + } + else if (contentItem.Action == ContentSaveAction.SendPublish || contentItem.Action == ContentSaveAction.SendPublishNew) + { + var sendResult = Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id); + wasCancelled = sendResult == false; + } + else + { + //publish the item and check if it worked, if not we will show a diff msg below + publishStatus = Services.ContentService.SaveAndPublishWithStatus(contentItem.PersistedContent, Security.CurrentUser.Id); + wasCancelled = publishStatus.Result.StatusType == PublishStatusType.FailedCancelledByEvent; + } - UpdatePreviewContext(contentItem.PersistedContent.Id); + //return the updated model + var display = Mapper.Map(contentItem.PersistedContent); - //If the item is new and the operation was cancelled, we need to return a different - // status code so the UI can handle it since it won't be able to redirect since there - // is no Id to redirect to! - if (wasCancelled && IsCreatingAction(contentItem.Action)) - { - throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); - } + //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 + HandleInvalidModelState(display); - return display; - } - - /// - /// Publishes a document with a given ID - /// - /// - /// - /// - /// The CanAccessContentAuthorize attribute will deny access to this method if the current user - /// does not have Publish access to this node. - /// - /// - [EnsureUserPermissionForContent("id", 'U')] - public HttpResponseMessage PostPublishById(int id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + //put the correct msgs in + switch (contentItem.Action) + { + case ContentSaveAction.Save: + case ContentSaveAction.SaveNew: + if (wasCancelled == false) + { + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentSavedHeader"), + Services.TextService.Localize("speechBubbles/editContentSavedText")); + } + else + { + AddCancelMessage(display); + } + break; + case ContentSaveAction.SendPublish: + case ContentSaveAction.SendPublishNew: + if (wasCancelled == false) + { + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentSendToPublish"), + Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); + } + else + { + AddCancelMessage(display); + } + break; + case ContentSaveAction.Publish: + case ContentSaveAction.PublishNew: + ShowMessageForPublishStatus(publishStatus.Result, display); + break; + } - if (foundContent == null) - { - return HandleContentNotFound(id, false); - } + UpdatePreviewContext(contentItem.PersistedContent.Id); - var publishResult = Services.ContentService.PublishWithStatus(foundContent, Security.GetUserId()); - if (publishResult.Success == false) - { - var notificationModel = new SimpleNotificationModel(); - ShowMessageForPublishStatus(publishResult.Result, notificationModel); - return Request.CreateValidationErrorResponse(notificationModel); - } + //If the item is new and the operation was cancelled, we need to return a different + // status code so the UI can handle it since it won't be able to redirect since there + // is no Id to redirect to! + if (wasCancelled && IsCreatingAction(contentItem.Action)) + { + throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); + } - //return ok - return Request.CreateResponse(HttpStatusCode.OK); + return display; + } - } + /// + /// Publishes a document with a given ID + /// + /// + /// + /// + /// The CanAccessContentAuthorize attribute will deny access to this method if the current user + /// does not have Publish access to this node. + /// + /// + [EnsureUserPermissionForContent("id", 'U')] + public HttpResponseMessage PostPublishById(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - /// - /// Moves an item to the recycle bin, if it is already there then it will permanently delete it - /// - /// - /// - /// - /// The CanAccessContentAuthorize attribute will deny access to this method if the current user - /// does not have Delete access to this node. - /// - [EnsureUserPermissionForContent("id", 'D')] - [HttpDelete] - [HttpPost] - public HttpResponseMessage DeleteById(int id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + if (foundContent == null) + { + return HandleContentNotFound(id, false); + } - if (foundContent == null) - { - return HandleContentNotFound(id, false); - } + var publishResult = Services.ContentService.PublishWithStatus(foundContent, Security.GetUserId()); + if (publishResult.Success == false) + { + var notificationModel = new SimpleNotificationModel(); + ShowMessageForPublishStatus(publishResult.Result, notificationModel); + return Request.CreateValidationErrorResponse(notificationModel); + } - //if the current item is in the recycle bin - if (foundContent.IsInRecycleBin() == false) - { - var moveResult = Services.ContentService.WithResult().MoveToRecycleBin(foundContent, Security.GetUserId()); - if (moveResult == false) - { - //returning an object of INotificationModel will ensure that any pending - // notification messages are added to the response. - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } - } - else - { - var deleteResult = Services.ContentService.WithResult().Delete(foundContent, Security.GetUserId()); - if (deleteResult == false) - { - //returning an object of INotificationModel will ensure that any pending - // notification messages are added to the response. - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } - } + //return ok + return Request.CreateResponse(HttpStatusCode.OK); - return Request.CreateResponse(HttpStatusCode.OK); - } + } - /// - /// Empties the recycle bin - /// - /// - /// - /// attributed with EnsureUserPermissionForContent to verify the user has access to the recycle bin - /// - [HttpDelete] - [HttpPost] - [EnsureUserPermissionForContent(Constants.System.RecycleBinContent)] - public HttpResponseMessage EmptyRecycleBin() - { - Services.ContentService.EmptyRecycleBin(); - return Request.CreateResponse(HttpStatusCode.OK); - } + /// + /// Moves an item to the recycle bin, if it is already there then it will permanently delete it + /// + /// + /// + /// + /// The CanAccessContentAuthorize attribute will deny access to this method if the current user + /// does not have Delete access to this node. + /// + [EnsureUserPermissionForContent("id", 'D')] + [HttpDelete] + [HttpPost] + public HttpResponseMessage DeleteById(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - /// - /// Change the sort order for media - /// - /// - /// - [EnsureUserPermissionForContent("sorted.ParentId", 'S')] - public HttpResponseMessage PostSort(ContentSortOrder sorted) - { - if (sorted == null) - { - return Request.CreateResponse(HttpStatusCode.NotFound); - } + if (foundContent == null) + { + return HandleContentNotFound(id, false); + } - //if there's nothing to sort just return ok - if (sorted.IdSortOrder.Length == 0) - { - return Request.CreateResponse(HttpStatusCode.OK); - } + //if the current item is in the recycle bin + if (foundContent.IsInRecycleBin() == false) + { + var moveResult = Services.ContentService.WithResult().MoveToRecycleBin(foundContent, Security.GetUserId()); + if (moveResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } + } + else + { + var deleteResult = Services.ContentService.WithResult().Delete(foundContent, Security.GetUserId()); + if (deleteResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } + } - var contentService = Services.ContentService; - var sortedContent = new List(); - try - { - sortedContent.AddRange(Services.ContentService.GetByIds(sorted.IdSortOrder)); + return Request.CreateResponse(HttpStatusCode.OK); + } - // Save content with new sort order and update content xml in db accordingly - if (contentService.Sort(sortedContent) == false) - { - LogHelper.Warn("Content sorting failed, this was probably caused by an event being cancelled"); - return Request.CreateValidationErrorResponse("Content sorting failed, this was probably caused by an event being cancelled"); - } - return Request.CreateResponse(HttpStatusCode.OK); - } - catch (Exception ex) - { - LogHelper.Error("Could not update content sort order", ex); - throw; - } - } + /// + /// Empties the recycle bin + /// + /// + /// + /// attributed with EnsureUserPermissionForContent to verify the user has access to the recycle bin + /// + [HttpDelete] + [HttpPost] + [EnsureUserPermissionForContent(Constants.System.RecycleBinContent)] + public HttpResponseMessage EmptyRecycleBin() + { + Services.ContentService.EmptyRecycleBin(); + return Request.CreateResponse(HttpStatusCode.OK); + } - /// - /// Change the sort order for media - /// - /// - /// - [EnsureUserPermissionForContent("move.ParentId", 'M')] - public HttpResponseMessage PostMove(MoveOrCopy move) - { - var toMove = ValidateMoveOrCopy(move); + /// + /// Change the sort order for media + /// + /// + /// + [EnsureUserPermissionForContent("sorted.ParentId", 'S')] + public HttpResponseMessage PostSort(ContentSortOrder sorted) + { + if (sorted == null) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } - Services.ContentService.Move(toMove, move.ParentId); + //if there's nothing to sort just return ok + if (sorted.IdSortOrder.Length == 0) + { + return Request.CreateResponse(HttpStatusCode.OK); + } - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); - return response; - } + var contentService = Services.ContentService; + var sortedContent = new List(); + try + { + sortedContent.AddRange(Services.ContentService.GetByIds(sorted.IdSortOrder)); - /// - /// Copies a content item and places the copy as a child of a given parent Id - /// - /// - /// - [EnsureUserPermissionForContent("copy.ParentId", 'C')] - public HttpResponseMessage PostCopy(MoveOrCopy copy) - { - var toCopy = ValidateMoveOrCopy(copy); + // Save content with new sort order and update content xml in db accordingly + if (contentService.Sort(sortedContent) == false) + { + LogHelper.Warn("Content sorting failed, this was probably caused by an event being cancelled"); + return Request.CreateValidationErrorResponse("Content sorting failed, this was probably caused by an event being cancelled"); + } + return Request.CreateResponse(HttpStatusCode.OK); + } + catch (Exception ex) + { + LogHelper.Error("Could not update content sort order", ex); + throw; + } + } - var c = Services.ContentService.Copy(toCopy, copy.ParentId, copy.RelateToOriginal, copy.Recursive); + /// + /// Change the sort order for media + /// + /// + /// + [EnsureUserPermissionForContent("move.ParentId", 'M')] + public HttpResponseMessage PostMove(MoveOrCopy move) + { + var toMove = ValidateMoveOrCopy(move); - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(c.Path, Encoding.UTF8, "application/json"); - return response; - } + Services.ContentService.Move(toMove, move.ParentId); - /// - /// Unpublishes a node with a given Id and returns the unpublished entity - /// - /// - /// - [EnsureUserPermissionForContent("id", 'U')] - public ContentItemDisplay PostUnPublish(int id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); + return response; + } - if (foundContent == null) - HandleContentNotFound(id); - - var unpublishResult = Services.ContentService.WithResult().UnPublish(foundContent, Security.CurrentUser.Id); + /// + /// Copies a content item and places the copy as a child of a given parent Id + /// + /// + /// + [EnsureUserPermissionForContent("copy.ParentId", 'C')] + public HttpResponseMessage PostCopy(MoveOrCopy copy) + { + var toCopy = ValidateMoveOrCopy(copy); - var content = Mapper.Map(foundContent); + var c = Services.ContentService.Copy(toCopy, copy.ParentId, copy.RelateToOriginal, copy.Recursive); - if (unpublishResult == false) - { - AddCancelMessage(content); - throw new HttpResponseException(Request.CreateValidationErrorResponse(content)); - } - else - { - content.AddSuccessNotification(Services.TextService.Localize("content/unPublish"), Services.TextService.Localize("speechBubbles/contentUnpublished")); - return content; - } - } + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(c.Path, Encoding.UTF8, "application/json"); + return response; + } - /// - /// Checks if the user is currently in preview mode and if so will update the preview content for this item - /// - /// - private void UpdatePreviewContext(int contentId) - { - var previewId = Request.GetPreviewCookieValue(); - if (previewId.IsNullOrWhiteSpace()) return; - Guid id; - if (Guid.TryParse(previewId, out id)) - { - var d = new Document(contentId); - var pc = new PreviewContent(UmbracoUser, id, false); - pc.PrepareDocument(UmbracoUser, d, true); - pc.SavePreviewSet(); - } - } + /// + /// Unpublishes a node with a given Id and returns the unpublished entity + /// + /// + /// + [EnsureUserPermissionForContent("id", 'U')] + public ContentItemDisplay PostUnPublish(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - /// - /// Maps the dto property values to the persisted model - /// - /// - private void MapPropertyValues(ContentItemSave contentItem) - { - UpdateName(contentItem); + if (foundContent == null) + HandleContentNotFound(id); - //TODO: We need to support 'send to publish' + var unpublishResult = Services.ContentService.WithResult().UnPublish(foundContent, Security.CurrentUser.Id); - contentItem.PersistedContent.ExpireDate = contentItem.ExpireDate; - contentItem.PersistedContent.ReleaseDate = contentItem.ReleaseDate; - //only set the template if it didn't change - var templateChanged = (contentItem.PersistedContent.Template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) - || (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias) - || (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace()); - if (templateChanged) - { - var template = Services.FileService.GetTemplate(contentItem.TemplateAlias); - if (template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) - { - //ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias); - LogHelper.Warn("No template exists with the specified alias: " + contentItem.TemplateAlias); - } - else - { - //NOTE: this could be null if there was a template and the posted template is null, this should remove the assigned template - contentItem.PersistedContent.Template = template; - } - } + var content = Mapper.Map(foundContent); - base.MapPropertyValues(contentItem); - } + if (unpublishResult == false) + { + AddCancelMessage(content); + throw new HttpResponseException(Request.CreateValidationErrorResponse(content)); + } + else + { + content.AddSuccessNotification(Services.TextService.Localize("content/unPublish"), Services.TextService.Localize("speechBubbles/contentUnpublished")); + return content; + } + } - /// - /// Ensures the item can be moved/copied to the new location - /// - /// - /// - private IContent ValidateMoveOrCopy(MoveOrCopy model) - { - if (model == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } + /// + /// Checks if the user is currently in preview mode and if so will update the preview content for this item + /// + /// + private void UpdatePreviewContext(int contentId) + { + var previewId = Request.GetPreviewCookieValue(); + if (previewId.IsNullOrWhiteSpace()) return; + Guid id; + if (Guid.TryParse(previewId, out id)) + { + var d = new Document(contentId); + var pc = new PreviewContent(UmbracoUser, id, false); + pc.PrepareDocument(UmbracoUser, d, true); + pc.SavePreviewSet(); + } + } - var contentService = Services.ContentService; - var toMove = contentService.GetById(model.Id); - if (toMove == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - if (model.ParentId < 0) - { - //cannot move if the content item is not allowed at the root - if (toMove.ContentType.AllowedAsRoot == false) - { - throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"))); - } - } - else - { - var parent = contentService.GetById(model.ParentId); - if (parent == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } + /// + /// Maps the dto property values to the persisted model + /// + /// + private void MapPropertyValues(ContentItemSave contentItem) + { + UpdateName(contentItem); - //check if the item is allowed under this one - if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() - .Any(x => x.Value == toMove.ContentType.Id) == false) - { - throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("moveOrCopy/notAllowedByContentType"))); - } + //TODO: We need to support 'send to publish' - // Check on paths - if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) - { - throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("moveOrCopy/notAllowedByPath"))); - } - } + contentItem.PersistedContent.ExpireDate = contentItem.ExpireDate; + contentItem.PersistedContent.ReleaseDate = contentItem.ReleaseDate; + //only set the template if it didn't change + var templateChanged = (contentItem.PersistedContent.Template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) + || (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias) + || (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace()); + if (templateChanged) + { + var template = Services.FileService.GetTemplate(contentItem.TemplateAlias); + if (template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) + { + //ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias); + LogHelper.Warn("No template exists with the specified alias: " + contentItem.TemplateAlias); + } + else + { + //NOTE: this could be null if there was a template and the posted template is null, this should remove the assigned template + contentItem.PersistedContent.Template = template; + } + } - return toMove; - } + base.MapPropertyValues(contentItem); + } - private void ShowMessageForPublishStatus(PublishStatus status, INotificationModel display) - { - switch (status.StatusType) - { - case PublishStatusType.Success: - case PublishStatusType.SuccessAlreadyPublished: - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), - Services.TextService.Localize("speechBubbles/editContentPublishedText")); - break; - case PublishStatusType.FailedPathNotPublished: - display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedByParent", - new[] {string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id)}).Trim()); - break; - case PublishStatusType.FailedCancelledByEvent: - AddCancelMessage(display, "publish", "speechBubbles/contentPublishedFailedByEvent"); - break; - case PublishStatusType.FailedAwaitingRelease: - display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease", - new[] {string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id)}).Trim()); - break; - case PublishStatusType.FailedHasExpired: - display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedExpired", - new[] - { - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), - }).Trim()); - break; - case PublishStatusType.FailedIsTrashed: - //TODO: We should add proper error messaging for this! - break; - case PublishStatusType.FailedContentInvalid: - display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedInvalid", - new[] - { - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), - string.Join(",", status.InvalidProperties.Select(x => x.Alias)) - }).Trim()); - break; - default: - throw new IndexOutOfRangeException(); - } - } + /// + /// Ensures the item can be moved/copied to the new location + /// + /// + /// + private IContent ValidateMoveOrCopy(MoveOrCopy model) + { + if (model == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } - + var contentService = Services.ContentService; + var toMove = contentService.GetById(model.Id); + if (toMove == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + if (model.ParentId < 0) + { + //cannot move if the content item is not allowed at the root + if (toMove.ContentType.AllowedAsRoot == false) + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"))); + } + } + else + { + var parent = contentService.GetById(model.ParentId); + if (parent == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } - /// - /// Performs a permissions check for the user to check if it has access to the node based on - /// start node and/or permissions for the node - /// - /// The storage to add the content item to so it can be reused - /// - /// - /// - /// The content to lookup, if the contentItem is not specified - /// - /// Specifies the already resolved content item to check against - /// - internal static bool CheckPermissions( - IDictionary storage, - IUser user, - IUserService userService, - IContentService contentService, - int nodeId, - char[] permissionsToCheck = null, - IContent contentItem = null) - { - - if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) - { - contentItem = contentService.GetById(nodeId); - //put the content item into storage so it can be retreived - // in the controller (saves a lookup) - storage[typeof(IContent).ToString()] = contentItem; - } + //check if the item is allowed under this one + if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() + .Any(x => x.Value == toMove.ContentType.Id) == false) + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedByContentType"))); + } - if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } + // Check on paths + if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedByPath"))); + } + } - var hasPathAccess = (nodeId == Constants.System.Root) - ? UserExtensions.HasPathAccess( - Constants.System.Root.ToInvariantString(), - user.StartContentId, - Constants.System.RecycleBinContent) - : (nodeId == Constants.System.RecycleBinContent) - ? UserExtensions.HasPathAccess( - Constants.System.RecycleBinContent.ToInvariantString(), - user.StartContentId, - Constants.System.RecycleBinContent) - : user.HasPathAccess(contentItem); + return toMove; + } - if (hasPathAccess == false) - { - return false; - } + private void ShowMessageForPublishStatus(PublishStatus status, INotificationModel display) + { + switch (status.StatusType) + { + case PublishStatusType.Success: + case PublishStatusType.SuccessAlreadyPublished: + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), + Services.TextService.Localize("speechBubbles/editContentPublishedText")); + break; + case PublishStatusType.FailedPathNotPublished: + display.AddWarningNotification( + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedByParent", + new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); + break; + case PublishStatusType.FailedCancelledByEvent: + AddCancelMessage(display, "publish", "speechBubbles/contentPublishedFailedByEvent"); + break; + case PublishStatusType.FailedAwaitingRelease: + display.AddWarningNotification( + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease", + new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); + break; + case PublishStatusType.FailedHasExpired: + display.AddWarningNotification( + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedExpired", + new[] + { + string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), + }).Trim()); + break; + case PublishStatusType.FailedIsTrashed: + //TODO: We should add proper error messaging for this! + break; + case PublishStatusType.FailedContentInvalid: + display.AddWarningNotification( + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedInvalid", + new[] + { + string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), + string.Join(",", status.InvalidProperties.Select(x => x.Alias)) + }).Trim()); + break; + default: + throw new IndexOutOfRangeException(); + } + } - if (permissionsToCheck == null || permissionsToCheck.Any() == false) - { - return true; - } - var permission = userService.GetPermissions(user, nodeId).FirstOrDefault(); - var allowed = true; - foreach (var p in permissionsToCheck) - { - if (permission == null || permission.AssignedPermissions.Contains(p.ToString(CultureInfo.InvariantCulture)) == false) - { - allowed = false; - } - } - return allowed; - } + /// + /// Performs a permissions check for the user to check if it has access to the node based on + /// start node and/or permissions for the node + /// + /// The storage to add the content item to so it can be reused + /// + /// + /// + /// The content to lookup, if the contentItem is not specified + /// + /// Specifies the already resolved content item to check against + /// + internal static bool CheckPermissions( + IDictionary storage, + IUser user, + IUserService userService, + IContentService contentService, + int nodeId, + char[] permissionsToCheck = null, + IContent contentItem = null) + { - } + if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) + { + contentItem = contentService.GetById(nodeId); + //put the content item into storage so it can be retreived + // in the controller (saves a lookup) + storage[typeof(IContent).ToString()] = contentItem; + } + + if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var hasPathAccess = (nodeId == Constants.System.Root) + ? UserExtensions.HasPathAccess( + Constants.System.Root.ToInvariantString(), + user.StartContentId, + Constants.System.RecycleBinContent) + : (nodeId == Constants.System.RecycleBinContent) + ? UserExtensions.HasPathAccess( + Constants.System.RecycleBinContent.ToInvariantString(), + user.StartContentId, + Constants.System.RecycleBinContent) + : user.HasPathAccess(contentItem); + + if (hasPathAccess == false) + { + return false; + } + + if (permissionsToCheck == null || permissionsToCheck.Any() == false) + { + return true; + } + + var permission = userService.GetPermissions(user, nodeId).FirstOrDefault(); + + var allowed = true; + foreach (var p in permissionsToCheck) + { + if (permission == null || permission.AssignedPermissions.Contains(p.ToString(CultureInfo.InvariantCulture)) == false) + { + allowed = false; + } + } + return allowed; + } + + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 66889e2206..da7c72a289 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -39,672 +39,675 @@ using Notification = Umbraco.Web.Models.ContentEditing.Notification; namespace Umbraco.Web.Editors { - /// - /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting - /// access to ALL of the methods on this controller will need access to the media application. - /// - [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Media)] - public class MediaController : ContentControllerBase - { - /// - /// Constructor - /// - public MediaController() - : this(UmbracoContext.Current) - { - } - - /// - /// Constructor - /// - /// - public MediaController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - } - - /// - /// Gets an empty content item for the - /// - /// - /// - /// - [OutgoingEditorModelEvent] - public MediaItemDisplay GetEmpty(string contentTypeAlias, int parentId) - { - var contentType = Services.ContentTypeService.GetMediaType(contentTypeAlias); - if (contentType == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var emptyContent = Services.MediaService.CreateMedia("", parentId, contentType.Alias, UmbracoUser.Id); - var mapped = Mapper.Map(emptyContent); - - //remove this tab if it exists: umbContainerView - var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); - mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); - return mapped; - } - - /// - /// Returns an item to be used to display the recycle bin for media - /// - /// - public ContentItemDisplay GetRecycleBin() - { - var display = new ContentItemDisplay - { - Id = Constants.System.RecycleBinMedia, - Alias = "recycleBin", - ParentId = -1, - Name = Services.TextService.Localize("general/recycleBin"), - ContentTypeAlias = "recycleBin", - CreateDate = DateTime.Now, - IsContainer = true, - Path = "-1," + Constants.System.RecycleBinMedia - }; - - TabsAndPropertiesResolver.AddListView(display, "media", Services.DataTypeService, Services.TextService); - - return display; - } - - /// - /// Gets the content json for the content id - /// - /// - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForMedia("id")] - public MediaItemDisplay GetById(int id) - { - var foundContent = GetObjectFromRequest(() => Services.MediaService.GetById(id)); - - if (foundContent == null) - { - HandleContentNotFound(id); - //HandleContentNotFound will throw an exception - return null; - } - return Mapper.Map(foundContent); - } - - /// - /// Return media for the specified ids - /// - /// - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable))] - public IEnumerable GetByIds([FromUri]int[] ids) - { - var foundMedia = Services.MediaService.GetByIds(ids); - return foundMedia.Select(Mapper.Map); - } - - /// - /// Returns media items known to be a container of other media items - /// - /// - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] - public IEnumerable> GetChildFolders(int id = -1) - { - //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 children = (id < 0) ? Services.MediaService.GetRootMedia() : Services.MediaService.GetById(id).Children(); - return children.Where(x => folderTypes.Contains(x.ContentTypeId)).Select(Mapper.Map>); - } - - /// - /// Returns the root media objects - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] - public IEnumerable> GetRootMedia() - { - //TODO: Add permissions check! - - return Services.MediaService.GetRootMedia() - .Select(Mapper.Map>); - } - - /// - /// Returns the child media objects - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(int id, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - string filter = "") - { - int totalChildren; - IMedia[] children; - if (pageNumber > 0 && pageSize > 0) - { - children = Services.MediaService.GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren, orderBy, orderDirection, filter).ToArray(); - } - else - { - children = Services.MediaService.GetChildren(id).ToArray(); - totalChildren = children.Length; - } - - if (totalChildren == 0) - { - return new PagedResult>(0, 0, 0); - } - - var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); - pagedResult.Items = children - .Select(Mapper.Map>); - - return pagedResult; - } - - /// - /// Moves an item to the recycle bin, if it is already there then it will permanently delete it - /// - /// - /// - [EnsureUserPermissionForMedia("id")] - [HttpPost] - public HttpResponseMessage DeleteById(int id) - { - var foundMedia = GetObjectFromRequest(() => Services.MediaService.GetById(id)); - - if (foundMedia == null) - { - return HandleContentNotFound(id, false); - } - - //if the current item is in the recycle bin - if (foundMedia.IsInRecycleBin() == false) - { - var moveResult = Services.MediaService.WithResult().MoveToRecycleBin(foundMedia, (int)Security.CurrentUser.Id); - if (moveResult == false) - { - //returning an object of INotificationModel will ensure that any pending - // notification messages are added to the response. - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } - } - else - { - var deleteResult = Services.MediaService.WithResult().Delete(foundMedia, (int)Security.CurrentUser.Id); - if (deleteResult == false) - { - //returning an object of INotificationModel will ensure that any pending - // notification messages are added to the response. - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } - } - - return Request.CreateResponse(HttpStatusCode.OK); - } - - /// - /// Change the sort order for media - /// - /// - /// - [EnsureUserPermissionForMedia("move.Id")] - public HttpResponseMessage PostMove(MoveOrCopy move) - { - var toMove = ValidateMoveOrCopy(move); - - Services.MediaService.Move(toMove, move.ParentId); - - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); - return response; - } - - /// - /// Saves content - /// - /// - [FileUploadCleanupFilter] - [MediaPostValidate] - public MediaItemDisplay PostSave( - [ModelBinder(typeof(MediaItemBinder))] - MediaItemSave contentItem) - { - //If we've reached here it means: - // * Our model has been bound - // * and validated - // * any file attachments have been saved to their temporary location for us to use - // * we have a reference to the DTO object and the persisted object - // * Permissions are valid - - MapPropertyValues(contentItem); - - //We need to manually check the validation results here because: - // * We still need to save the entity even if there are validation value errors - // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) - // then we cannot continue saving, we can only display errors - // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display - // a message indicating this - if (ModelState.IsValid == false) - { - if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) - && (contentItem.Action == ContentSaveAction.SaveNew)) - { - //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! - // add the modelstate to the outgoing object and throw validation response - var forDisplay = Mapper.Map(contentItem.PersistedContent); - forDisplay.Errors = ModelState.ToErrorDictionary(); - throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); - } - } - - //save the item - var saveStatus = Services.MediaService.WithResult().Save(contentItem.PersistedContent, (int)Security.CurrentUser.Id); - - //return the updated model - var display = Mapper.Map(contentItem.PersistedContent); - - //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 - HandleInvalidModelState(display); - - //put the correct msgs in - switch (contentItem.Action) - { - case ContentSaveAction.Save: - case ContentSaveAction.SaveNew: - if (saveStatus.Success) - { - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editMediaSaved"), - Services.TextService.Localize("speechBubbles/editMediaSavedText")); - } - else - { - AddCancelMessage(display); - - //If the item is new and the operation was cancelled, we need to return a different - // status code so the UI can handle it since it won't be able to redirect since there - // is no Id to redirect to! - if (saveStatus.Result.StatusType == OperationStatusType.FailedCancelledByEvent && IsCreatingAction(contentItem.Action)) - { - throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); - } - } - - break; - } - - return display; - } - - /// - /// Maps the property values to the persisted entity - /// - /// - protected override void MapPropertyValues(ContentBaseItemSave contentItem) - { - UpdateName(contentItem); - - //use the base method to map the rest of the properties - base.MapPropertyValues(contentItem); - } - - /// - /// Empties the recycle bin - /// - /// - [HttpDelete] - [HttpPost] - public HttpResponseMessage EmptyRecycleBin() - { - Services.MediaService.EmptyRecycleBin(); - return Request.CreateResponse(HttpStatusCode.OK); - } - - /// - /// Change the sort order for media - /// - /// - /// - [EnsureUserPermissionForMedia("sorted.ParentId")] - public HttpResponseMessage PostSort(ContentSortOrder sorted) - { - if (sorted == null) - { - return Request.CreateResponse(HttpStatusCode.NotFound); - } - - //if there's nothing to sort just return ok - if (sorted.IdSortOrder.Length == 0) - { - return Request.CreateResponse(HttpStatusCode.OK); - } - - var mediaService = base.ApplicationContext.Services.MediaService; - var sortedMedia = new List(); - try - { - sortedMedia.AddRange(sorted.IdSortOrder.Select(mediaService.GetById)); - - // Save Media with new sort order and update content xml in db accordingly - if (mediaService.Sort(sortedMedia) == false) - { - LogHelper.Warn("Media sorting failed, this was probably caused by an event being cancelled"); - return Request.CreateValidationErrorResponse("Media sorting failed, this was probably caused by an event being cancelled"); - } - return Request.CreateResponse(HttpStatusCode.OK); - } - catch (Exception ex) - { - LogHelper.Error("Could not update media sort order", ex); - throw; - } - } - - [EnsureUserPermissionForMedia("folder.ParentId")] - public MediaItemDisplay PostAddFolder(EntityBasic folder) - { - var mediaService = ApplicationContext.Services.MediaService; - var f = mediaService.CreateMedia(folder.Name, folder.ParentId, Constants.Conventions.MediaTypes.Folder); - mediaService.Save(f, Security.CurrentUser.Id); - - return Mapper.Map(f); - } - - /// - /// Used to submit a media file - /// - /// - /// - /// We cannot validate this request with attributes (nicely) due to the nature of the multi-part for data. - /// - [FileUploadCleanupFilter(false)] - public async Task PostAddFile() - { - if (Request.Content.IsMimeMultipartContent() == false) - { - throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); - } - - var root = IOHelper.MapPath("~/App_Data/TEMP/FileUploads"); - //ensure it exists - Directory.CreateDirectory(root); - var provider = new MultipartFormDataStreamProvider(root); - - var result = await Request.Content.ReadAsMultipartAsync(provider); - - //must have a file - if (result.FileData.Count == 0) - { - return Request.CreateResponse(HttpStatusCode.NotFound); - } - - //get the string json from the request - int parentId; - if (int.TryParse(result.FormData["currentFolder"], out parentId) == false) - { - return Request.CreateValidationErrorResponse("The request was not formatted correctly, the currentFolder is not an integer"); - } - - //ensure the user has access to this folder by parent id! - if (CheckPermissions( - new Dictionary(), - Security.CurrentUser, - Services.MediaService, parentId) == false) - { - return Request.CreateResponse( - HttpStatusCode.Unauthorized, - new SimpleNotificationModel(new Notification( - Services.TextService.Localize("speechBubbles/operationFailedHeader"), - Services.TextService.Localize("speechBubbles/invalidUserPermissionsText"), - SpeechBubbleIcon.Warning))); - } - - var tempFiles = new PostedFiles(); - var mediaService = ApplicationContext.Services.MediaService; - - - //in case we pass a path with a folder in it, we will create it and upload media to it. - if (result.FormData.ContainsKey("path")) - { - - var folders = result.FormData["path"].Split('/'); - - for (int i = 0; i < folders.Length - 1; i++) - { - var folderName = folders[i]; - IMedia folderMediaItem; - - //if uploading directly to media root and not a subfolder - if (parentId == -1) - { - //look for matching folder - folderMediaItem = - mediaService.GetRootMedia().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); - if (folderMediaItem == null) - { - //if null, create a folder - folderMediaItem = mediaService.CreateMedia(folderName, -1, Constants.Conventions.MediaTypes.Folder); - mediaService.Save(folderMediaItem); - } - } - else - { - //get current parent - var mediaRoot = mediaService.GetById(parentId); - - //if the media root is null, something went wrong, we'll abort - if (mediaRoot == null) - return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, - "The folder: " + folderName + " could not be used for storing images, its ID: " + parentId + - " returned null"); - - //look for matching folder - folderMediaItem = mediaRoot.Children().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); - if (folderMediaItem == null) - { - //if null, create a folder - folderMediaItem = mediaService.CreateMedia(folderName, mediaRoot, Constants.Conventions.MediaTypes.Folder); - mediaService.Save(folderMediaItem); - } - } - //set the media root to the folder id so uploaded files will end there. - parentId = folderMediaItem.Id; - } - } - - //get the files - foreach (var file in result.FileData) - { - var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }); - var ext = fileName.Substring(fileName.LastIndexOf('.')+1).ToLower(); - - if (UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext) == false) - { - var mediaType = Constants.Conventions.MediaTypes.File; - - if (UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.Contains(ext)) - mediaType = Constants.Conventions.MediaTypes.Image; - - //TODO: make the media item name "nice" since file names could be pretty ugly, we have - // string extensions to do much of this but we'll need: - // * Pascalcase the name (use string extensions) - // * strip the file extension - // * underscores to spaces - // * probably remove 'ugly' characters - let's discuss - // All of this logic should exist in a string extensions method and be unit tested - // http://issues.umbraco.org/issue/U4-5572 - var mediaItemName = fileName; - - var f = mediaService.CreateMedia(mediaItemName, parentId, mediaType, Security.CurrentUser.Id); - - var fileInfo = new FileInfo(file.LocalFileName); - var fs = fileInfo.OpenReadWithRetry(); - if (fs == null) throw new InvalidOperationException("Could not acquire file stream"); - using (fs) - { - f.SetValue(Constants.Conventions.Media.File, fileName, fs); - } - - var saveResult = mediaService.WithResult().Save(f, Security.CurrentUser.Id); - if (saveResult == false) - { - AddCancelMessage(tempFiles, - message: Services.TextService.Localize("speechBubbles/operationCancelledText") + " -- " + mediaItemName, - localizeMessage: false); - } - else - { - tempFiles.UploadedFiles.Add(new ContentItemFile - { - FileName = fileName, - PropertyAlias = Constants.Conventions.Media.File, - TempFilePath = file.LocalFileName - }); - } - } - else - { - tempFiles.Notifications.Add(new Notification( - Services.TextService.Localize("speechBubbles/operationFailedHeader"), - "Cannot upload file " + file.Headers.ContentDisposition.FileName + ", it is not an approved file type", - SpeechBubbleIcon.Warning)); - } - } - - //Different response if this is a 'blueimp' request - if (Request.GetQueryNameValuePairs().Any(x => x.Key == "origin")) - { - var origin = Request.GetQueryNameValuePairs().First(x => x.Key == "origin"); - if (origin.Value == "blueimp") - { - return Request.CreateResponse(HttpStatusCode.OK, - tempFiles, - //Don't output the angular xsrf stuff, blue imp doesn't like that - new JsonMediaTypeFormatter()); - } - } - - return Request.CreateResponse(HttpStatusCode.OK, tempFiles); - } - - /// - /// This is used for the response of PostAddFile so that we can analyze the response in a filter and remove the - /// temporary files that were created. - /// - [DataContract] - private class PostedFiles : IHaveUploadedFiles, INotificationModel - { - public PostedFiles() - { - UploadedFiles = new List(); - Notifications = new List(); - } - public List UploadedFiles { get; private set; } - - [DataMember(Name = "notifications")] - public List Notifications { get; private set; } - } - - /// - /// Ensures the item can be moved/copied to the new location - /// - /// - /// - private IMedia ValidateMoveOrCopy(MoveOrCopy model) - { - if (model == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var mediaService = Services.MediaService; - var toMove = mediaService.GetById(model.Id); - if (toMove == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - if (model.ParentId < 0) - { - //cannot move if the content item is not allowed at the root - if (toMove.ContentType.AllowedAsRoot == false) - { - var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"), ""); - throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); - } - } - else - { - var parent = mediaService.GetById(model.ParentId); - if (parent == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - //check if the item is allowed under this one - if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() - .Any(x => x.Value == toMove.ContentType.Id) == false) - { - var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByContentType"), ""); - throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); - } - - // Check on paths - if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) - { - var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByPath"), ""); - throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); - } - } - - return toMove; - } - - /// - /// Performs a permissions check for the user to check if it has access to the node based on - /// start node and/or permissions for the node - /// - /// The storage to add the content item to so it can be reused - /// - /// - /// The content to lookup, if the contentItem is not specified - /// Specifies the already resolved content item to check against, setting this ignores the nodeId - /// - internal static bool CheckPermissions(IDictionary storage, IUser user, IMediaService mediaService, int nodeId, IMedia media = null) - { - if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) - { - media = mediaService.GetById(nodeId); - //put the content item into storage so it can be retreived - // in the controller (saves a lookup) - storage[typeof(IMedia).ToString()] = media; - } - - if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var hasPathAccess = (nodeId == Constants.System.Root) - ? UserExtensions.HasPathAccess( - Constants.System.Root.ToInvariantString(), - user.StartMediaId, - Constants.System.RecycleBinMedia) - : (nodeId == Constants.System.RecycleBinMedia) - ? UserExtensions.HasPathAccess( - Constants.System.RecycleBinMedia.ToInvariantString(), - user.StartMediaId, - Constants.System.RecycleBinMedia) - : user.HasPathAccess(media); - - return hasPathAccess; - } - } + /// + /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting + /// access to ALL of the methods on this controller will need access to the media application. + /// + [PluginController("UmbracoApi")] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Media)] + public class MediaController : ContentControllerBase + { + /// + /// Constructor + /// + public MediaController() + : this(UmbracoContext.Current) + { + } + + /// + /// Constructor + /// + /// + public MediaController(UmbracoContext umbracoContext) + : base(umbracoContext) + { + } + + /// + /// Gets an empty content item for the + /// + /// + /// + /// + [OutgoingEditorModelEvent] + public MediaItemDisplay GetEmpty(string contentTypeAlias, int parentId) + { + var contentType = Services.ContentTypeService.GetMediaType(contentTypeAlias); + if (contentType == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var emptyContent = Services.MediaService.CreateMedia("", parentId, contentType.Alias, UmbracoUser.Id); + var mapped = Mapper.Map(emptyContent); + + //remove this tab if it exists: umbContainerView + var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); + mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); + return mapped; + } + + /// + /// Returns an item to be used to display the recycle bin for media + /// + /// + public ContentItemDisplay GetRecycleBin() + { + var display = new ContentItemDisplay + { + Id = Constants.System.RecycleBinMedia, + Alias = "recycleBin", + ParentId = -1, + Name = Services.TextService.Localize("general/recycleBin"), + ContentTypeAlias = "recycleBin", + CreateDate = DateTime.Now, + IsContainer = true, + Path = "-1," + Constants.System.RecycleBinMedia + }; + + TabsAndPropertiesResolver.AddListView(display, "media", Services.DataTypeService, Services.TextService); + + return display; + } + + /// + /// Gets the content json for the content id + /// + /// + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForMedia("id")] + public MediaItemDisplay GetById(int id) + { + var foundContent = GetObjectFromRequest(() => Services.MediaService.GetById(id)); + + if (foundContent == null) + { + HandleContentNotFound(id); + //HandleContentNotFound will throw an exception + return null; + } + return Mapper.Map(foundContent); + } + + /// + /// Return media for the specified ids + /// + /// + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable))] + public IEnumerable GetByIds([FromUri]int[] ids) + { + var foundMedia = Services.MediaService.GetByIds(ids); + return foundMedia.Select(Mapper.Map); + } + + /// + /// Returns media items known to be a container of other media items + /// + /// + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] + public IEnumerable> GetChildFolders(int id = -1) + { + //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 children = (id < 0) ? Services.MediaService.GetRootMedia() : Services.MediaService.GetById(id).Children(); + return children.Where(x => folderTypes.Contains(x.ContentTypeId)).Select(Mapper.Map>); + } + + /// + /// Returns the root media objects + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] + public IEnumerable> GetRootMedia() + { + //TODO: Add permissions check! + + return Services.MediaService.GetRootMedia() + .Select(Mapper.Map>); + } + + /// + /// Returns the child media objects + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(int id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + int totalChildren; + IMedia[] children; + if (pageNumber > 0 && pageSize > 0) + { + children = Services.MediaService + .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren + , orderBy, orderDirection, orderBySystemField, filter).ToArray(); + } + else + { + children = Services.MediaService.GetChildren(id).ToArray(); + totalChildren = children.Length; + } + + if (totalChildren == 0) + { + return new PagedResult>(0, 0, 0); + } + + var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); + pagedResult.Items = children + .Select(Mapper.Map>); + + return pagedResult; + } + + /// + /// Moves an item to the recycle bin, if it is already there then it will permanently delete it + /// + /// + /// + [EnsureUserPermissionForMedia("id")] + [HttpPost] + public HttpResponseMessage DeleteById(int id) + { + var foundMedia = GetObjectFromRequest(() => Services.MediaService.GetById(id)); + + if (foundMedia == null) + { + return HandleContentNotFound(id, false); + } + + //if the current item is in the recycle bin + if (foundMedia.IsInRecycleBin() == false) + { + var moveResult = Services.MediaService.WithResult().MoveToRecycleBin(foundMedia, (int)Security.CurrentUser.Id); + if (moveResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } + } + else + { + var deleteResult = Services.MediaService.WithResult().Delete(foundMedia, (int)Security.CurrentUser.Id); + if (deleteResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } + } + + return Request.CreateResponse(HttpStatusCode.OK); + } + + /// + /// Change the sort order for media + /// + /// + /// + [EnsureUserPermissionForMedia("move.Id")] + public HttpResponseMessage PostMove(MoveOrCopy move) + { + var toMove = ValidateMoveOrCopy(move); + + Services.MediaService.Move(toMove, move.ParentId); + + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); + return response; + } + + /// + /// Saves content + /// + /// + [FileUploadCleanupFilter] + [MediaPostValidate] + public MediaItemDisplay PostSave( + [ModelBinder(typeof(MediaItemBinder))] + MediaItemSave contentItem) + { + //If we've reached here it means: + // * Our model has been bound + // * and validated + // * any file attachments have been saved to their temporary location for us to use + // * we have a reference to the DTO object and the persisted object + // * Permissions are valid + + MapPropertyValues(contentItem); + + //We need to manually check the validation results here because: + // * We still need to save the entity even if there are validation value errors + // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) + // then we cannot continue saving, we can only display errors + // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display + // a message indicating this + if (ModelState.IsValid == false) + { + if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) + && (contentItem.Action == ContentSaveAction.SaveNew)) + { + //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! + // add the modelstate to the outgoing object and throw validation response + var forDisplay = Mapper.Map(contentItem.PersistedContent); + forDisplay.Errors = ModelState.ToErrorDictionary(); + throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); + } + } + + //save the item + var saveStatus = Services.MediaService.WithResult().Save(contentItem.PersistedContent, (int)Security.CurrentUser.Id); + + //return the updated model + var display = Mapper.Map(contentItem.PersistedContent); + + //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 + HandleInvalidModelState(display); + + //put the correct msgs in + switch (contentItem.Action) + { + case ContentSaveAction.Save: + case ContentSaveAction.SaveNew: + if (saveStatus.Success) + { + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editMediaSaved"), + Services.TextService.Localize("speechBubbles/editMediaSavedText")); + } + else + { + AddCancelMessage(display); + + //If the item is new and the operation was cancelled, we need to return a different + // status code so the UI can handle it since it won't be able to redirect since there + // is no Id to redirect to! + if (saveStatus.Result.StatusType == OperationStatusType.FailedCancelledByEvent && IsCreatingAction(contentItem.Action)) + { + throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); + } + } + + break; + } + + return display; + } + + /// + /// Maps the property values to the persisted entity + /// + /// + protected override void MapPropertyValues(ContentBaseItemSave contentItem) + { + UpdateName(contentItem); + + //use the base method to map the rest of the properties + base.MapPropertyValues(contentItem); + } + + /// + /// Empties the recycle bin + /// + /// + [HttpDelete] + [HttpPost] + public HttpResponseMessage EmptyRecycleBin() + { + Services.MediaService.EmptyRecycleBin(); + return Request.CreateResponse(HttpStatusCode.OK); + } + + /// + /// Change the sort order for media + /// + /// + /// + [EnsureUserPermissionForMedia("sorted.ParentId")] + public HttpResponseMessage PostSort(ContentSortOrder sorted) + { + if (sorted == null) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + //if there's nothing to sort just return ok + if (sorted.IdSortOrder.Length == 0) + { + return Request.CreateResponse(HttpStatusCode.OK); + } + + var mediaService = base.ApplicationContext.Services.MediaService; + var sortedMedia = new List(); + try + { + sortedMedia.AddRange(sorted.IdSortOrder.Select(mediaService.GetById)); + + // Save Media with new sort order and update content xml in db accordingly + if (mediaService.Sort(sortedMedia) == false) + { + LogHelper.Warn("Media sorting failed, this was probably caused by an event being cancelled"); + return Request.CreateValidationErrorResponse("Media sorting failed, this was probably caused by an event being cancelled"); + } + return Request.CreateResponse(HttpStatusCode.OK); + } + catch (Exception ex) + { + LogHelper.Error("Could not update media sort order", ex); + throw; + } + } + + [EnsureUserPermissionForMedia("folder.ParentId")] + public MediaItemDisplay PostAddFolder(EntityBasic folder) + { + var mediaService = ApplicationContext.Services.MediaService; + var f = mediaService.CreateMedia(folder.Name, folder.ParentId, Constants.Conventions.MediaTypes.Folder); + mediaService.Save(f, Security.CurrentUser.Id); + + return Mapper.Map(f); + } + + /// + /// Used to submit a media file + /// + /// + /// + /// We cannot validate this request with attributes (nicely) due to the nature of the multi-part for data. + /// + [FileUploadCleanupFilter(false)] + public async Task PostAddFile() + { + if (Request.Content.IsMimeMultipartContent() == false) + { + throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); + } + + var root = IOHelper.MapPath("~/App_Data/TEMP/FileUploads"); + //ensure it exists + Directory.CreateDirectory(root); + var provider = new MultipartFormDataStreamProvider(root); + + var result = await Request.Content.ReadAsMultipartAsync(provider); + + //must have a file + if (result.FileData.Count == 0) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + //get the string json from the request + int parentId; + if (int.TryParse(result.FormData["currentFolder"], out parentId) == false) + { + return Request.CreateValidationErrorResponse("The request was not formatted correctly, the currentFolder is not an integer"); + } + + //ensure the user has access to this folder by parent id! + if (CheckPermissions( + new Dictionary(), + Security.CurrentUser, + Services.MediaService, parentId) == false) + { + return Request.CreateResponse( + HttpStatusCode.Unauthorized, + new SimpleNotificationModel(new Notification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + Services.TextService.Localize("speechBubbles/invalidUserPermissionsText"), + SpeechBubbleIcon.Warning))); + } + + var tempFiles = new PostedFiles(); + var mediaService = ApplicationContext.Services.MediaService; + + + //in case we pass a path with a folder in it, we will create it and upload media to it. + if (result.FormData.ContainsKey("path")) + { + + var folders = result.FormData["path"].Split('/'); + + for (int i = 0; i < folders.Length - 1; i++) + { + var folderName = folders[i]; + IMedia folderMediaItem; + + //if uploading directly to media root and not a subfolder + if (parentId == -1) + { + //look for matching folder + folderMediaItem = + mediaService.GetRootMedia().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); + if (folderMediaItem == null) + { + //if null, create a folder + folderMediaItem = mediaService.CreateMedia(folderName, -1, Constants.Conventions.MediaTypes.Folder); + mediaService.Save(folderMediaItem); + } + } + else + { + //get current parent + var mediaRoot = mediaService.GetById(parentId); + + //if the media root is null, something went wrong, we'll abort + if (mediaRoot == null) + return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, + "The folder: " + folderName + " could not be used for storing images, its ID: " + parentId + + " returned null"); + + //look for matching folder + folderMediaItem = mediaRoot.Children().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); + if (folderMediaItem == null) + { + //if null, create a folder + folderMediaItem = mediaService.CreateMedia(folderName, mediaRoot, Constants.Conventions.MediaTypes.Folder); + mediaService.Save(folderMediaItem); + } + } + //set the media root to the folder id so uploaded files will end there. + parentId = folderMediaItem.Id; + } + } + + //get the files + foreach (var file in result.FileData) + { + var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }); + var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower(); + + if (UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext) == false) + { + var mediaType = Constants.Conventions.MediaTypes.File; + + if (UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.Contains(ext)) + mediaType = Constants.Conventions.MediaTypes.Image; + + //TODO: make the media item name "nice" since file names could be pretty ugly, we have + // string extensions to do much of this but we'll need: + // * Pascalcase the name (use string extensions) + // * strip the file extension + // * underscores to spaces + // * probably remove 'ugly' characters - let's discuss + // All of this logic should exist in a string extensions method and be unit tested + // http://issues.umbraco.org/issue/U4-5572 + var mediaItemName = fileName; + + var f = mediaService.CreateMedia(mediaItemName, parentId, mediaType, Security.CurrentUser.Id); + + var fileInfo = new FileInfo(file.LocalFileName); + var fs = fileInfo.OpenReadWithRetry(); + if (fs == null) throw new InvalidOperationException("Could not acquire file stream"); + using (fs) + { + f.SetValue(Constants.Conventions.Media.File, fileName, fs); + } + + var saveResult = mediaService.WithResult().Save(f, Security.CurrentUser.Id); + if (saveResult == false) + { + AddCancelMessage(tempFiles, + message: Services.TextService.Localize("speechBubbles/operationCancelledText") + " -- " + mediaItemName, + localizeMessage: false); + } + else + { + tempFiles.UploadedFiles.Add(new ContentItemFile + { + FileName = fileName, + PropertyAlias = Constants.Conventions.Media.File, + TempFilePath = file.LocalFileName + }); + } + } + else + { + tempFiles.Notifications.Add(new Notification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + "Cannot upload file " + file.Headers.ContentDisposition.FileName + ", it is not an approved file type", + SpeechBubbleIcon.Warning)); + } + } + + //Different response if this is a 'blueimp' request + if (Request.GetQueryNameValuePairs().Any(x => x.Key == "origin")) + { + var origin = Request.GetQueryNameValuePairs().First(x => x.Key == "origin"); + if (origin.Value == "blueimp") + { + return Request.CreateResponse(HttpStatusCode.OK, + tempFiles, + //Don't output the angular xsrf stuff, blue imp doesn't like that + new JsonMediaTypeFormatter()); + } + } + + return Request.CreateResponse(HttpStatusCode.OK, tempFiles); + } + + /// + /// This is used for the response of PostAddFile so that we can analyze the response in a filter and remove the + /// temporary files that were created. + /// + [DataContract] + private class PostedFiles : IHaveUploadedFiles, INotificationModel + { + public PostedFiles() + { + UploadedFiles = new List(); + Notifications = new List(); + } + public List UploadedFiles { get; private set; } + + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } + } + + /// + /// Ensures the item can be moved/copied to the new location + /// + /// + /// + private IMedia ValidateMoveOrCopy(MoveOrCopy model) + { + if (model == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var mediaService = Services.MediaService; + var toMove = mediaService.GetById(model.Id); + if (toMove == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + if (model.ParentId < 0) + { + //cannot move if the content item is not allowed at the root + if (toMove.ContentType.AllowedAsRoot == false) + { + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"), ""); + throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); + } + } + else + { + var parent = mediaService.GetById(model.ParentId); + if (parent == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + //check if the item is allowed under this one + if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() + .Any(x => x.Value == toMove.ContentType.Id) == false) + { + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByContentType"), ""); + throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); + } + + // Check on paths + if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) + { + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByPath"), ""); + throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); + } + } + + return toMove; + } + + /// + /// Performs a permissions check for the user to check if it has access to the node based on + /// start node and/or permissions for the node + /// + /// The storage to add the content item to so it can be reused + /// + /// + /// The content to lookup, if the contentItem is not specified + /// Specifies the already resolved content item to check against, setting this ignores the nodeId + /// + internal static bool CheckPermissions(IDictionary storage, IUser user, IMediaService mediaService, int nodeId, IMedia media = null) + { + if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) + { + media = mediaService.GetById(nodeId); + //put the content item into storage so it can be retreived + // in the controller (saves a lookup) + storage[typeof(IMedia).ToString()] = media; + } + + if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var hasPathAccess = (nodeId == Constants.System.Root) + ? UserExtensions.HasPathAccess( + Constants.System.Root.ToInvariantString(), + user.StartMediaId, + Constants.System.RecycleBinMedia) + : (nodeId == Constants.System.RecycleBinMedia) + ? UserExtensions.HasPathAccess( + Constants.System.RecycleBinMedia.ToInvariantString(), + user.StartMediaId, + Constants.System.RecycleBinMedia) + : user.HasPathAccess(media); + + return hasPathAccess; + } + } } diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 909189b7ad..a2f1090a63 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -34,248 +34,251 @@ using Examine; namespace Umbraco.Web.Editors { - /// - /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting - /// access to ALL of the methods on this controller will need access to the member application. - /// - [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Members)] - [OutgoingNoHyphenGuidFormat] - public class MemberController : ContentControllerBase - { - /// - /// Constructor - /// - public MemberController() - : this(UmbracoContext.Current) - { - _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - } + /// + /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting + /// access to ALL of the methods on this controller will need access to the member application. + /// + [PluginController("UmbracoApi")] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Members)] + [OutgoingNoHyphenGuidFormat] + public class MemberController : ContentControllerBase + { + /// + /// Constructor + /// + public MemberController() + : this(UmbracoContext.Current) + { + _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + } - /// - /// Constructor - /// - /// - public MemberController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - } + /// + /// Constructor + /// + /// + public MemberController(UmbracoContext umbracoContext) + : base(umbracoContext) + { + _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + } - private readonly MembershipProvider _provider; + private readonly MembershipProvider _provider; - /// - /// Returns the currently configured membership scenario for members in umbraco - /// - /// - protected MembershipScenario MembershipScenario - { - get { return Services.MemberService.GetMembershipScenario(); } - } + /// + /// Returns the currently configured membership scenario for members in umbraco + /// + /// + protected MembershipScenario MembershipScenario + { + get { return Services.MemberService.GetMembershipScenario(); } + } - public PagedResult GetPagedResults( - int pageNumber = 1, - int pageSize = 100, - string orderBy = "Name", - Direction orderDirection = Direction.Ascending, - string filter = "", - string memberTypeAlias = null) - { - - if (pageNumber <= 0 || pageSize <= 0) - { - throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero"); - } + public PagedResult GetPagedResults( + int pageNumber = 1, + int pageSize = 100, + string orderBy = "Name", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "", + string memberTypeAlias = null) + { - if (MembershipScenario == MembershipScenario.NativeUmbraco) - { - long totalRecords; - var members = Services.MemberService.GetAll((pageNumber - 1), pageSize, out totalRecords, orderBy, orderDirection, memberTypeAlias, filter).ToArray(); - if (totalRecords == 0) - { - return new PagedResult(0, 0, 0); - } - var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize); - pagedResult.Items = members - .Select(Mapper.Map); - return pagedResult; - } - else - { - int totalRecords; - var members = _provider.GetAllUsers((pageNumber - 1), pageSize, out totalRecords); - if (totalRecords == 0) - { - return new PagedResult(0, 0, 0); - } - var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize); - pagedResult.Items = members - .Cast() - .Select(Mapper.Map); - return pagedResult; - } - - } + if (pageNumber <= 0 || pageSize <= 0) + { + throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero"); + } - /// - /// Returns a display node with a list view to render members - /// - /// - /// - public MemberListDisplay GetListNodeDisplay(string listName) - { - var display = new MemberListDisplay - { - ContentTypeAlias = listName, - ContentTypeName = listName, - Id = listName, - IsContainer = true, - Name = listName == Constants.Conventions.MemberTypes.AllMembersListId ? "All Members" : listName, - Path = "-1," + listName, - ParentId = -1 - }; + if (MembershipScenario == MembershipScenario.NativeUmbraco) + { + long totalRecords; + var members = Services.MemberService +.GetAll((pageNumber - 1), pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField +, memberTypeAlias, filter).ToArray(); + if (totalRecords == 0) + { + return new PagedResult(0, 0, 0); + } + var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize); + pagedResult.Items = members + .Select(Mapper.Map); + return pagedResult; + } + else + { + int totalRecords; + var members = _provider.GetAllUsers((pageNumber - 1), pageSize, out totalRecords); + if (totalRecords == 0) + { + return new PagedResult(0, 0, 0); + } + var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize); + pagedResult.Items = members + .Cast() + .Select(Mapper.Map); + return pagedResult; + } - TabsAndPropertiesResolver.AddListView(display, "member", Services.DataTypeService, Services.TextService); + } - return display; - } + /// + /// Returns a display node with a list view to render members + /// + /// + /// + public MemberListDisplay GetListNodeDisplay(string listName) + { + var display = new MemberListDisplay + { + ContentTypeAlias = listName, + ContentTypeName = listName, + Id = listName, + IsContainer = true, + Name = listName == Constants.Conventions.MemberTypes.AllMembersListId ? "All Members" : listName, + Path = "-1," + listName, + ParentId = -1 + }; - /// - /// Gets the content json for the member - /// - /// - /// - [OutgoingEditorModelEvent] - public MemberDisplay GetByKey(Guid key) - { - MembershipUser foundMembershipMember; - MemberDisplay display; - IMember foundMember; - switch (MembershipScenario) - { - case MembershipScenario.NativeUmbraco: - foundMember = Services.MemberService.GetByKey(key); - if (foundMember == null) - { - HandleContentNotFound(key); - } - return Mapper.Map(foundMember); - case MembershipScenario.CustomProviderWithUmbracoLink: + TabsAndPropertiesResolver.AddListView(display, "member", Services.DataTypeService, Services.TextService); - //TODO: Support editing custom properties for members with a custom membership provider here. + return display; + } - //foundMember = Services.MemberService.GetByKey(key); - //if (foundMember == null) - //{ - // HandleContentNotFound(key); - //} - //foundMembershipMember = Membership.GetUser(key, false); - //if (foundMembershipMember == null) - //{ - // HandleContentNotFound(key); - //} + /// + /// Gets the content json for the member + /// + /// + /// + [OutgoingEditorModelEvent] + public MemberDisplay GetByKey(Guid key) + { + MembershipUser foundMembershipMember; + MemberDisplay display; + IMember foundMember; + switch (MembershipScenario) + { + case MembershipScenario.NativeUmbraco: + foundMember = Services.MemberService.GetByKey(key); + if (foundMember == null) + { + HandleContentNotFound(key); + } + return Mapper.Map(foundMember); + case MembershipScenario.CustomProviderWithUmbracoLink: - //display = Mapper.Map(foundMembershipMember); - ////map the name over - //display.Name = foundMember.Name; - //return display; + //TODO: Support editing custom properties for members with a custom membership provider here. - case MembershipScenario.StandaloneCustomProvider: - default: - foundMembershipMember = _provider.GetUser(key, false); - if (foundMembershipMember == null) - { - HandleContentNotFound(key); - } - display = Mapper.Map(foundMembershipMember); - return display; - } - } + //foundMember = Services.MemberService.GetByKey(key); + //if (foundMember == null) + //{ + // HandleContentNotFound(key); + //} + //foundMembershipMember = Membership.GetUser(key, false); + //if (foundMembershipMember == null) + //{ + // HandleContentNotFound(key); + //} - /// - /// Gets an empty content item for the - /// - /// - /// - [OutgoingEditorModelEvent] - public MemberDisplay GetEmpty(string contentTypeAlias = null) - { - IMember emptyContent; - switch (MembershipScenario) - { - case MembershipScenario.NativeUmbraco: - if (contentTypeAlias == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } + //display = Mapper.Map(foundMembershipMember); + ////map the name over + //display.Name = foundMember.Name; + //return display; - var contentType = Services.MemberTypeService.Get(contentTypeAlias); - if (contentType == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } + case MembershipScenario.StandaloneCustomProvider: + default: + foundMembershipMember = _provider.GetUser(key, false); + if (foundMembershipMember == null) + { + HandleContentNotFound(key); + } + display = Mapper.Map(foundMembershipMember); + return display; + } + } - emptyContent = new Member(contentType); - emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(Membership.MinRequiredPasswordLength, Membership.MinRequiredNonAlphanumericCharacters); - return Mapper.Map(emptyContent); - case MembershipScenario.CustomProviderWithUmbracoLink: - //TODO: Support editing custom properties for members with a custom membership provider here. + /// + /// Gets an empty content item for the + /// + /// + /// + [OutgoingEditorModelEvent] + public MemberDisplay GetEmpty(string contentTypeAlias = null) + { + IMember emptyContent; + switch (MembershipScenario) + { + case MembershipScenario.NativeUmbraco: + if (contentTypeAlias == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } - case MembershipScenario.StandaloneCustomProvider: - default: - //we need to return a scaffold of a 'simple' member - basically just what a membership provider can edit - emptyContent = MemberService.CreateGenericMembershipProviderMember("", "", "", ""); - emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(Membership.MinRequiredPasswordLength, Membership.MinRequiredNonAlphanumericCharacters); - return Mapper.Map(emptyContent); - } - } + var contentType = Services.MemberTypeService.Get(contentTypeAlias); + if (contentType == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } - /// - /// Saves member - /// - /// - [FileUploadCleanupFilter] - public MemberDisplay PostSave( - [ModelBinder(typeof(MemberBinder))] - MemberSave contentItem) - { + emptyContent = new Member(contentType); + emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(Membership.MinRequiredPasswordLength, Membership.MinRequiredNonAlphanumericCharacters); + return Mapper.Map(emptyContent); + case MembershipScenario.CustomProviderWithUmbracoLink: + //TODO: Support editing custom properties for members with a custom membership provider here. - //If we've reached here it means: - // * Our model has been bound - // * and validated - // * any file attachments have been saved to their temporary location for us to use - // * we have a reference to the DTO object and the persisted object - // * Permissions are valid + case MembershipScenario.StandaloneCustomProvider: + default: + //we need to return a scaffold of a 'simple' member - basically just what a membership provider can edit + emptyContent = MemberService.CreateGenericMembershipProviderMember("", "", "", ""); + emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(Membership.MinRequiredPasswordLength, Membership.MinRequiredNonAlphanumericCharacters); + return Mapper.Map(emptyContent); + } + } - //This is a special case for when we're not using the umbraco membership provider - when this is the case - // we will not have a ContentTypeAlias set which means the model state will be invalid but we don't care about that - // so we'll remove that model state value - if (MembershipScenario != MembershipScenario.NativeUmbraco) - { - ModelState.Remove("ContentTypeAlias"); + /// + /// Saves member + /// + /// + [FileUploadCleanupFilter] + public MemberDisplay PostSave( + [ModelBinder(typeof(MemberBinder))] + MemberSave contentItem) + { - //TODO: We're removing this because we are not displaying it but when we support the CustomProviderWithUmbracoLink scenario - // we will be able to have a real name associated so do not remove this state once that is implemented! - ModelState.Remove("Name"); - } + //If we've reached here it means: + // * Our model has been bound + // * and validated + // * any file attachments have been saved to their temporary location for us to use + // * we have a reference to the DTO object and the persisted object + // * Permissions are valid - //map the properties to the persisted entity - MapPropertyValues(contentItem); + //This is a special case for when we're not using the umbraco membership provider - when this is the case + // we will not have a ContentTypeAlias set which means the model state will be invalid but we don't care about that + // so we'll remove that model state value + if (MembershipScenario != MembershipScenario.NativeUmbraco) + { + ModelState.Remove("ContentTypeAlias"); - //Unlike content/media - if there are errors for a member, we do NOT proceed to save them, we cannot so return the errors - if (ModelState.IsValid == false) - { - var forDisplay = Mapper.Map(contentItem.PersistedContent); - forDisplay.Errors = ModelState.ToErrorDictionary(); - throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); - } + //TODO: We're removing this because we are not displaying it but when we support the CustomProviderWithUmbracoLink scenario + // we will be able to have a real name associated so do not remove this state once that is implemented! + ModelState.Remove("Name"); + } + + //map the properties to the persisted entity + MapPropertyValues(contentItem); + + //Unlike content/media - if there are errors for a member, we do NOT proceed to save them, we cannot so return the errors + if (ModelState.IsValid == false) + { + var forDisplay = Mapper.Map(contentItem.PersistedContent); + forDisplay.Errors = ModelState.ToErrorDictionary(); + throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); + } + + //TODO: WE need to support this! - requires UI updates, etc... + if (_provider.RequiresQuestionAndAnswer) + { + throw new NotSupportedException("Currently the member editor does not support providers that have RequiresQuestionAndAnswer specified"); + } - //TODO: WE need to support this! - requires UI updates, etc... - if (_provider.RequiresQuestionAndAnswer) - { - throw new NotSupportedException("Currently the member editor does not support providers that have RequiresQuestionAndAnswer specified"); - } - //We're gonna look up the current roles now because the below code can cause // events to be raised and developers could be manually adding roles to members in // their handlers. If we don't look this up now there's a chance we'll just end up @@ -284,473 +287,473 @@ namespace Umbraco.Web.Editors //find the ones to remove and remove them var rolesToRemove = currRoles.Except(contentItem.Groups).ToArray(); - string generatedPassword = null; - //Depending on the action we need to first do a create or update using the membership provider - // this ensures that passwords are formatted correclty and also performs the validation on the provider itself. - switch (contentItem.Action) - { - case ContentSaveAction.Save: - generatedPassword = UpdateWithMembershipProvider(contentItem); - break; - case ContentSaveAction.SaveNew: - MembershipCreateStatus status; - CreateWithMembershipProvider(contentItem, out status); + string generatedPassword = null; + //Depending on the action we need to first do a create or update using the membership provider + // this ensures that passwords are formatted correclty and also performs the validation on the provider itself. + switch (contentItem.Action) + { + case ContentSaveAction.Save: + generatedPassword = UpdateWithMembershipProvider(contentItem); + break; + case ContentSaveAction.SaveNew: + MembershipCreateStatus status; + CreateWithMembershipProvider(contentItem, out status); - // save the ID of the creator - contentItem.PersistedContent.CreatorId = Security.CurrentUser.Id; - break; - default: - //we don't support anything else for members - throw new HttpResponseException(HttpStatusCode.NotFound); - } + // save the ID of the creator + contentItem.PersistedContent.CreatorId = Security.CurrentUser.Id; + break; + default: + //we don't support anything else for members + throw new HttpResponseException(HttpStatusCode.NotFound); + } - //If we've had problems creating/updating the user with the provider then return the error - if (ModelState.IsValid == false) - { - var forDisplay = Mapper.Map(contentItem.PersistedContent); - forDisplay.Errors = ModelState.ToErrorDictionary(); - throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); - } + //If we've had problems creating/updating the user with the provider then return the error + if (ModelState.IsValid == false) + { + var forDisplay = Mapper.Map(contentItem.PersistedContent); + forDisplay.Errors = ModelState.ToErrorDictionary(); + throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); + } - //save the IMember - - //TODO: When we support the CustomProviderWithUmbracoLink scenario, we'll need to save the custom properties for that here too - if (MembershipScenario == MembershipScenario.NativeUmbraco) - { - //save the item - //NOTE: We are setting the password to NULL - this indicates to the system to not actually save the password - // so it will not get overwritten! - contentItem.PersistedContent.RawPasswordValue = null; + //save the IMember - + //TODO: When we support the CustomProviderWithUmbracoLink scenario, we'll need to save the custom properties for that here too + if (MembershipScenario == MembershipScenario.NativeUmbraco) + { + //save the item + //NOTE: We are setting the password to NULL - this indicates to the system to not actually save the password + // so it will not get overwritten! + contentItem.PersistedContent.RawPasswordValue = null; - //create/save the IMember - Services.MemberService.Save(contentItem.PersistedContent); - } - - //Now let's do the role provider stuff - now that we've saved the content item (that is important since - // if we are changing the username, it must be persisted before looking up the member roles). + //create/save the IMember + Services.MemberService.Save(contentItem.PersistedContent); + } + + //Now let's do the role provider stuff - now that we've saved the content item (that is important since + // if we are changing the username, it must be persisted before looking up the member roles). if (rolesToRemove.Any()) - { + { Roles.RemoveUserFromRoles(contentItem.PersistedContent.Username, rolesToRemove); - } - //find the ones to add and add them + } + //find the ones to add and add them var toAdd = contentItem.Groups.Except(currRoles).ToArray(); - if (toAdd.Any()) - { - //add the ones submitted - Roles.AddUserToRoles(contentItem.PersistedContent.Username, toAdd); - } + if (toAdd.Any()) + { + //add the ones submitted + Roles.AddUserToRoles(contentItem.PersistedContent.Username, toAdd); + } - //set the generated password (if there was one) - in order to do this we'll chuck the gen'd password into the - // additional data of the IUmbracoEntity of the persisted item - then we can retrieve this in the model mapper and set - // the value to be given to the UI. Hooray for AdditionalData :) - contentItem.PersistedContent.AdditionalData["GeneratedPassword"] = generatedPassword; + //set the generated password (if there was one) - in order to do this we'll chuck the gen'd password into the + // additional data of the IUmbracoEntity of the persisted item - then we can retrieve this in the model mapper and set + // the value to be given to the UI. Hooray for AdditionalData :) + contentItem.PersistedContent.AdditionalData["GeneratedPassword"] = generatedPassword; - //return the updated model - var display = Mapper.Map(contentItem.PersistedContent); + //return the updated model + var display = Mapper.Map(contentItem.PersistedContent); - //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 - HandleInvalidModelState(display); + //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 + HandleInvalidModelState(display); - var localizedTextService = Services.TextService; - //put the correct msgs in - switch (contentItem.Action) - { - case ContentSaveAction.Save: - case ContentSaveAction.SaveNew: - display.AddSuccessNotification(localizedTextService.Localize("speechBubbles/editMemberSaved"), localizedTextService.Localize("speechBubbles/editMemberSaved")); - break; - } + var localizedTextService = Services.TextService; + //put the correct msgs in + switch (contentItem.Action) + { + case ContentSaveAction.Save: + case ContentSaveAction.SaveNew: + display.AddSuccessNotification(localizedTextService.Localize("speechBubbles/editMemberSaved"), localizedTextService.Localize("speechBubbles/editMemberSaved")); + break; + } - return display; - } + return display; + } - /// - /// Maps the property values to the persisted entity - /// - /// - private void MapPropertyValues(MemberSave contentItem) - { - UpdateName(contentItem); + /// + /// Maps the property values to the persisted entity + /// + /// + private void MapPropertyValues(MemberSave contentItem) + { + UpdateName(contentItem); - //map the custom properties - this will already be set for new entities in our member binder - contentItem.PersistedContent.Email = contentItem.Email; - contentItem.PersistedContent.Username = contentItem.Username; + //map the custom properties - this will already be set for new entities in our member binder + contentItem.PersistedContent.Email = contentItem.Email; + contentItem.PersistedContent.Username = contentItem.Username; - //use the base method to map the rest of the properties - base.MapPropertyValues(contentItem); - } + //use the base method to map the rest of the properties + base.MapPropertyValues(contentItem); + } - /// - /// Update the membership user using the membership provider (for things like email, etc...) - /// If a password change is detected then we'll try that too. - /// - /// - /// - /// If the password has been reset then this method will return the reset/generated password, otherwise will return null. - /// - private string UpdateWithMembershipProvider(MemberSave contentItem) - { - //Get the member from the provider + /// + /// Update the membership user using the membership provider (for things like email, etc...) + /// If a password change is detected then we'll try that too. + /// + /// + /// + /// If the password has been reset then this method will return the reset/generated password, otherwise will return null. + /// + private string UpdateWithMembershipProvider(MemberSave contentItem) + { + //Get the member from the provider - var membershipUser = _provider.GetUser(contentItem.PersistedContent.Key, false); - if (membershipUser == null) - { - //This should never happen! so we'll let it YSOD if it does. - throw new InvalidOperationException("Could not get member from membership provider " + _provider.Name + " with key " + contentItem.PersistedContent.Key); - } + var membershipUser = _provider.GetUser(contentItem.PersistedContent.Key, false); + if (membershipUser == null) + { + //This should never happen! so we'll let it YSOD if it does. + throw new InvalidOperationException("Could not get member from membership provider " + _provider.Name + " with key " + contentItem.PersistedContent.Key); + } - var shouldReFetchMember = false; - var providedUserName = contentItem.PersistedContent.Username; + var shouldReFetchMember = false; + var providedUserName = contentItem.PersistedContent.Username; - //Update the membership user if it has changed - try - { - var requiredUpdating = Members.UpdateMember(membershipUser, _provider, - contentItem.Email.Trim(), - contentItem.IsApproved, - comment: contentItem.Comments); + //Update the membership user if it has changed + try + { + var requiredUpdating = Members.UpdateMember(membershipUser, _provider, + contentItem.Email.Trim(), + contentItem.IsApproved, + comment: contentItem.Comments); - if (requiredUpdating.Success) - { - //re-map these values - shouldReFetchMember = true; - } - } - catch (Exception ex) - { - LogHelper.WarnWithException("Could not update member, the provider returned an error", ex); - ModelState.AddPropertyError( - //specify 'default' just so that it shows up as a notification - is not assigned to a property - new ValidationResult("Could not update member, the provider returned an error: " + ex.Message + " (see log for full details)"), "default"); - } + if (requiredUpdating.Success) + { + //re-map these values + shouldReFetchMember = true; + } + } + catch (Exception ex) + { + LogHelper.WarnWithException("Could not update member, the provider returned an error", ex); + ModelState.AddPropertyError( + //specify 'default' just so that it shows up as a notification - is not assigned to a property + new ValidationResult("Could not update member, the provider returned an error: " + ex.Message + " (see log for full details)"), "default"); + } - //if they were locked but now they are trying to be unlocked - if (membershipUser.IsLockedOut && contentItem.IsLockedOut == false) - { - try - { - var result = _provider.UnlockUser(membershipUser.UserName); - if (result == false) - { - //it wasn't successful - but it won't really tell us why. - ModelState.AddModelError("custom", "Could not unlock the user"); - } - else - { - shouldReFetchMember = true; - } - } - catch (Exception ex) - { - ModelState.AddModelError("custom", ex); - } - } - else if (membershipUser.IsLockedOut == false && contentItem.IsLockedOut) - { - //NOTE: This should not ever happen unless someone is mucking around with the request data. - //An admin cannot simply lock a user, they get locked out by password attempts, but an admin can un-approve them - ModelState.AddModelError("custom", "An admin cannot lock a user"); - } + //if they were locked but now they are trying to be unlocked + if (membershipUser.IsLockedOut && contentItem.IsLockedOut == false) + { + try + { + var result = _provider.UnlockUser(membershipUser.UserName); + if (result == false) + { + //it wasn't successful - but it won't really tell us why. + ModelState.AddModelError("custom", "Could not unlock the user"); + } + else + { + shouldReFetchMember = true; + } + } + catch (Exception ex) + { + ModelState.AddModelError("custom", ex); + } + } + else if (membershipUser.IsLockedOut == false && contentItem.IsLockedOut) + { + //NOTE: This should not ever happen unless someone is mucking around with the request data. + //An admin cannot simply lock a user, they get locked out by password attempts, but an admin can un-approve them + ModelState.AddModelError("custom", "An admin cannot lock a user"); + } - //password changes ? - if (contentItem.Password == null) - { - //If the provider has changed some values, these values need to be reflected in the member object - //that will get mapped to the display object - if (shouldReFetchMember) - { - RefetchMemberData(contentItem, LookupType.ByKey); - RestoreProvidedUserName(contentItem, providedUserName); - } + //password changes ? + if (contentItem.Password == null) + { + //If the provider has changed some values, these values need to be reflected in the member object + //that will get mapped to the display object + if (shouldReFetchMember) + { + RefetchMemberData(contentItem, LookupType.ByKey); + RestoreProvidedUserName(contentItem, providedUserName); + } - return null; - } + return null; + } - var passwordChangeResult = Members.ChangePassword(membershipUser.UserName, contentItem.Password, _provider); - if (passwordChangeResult.Success) - { - //If the provider has changed some values, these values need to be reflected in the member object - //that will get mapped to the display object - if (shouldReFetchMember) - { - RefetchMemberData(contentItem, LookupType.ByKey); - RestoreProvidedUserName(contentItem, providedUserName); - } + var passwordChangeResult = Members.ChangePassword(membershipUser.UserName, contentItem.Password, _provider); + if (passwordChangeResult.Success) + { + //If the provider has changed some values, these values need to be reflected in the member object + //that will get mapped to the display object + if (shouldReFetchMember) + { + RefetchMemberData(contentItem, LookupType.ByKey); + RestoreProvidedUserName(contentItem, providedUserName); + } - //even if we weren't resetting this, it is the correct value (null), otherwise if we were resetting then it will contain the new pword - return passwordChangeResult.Result.ResetPassword; - } + //even if we weren't resetting this, it is the correct value (null), otherwise if we were resetting then it will contain the new pword + return passwordChangeResult.Result.ResetPassword; + } - //it wasn't successful, so add the change error to the model state - ModelState.AddPropertyError( - passwordChangeResult.Result.ChangeError, - string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + //it wasn't successful, so add the change error to the model state + ModelState.AddPropertyError( + passwordChangeResult.Result.ChangeError, + string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - return null; - } + return null; + } - private enum LookupType - { - ByKey, - ByUserName - } + private enum LookupType + { + ByKey, + ByUserName + } - /// - /// Re-fetches the database data to map to the PersistedContent object and re-assigns the already mapped the posted properties so that the display object is up-to-date - /// - /// - /// - /// This is done during an update if the membership provider has changed some underlying data - we need to ensure that our model is consistent with that data - /// - private void RefetchMemberData(MemberSave contentItem, LookupType lookup) - { - var currProps = contentItem.PersistedContent.Properties.ToArray(); - - switch (MembershipScenario) - { - case MembershipScenario.NativeUmbraco: - switch (lookup) - { - case LookupType.ByKey: - //Go and re-fetch the persisted item - contentItem.PersistedContent = Services.MemberService.GetByKey(contentItem.Key); - break; - case LookupType.ByUserName: - contentItem.PersistedContent = Services.MemberService.GetByUsername(contentItem.Username.Trim()); - break; - } - break; - case MembershipScenario.CustomProviderWithUmbracoLink: - case MembershipScenario.StandaloneCustomProvider: - default: - var membershipUser = _provider.GetUser(contentItem.Key, false); - //Go and re-fetch the persisted item - contentItem.PersistedContent = Mapper.Map(membershipUser); - break; - } + /// + /// Re-fetches the database data to map to the PersistedContent object and re-assigns the already mapped the posted properties so that the display object is up-to-date + /// + /// + /// + /// This is done during an update if the membership provider has changed some underlying data - we need to ensure that our model is consistent with that data + /// + private void RefetchMemberData(MemberSave contentItem, LookupType lookup) + { + var currProps = contentItem.PersistedContent.Properties.ToArray(); - UpdateName(contentItem); + switch (MembershipScenario) + { + case MembershipScenario.NativeUmbraco: + switch (lookup) + { + case LookupType.ByKey: + //Go and re-fetch the persisted item + contentItem.PersistedContent = Services.MemberService.GetByKey(contentItem.Key); + break; + case LookupType.ByUserName: + contentItem.PersistedContent = Services.MemberService.GetByUsername(contentItem.Username.Trim()); + break; + } + break; + case MembershipScenario.CustomProviderWithUmbracoLink: + case MembershipScenario.StandaloneCustomProvider: + default: + var membershipUser = _provider.GetUser(contentItem.Key, false); + //Go and re-fetch the persisted item + contentItem.PersistedContent = Mapper.Map(membershipUser); + break; + } - //re-assign the mapped values that are not part of the membership provider properties. - var builtInAliases = Constants.Conventions.Member.GetStandardPropertyTypeStubs().Select(x => x.Key).ToArray(); - foreach (var p in contentItem.PersistedContent.Properties) - { - var valueMapped = currProps.SingleOrDefault(x => x.Alias == p.Alias); - if (builtInAliases.Contains(p.Alias) == false && valueMapped != null) - { - p.Value = valueMapped.Value; - p.TagSupport.Behavior = valueMapped.TagSupport.Behavior; - p.TagSupport.Enable = valueMapped.TagSupport.Enable; - p.TagSupport.Tags = valueMapped.TagSupport.Tags; - } - } - } + UpdateName(contentItem); - /// - /// Following a refresh of member data called during an update if the membership provider has changed some underlying data, - /// we don't want to lose the provided, and potentiallly changed, username - /// - /// - /// - private static void RestoreProvidedUserName(MemberSave contentItem, string providedUserName) - { - contentItem.PersistedContent.Username = providedUserName; - } + //re-assign the mapped values that are not part of the membership provider properties. + var builtInAliases = Constants.Conventions.Member.GetStandardPropertyTypeStubs().Select(x => x.Key).ToArray(); + foreach (var p in contentItem.PersistedContent.Properties) + { + var valueMapped = currProps.SingleOrDefault(x => x.Alias == p.Alias); + if (builtInAliases.Contains(p.Alias) == false && valueMapped != null) + { + p.Value = valueMapped.Value; + p.TagSupport.Behavior = valueMapped.TagSupport.Behavior; + p.TagSupport.Enable = valueMapped.TagSupport.Enable; + p.TagSupport.Tags = valueMapped.TagSupport.Tags; + } + } + } - /// - /// This is going to create the user with the membership provider and check for validation - /// - /// - /// - /// - /// - /// Depending on if the Umbraco membership provider is active or not, the process differs slightly: - /// - /// * If the umbraco membership provider is used - we create the membership user first with the membership provider, since - /// it's the umbraco membership provider, this writes to the umbraco tables. When that is complete we re-fetch the IMember - /// model data from the db. In this case we don't care what the provider user key is. - /// * If we're using a non-umbraco membership provider - we check if there is a 'Member' member type - if so - /// we create an empty IMember instance first (of type 'Member'), this gives us a unique ID (GUID) - /// that we then use to create the member in the custom membership provider. This acts as the link between Umbraco data and - /// the custom membership provider data. This gives us the ability to eventually have custom membership properties but still use - /// a custom memberhip provider. If there is no 'Member' member type, then we will simply just create the membership provider member - /// with no link to our data. - /// - /// If this is successful, it will go and re-fetch the IMember from the db because it will now have an ID because the Umbraco provider - /// uses the umbraco data store - then of course we need to re-map it to the saved property values. - /// - private MembershipUser CreateWithMembershipProvider(MemberSave contentItem, out MembershipCreateStatus status) - { - MembershipUser membershipUser; + /// + /// Following a refresh of member data called during an update if the membership provider has changed some underlying data, + /// we don't want to lose the provided, and potentiallly changed, username + /// + /// + /// + private static void RestoreProvidedUserName(MemberSave contentItem, string providedUserName) + { + contentItem.PersistedContent.Username = providedUserName; + } - switch (MembershipScenario) - { - case MembershipScenario.NativeUmbraco: - //We are using the umbraco membership provider, create the member using the membership provider first. - var umbracoMembershipProvider = (UmbracoMembershipProviderBase)_provider; - //TODO: We are not supporting q/a - passing in empty here - membershipUser = umbracoMembershipProvider.CreateUser( - contentItem.ContentTypeAlias, contentItem.Username, - contentItem.Password.NewPassword, - contentItem.Email, "", "", - contentItem.IsApproved, - Guid.NewGuid(), //since it's the umbraco provider, the user key here doesn't make any difference - out status); - - break; - case MembershipScenario.CustomProviderWithUmbracoLink: - //We are using a custom membership provider, we'll create an empty IMember first to get the unique id to use - // as the provider user key. - //create it - this persisted item has already been set in the MemberBinder based on the 'Member' member type: - Services.MemberService.Save(contentItem.PersistedContent); + /// + /// This is going to create the user with the membership provider and check for validation + /// + /// + /// + /// + /// + /// Depending on if the Umbraco membership provider is active or not, the process differs slightly: + /// + /// * If the umbraco membership provider is used - we create the membership user first with the membership provider, since + /// it's the umbraco membership provider, this writes to the umbraco tables. When that is complete we re-fetch the IMember + /// model data from the db. In this case we don't care what the provider user key is. + /// * If we're using a non-umbraco membership provider - we check if there is a 'Member' member type - if so + /// we create an empty IMember instance first (of type 'Member'), this gives us a unique ID (GUID) + /// that we then use to create the member in the custom membership provider. This acts as the link between Umbraco data and + /// the custom membership provider data. This gives us the ability to eventually have custom membership properties but still use + /// a custom memberhip provider. If there is no 'Member' member type, then we will simply just create the membership provider member + /// with no link to our data. + /// + /// If this is successful, it will go and re-fetch the IMember from the db because it will now have an ID because the Umbraco provider + /// uses the umbraco data store - then of course we need to re-map it to the saved property values. + /// + private MembershipUser CreateWithMembershipProvider(MemberSave contentItem, out MembershipCreateStatus status) + { + MembershipUser membershipUser; - //TODO: We are not supporting q/a - passing in empty here - membershipUser = _provider.CreateUser( - contentItem.Username, - contentItem.Password.NewPassword, - contentItem.Email, - "TEMP", //some membership provider's require something here even if q/a is disabled! - "TEMP", //some membership provider's require something here even if q/a is disabled! - contentItem.IsApproved, - contentItem.PersistedContent.Key, //custom membership provider, we'll link that based on the IMember unique id (GUID) - out status); + switch (MembershipScenario) + { + case MembershipScenario.NativeUmbraco: + //We are using the umbraco membership provider, create the member using the membership provider first. + var umbracoMembershipProvider = (UmbracoMembershipProviderBase)_provider; + //TODO: We are not supporting q/a - passing in empty here + membershipUser = umbracoMembershipProvider.CreateUser( + contentItem.ContentTypeAlias, contentItem.Username, + contentItem.Password.NewPassword, + contentItem.Email, "", "", + contentItem.IsApproved, + Guid.NewGuid(), //since it's the umbraco provider, the user key here doesn't make any difference + out status); - break; - case MembershipScenario.StandaloneCustomProvider: - // we don't have a member type to use so we will just create the basic membership user with the provider with no - // link back to the umbraco data - - var newKey = Guid.NewGuid(); - //TODO: We are not supporting q/a - passing in empty here - membershipUser = _provider.CreateUser( - contentItem.Username, - contentItem.Password.NewPassword, - contentItem.Email, - "TEMP", //some membership provider's require something here even if q/a is disabled! - "TEMP", //some membership provider's require something here even if q/a is disabled! - contentItem.IsApproved, - newKey, - out status); - - break; - default: - throw new ArgumentOutOfRangeException(); - } + break; + case MembershipScenario.CustomProviderWithUmbracoLink: + //We are using a custom membership provider, we'll create an empty IMember first to get the unique id to use + // as the provider user key. + //create it - this persisted item has already been set in the MemberBinder based on the 'Member' member type: + Services.MemberService.Save(contentItem.PersistedContent); - //TODO: Localize these! - switch (status) - { - case MembershipCreateStatus.Success: + //TODO: We are not supporting q/a - passing in empty here + membershipUser = _provider.CreateUser( + contentItem.Username, + contentItem.Password.NewPassword, + contentItem.Email, + "TEMP", //some membership provider's require something here even if q/a is disabled! + "TEMP", //some membership provider's require something here even if q/a is disabled! + contentItem.IsApproved, + contentItem.PersistedContent.Key, //custom membership provider, we'll link that based on the IMember unique id (GUID) + out status); - //map the key back - contentItem.Key = membershipUser.ProviderUserKey.TryConvertTo().Result; - contentItem.PersistedContent.Key = contentItem.Key; + break; + case MembershipScenario.StandaloneCustomProvider: + // we don't have a member type to use so we will just create the basic membership user with the provider with no + // link back to the umbraco data - //if the comments are there then we need to save them - if (contentItem.Comments.IsNullOrWhiteSpace() == false) - { - membershipUser.Comment = contentItem.Comments; - _provider.UpdateUser(membershipUser); - } + var newKey = Guid.NewGuid(); + //TODO: We are not supporting q/a - passing in empty here + membershipUser = _provider.CreateUser( + contentItem.Username, + contentItem.Password.NewPassword, + contentItem.Email, + "TEMP", //some membership provider's require something here even if q/a is disabled! + "TEMP", //some membership provider's require something here even if q/a is disabled! + contentItem.IsApproved, + newKey, + out status); - RefetchMemberData(contentItem, LookupType.ByUserName); + break; + default: + throw new ArgumentOutOfRangeException(); + } - break; - case MembershipCreateStatus.InvalidUserName: - ModelState.AddPropertyError( - new ValidationResult("Invalid user name", new[] { "value" }), - string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - break; - case MembershipCreateStatus.InvalidPassword: - ModelState.AddPropertyError( - new ValidationResult("Invalid password", new[] { "value" }), - string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - break; - case MembershipCreateStatus.InvalidQuestion: - case MembershipCreateStatus.InvalidAnswer: - throw new NotSupportedException("Currently the member editor does not support providers that have RequiresQuestionAndAnswer specified"); - case MembershipCreateStatus.InvalidEmail: - ModelState.AddPropertyError( - new ValidationResult("Invalid email", new[] { "value" }), - string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - break; - case MembershipCreateStatus.DuplicateUserName: - ModelState.AddPropertyError( - new ValidationResult("Username is already in use", new[] { "value" }), - string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - break; - case MembershipCreateStatus.DuplicateEmail: - ModelState.AddPropertyError( - new ValidationResult("Email address is already in use", new[] { "value" }), - string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - break; - case MembershipCreateStatus.InvalidProviderUserKey: - ModelState.AddPropertyError( - //specify 'default' just so that it shows up as a notification - is not assigned to a property - new ValidationResult("Invalid provider user key"), "default"); - break; - case MembershipCreateStatus.DuplicateProviderUserKey: - ModelState.AddPropertyError( - //specify 'default' just so that it shows up as a notification - is not assigned to a property - new ValidationResult("Duplicate provider user key"), "default"); - break; - case MembershipCreateStatus.ProviderError: - case MembershipCreateStatus.UserRejected: - ModelState.AddPropertyError( - //specify 'default' just so that it shows up as a notification - is not assigned to a property - new ValidationResult("User could not be created (rejected by provider)"), "default"); - break; - default: - throw new ArgumentOutOfRangeException(); - } + //TODO: Localize these! + switch (status) + { + case MembershipCreateStatus.Success: - return membershipUser; - } + //map the key back + contentItem.Key = membershipUser.ProviderUserKey.TryConvertTo().Result; + contentItem.PersistedContent.Key = contentItem.Key; - /// - /// Permanently deletes a member - /// - /// - /// - /// - [HttpPost] - public HttpResponseMessage DeleteByKey(Guid key) - { - IMember foundMember; - MembershipUser foundMembershipUser; - switch (MembershipScenario) - { - case MembershipScenario.NativeUmbraco: - foundMember = Services.MemberService.GetByKey(key); - if (foundMember == null) - { - return HandleContentNotFound(key, false); - } - Services.MemberService.Delete(foundMember); - break; - case MembershipScenario.CustomProviderWithUmbracoLink: - foundMember = Services.MemberService.GetByKey(key); - if (foundMember != null) - { - Services.MemberService.Delete(foundMember); - } - foundMembershipUser = _provider.GetUser(key, false); - if (foundMembershipUser != null) - { - _provider.DeleteUser(foundMembershipUser.UserName, true); - } - break; - case MembershipScenario.StandaloneCustomProvider: - foundMembershipUser = _provider.GetUser(key, false); - if (foundMembershipUser != null) - { - _provider.DeleteUser(foundMembershipUser.UserName, true); - } - break; - default: - throw new ArgumentOutOfRangeException(); - } + //if the comments are there then we need to save them + if (contentItem.Comments.IsNullOrWhiteSpace() == false) + { + membershipUser.Comment = contentItem.Comments; + _provider.UpdateUser(membershipUser); + } - return Request.CreateResponse(HttpStatusCode.OK); - } - } + RefetchMemberData(contentItem, LookupType.ByUserName); + + break; + case MembershipCreateStatus.InvalidUserName: + ModelState.AddPropertyError( + new ValidationResult("Invalid user name", new[] { "value" }), + string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + case MembershipCreateStatus.InvalidPassword: + ModelState.AddPropertyError( + new ValidationResult("Invalid password", new[] { "value" }), + string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + case MembershipCreateStatus.InvalidQuestion: + case MembershipCreateStatus.InvalidAnswer: + throw new NotSupportedException("Currently the member editor does not support providers that have RequiresQuestionAndAnswer specified"); + case MembershipCreateStatus.InvalidEmail: + ModelState.AddPropertyError( + new ValidationResult("Invalid email", new[] { "value" }), + string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + case MembershipCreateStatus.DuplicateUserName: + ModelState.AddPropertyError( + new ValidationResult("Username is already in use", new[] { "value" }), + string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + case MembershipCreateStatus.DuplicateEmail: + ModelState.AddPropertyError( + new ValidationResult("Email address is already in use", new[] { "value" }), + string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + case MembershipCreateStatus.InvalidProviderUserKey: + ModelState.AddPropertyError( + //specify 'default' just so that it shows up as a notification - is not assigned to a property + new ValidationResult("Invalid provider user key"), "default"); + break; + case MembershipCreateStatus.DuplicateProviderUserKey: + ModelState.AddPropertyError( + //specify 'default' just so that it shows up as a notification - is not assigned to a property + new ValidationResult("Duplicate provider user key"), "default"); + break; + case MembershipCreateStatus.ProviderError: + case MembershipCreateStatus.UserRejected: + ModelState.AddPropertyError( + //specify 'default' just so that it shows up as a notification - is not assigned to a property + new ValidationResult("User could not be created (rejected by provider)"), "default"); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + return membershipUser; + } + + /// + /// Permanently deletes a member + /// + /// + /// + /// + [HttpPost] + public HttpResponseMessage DeleteByKey(Guid key) + { + IMember foundMember; + MembershipUser foundMembershipUser; + switch (MembershipScenario) + { + case MembershipScenario.NativeUmbraco: + foundMember = Services.MemberService.GetByKey(key); + if (foundMember == null) + { + return HandleContentNotFound(key, false); + } + Services.MemberService.Delete(foundMember); + break; + case MembershipScenario.CustomProviderWithUmbracoLink: + foundMember = Services.MemberService.GetByKey(key); + if (foundMember != null) + { + Services.MemberService.Delete(foundMember); + } + foundMembershipUser = _provider.GetUser(key, false); + if (foundMembershipUser != null) + { + _provider.DeleteUser(foundMembershipUser.UserName, true); + } + break; + case MembershipScenario.StandaloneCustomProvider: + foundMembershipUser = _provider.GetUser(key, false); + if (foundMembershipUser != null) + { + _provider.DeleteUser(foundMembershipUser.UserName, true); + } + break; + default: + throw new ArgumentOutOfRangeException(); + } + + return Request.CreateResponse(HttpStatusCode.OK); + } + } } diff --git a/src/UmbracoExamine/UmbracoMemberIndexer.cs b/src/UmbracoExamine/UmbracoMemberIndexer.cs index 873391da50..8edc470bfa 100644 --- a/src/UmbracoExamine/UmbracoMemberIndexer.cs +++ b/src/UmbracoExamine/UmbracoMemberIndexer.cs @@ -16,207 +16,208 @@ using Lucene.Net.Analysis; namespace UmbracoExamine { - + + /// + /// Custom indexer for members + /// + public class UmbracoMemberIndexer : UmbracoContentIndexer + { + + private readonly IMemberService _memberService; + private readonly IDataTypeService _dataTypeService; + /// - /// Custom indexer for members - /// - public class UmbracoMemberIndexer : UmbracoContentIndexer - { + /// Default constructor + /// + public UmbracoMemberIndexer() : base() + { + _dataTypeService = ApplicationContext.Current.Services.DataTypeService; + _memberService = ApplicationContext.Current.Services.MemberService; + } - private readonly IMemberService _memberService; - private readonly IDataTypeService _dataTypeService; + /// + /// Constructor to allow for creating an indexer at runtime + /// + /// + /// + /// + /// + [Obsolete("Use the overload that specifies the Umbraco services")] + public UmbracoMemberIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, Analyzer analyzer, bool async) + : base(indexerData, indexPath, dataService, analyzer, async) + { + _dataTypeService = ApplicationContext.Current.Services.DataTypeService; + _memberService = ApplicationContext.Current.Services.MemberService; + } - /// - /// Default constructor - /// - public UmbracoMemberIndexer() : base() - { - _dataTypeService = ApplicationContext.Current.Services.DataTypeService; - _memberService = ApplicationContext.Current.Services.MemberService; - } + /// + /// Constructor to allow for creating an indexer at runtime + /// + /// + /// + /// + /// + /// + /// + /// + public UmbracoMemberIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, + IDataTypeService dataTypeService, + IMemberService memberService, + Analyzer analyzer, bool async) + : base(indexerData, indexPath, dataService, analyzer, async) + { + _dataTypeService = dataTypeService; + _memberService = memberService; + } - /// - /// Constructor to allow for creating an indexer at runtime - /// - /// - /// - /// - /// - [Obsolete("Use the overload that specifies the Umbraco services")] - public UmbracoMemberIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, Analyzer analyzer, bool async) - : base(indexerData, indexPath, dataService, analyzer, async) - { - _dataTypeService = ApplicationContext.Current.Services.DataTypeService; - _memberService = ApplicationContext.Current.Services.MemberService; - } - /// - /// Constructor to allow for creating an indexer at runtime - /// - /// - /// - /// - /// - /// - /// - /// - public UmbracoMemberIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, - IDataTypeService dataTypeService, - IMemberService memberService, - Analyzer analyzer, bool async) - : base(indexerData, indexPath, dataService, analyzer, async) - { - _dataTypeService = dataTypeService; - _memberService = memberService; - } - + /// + /// Ensures that the'_searchEmail' is added to the user fields so that it is indexed - without having to modify the config + /// + /// + /// + protected override IIndexCriteria GetIndexerData(IndexSet indexSet) + { + var indexerData = base.GetIndexerData(indexSet); - /// - /// Ensures that the'_searchEmail' is added to the user fields so that it is indexed - without having to modify the config - /// - /// - /// - protected override IIndexCriteria GetIndexerData(IndexSet indexSet) - { - var indexerData = base.GetIndexerData(indexSet); + if (CanInitialize()) + { + //If the fields are missing a custom _searchEmail, then add it - if (CanInitialize()) - { - //If the fields are missing a custom _searchEmail, then add it + if (indexerData.UserFields.Any(x => x.Name == "_searchEmail") == false) + { + var field = new IndexField { Name = "_searchEmail" }; + var policy = IndexFieldPolicies.FirstOrDefault(x => x.Name == "_searchEmail"); + if (policy != null) + { + field.Type = policy.Type; + field.EnableSorting = policy.EnableSorting; + } - if (indexerData.UserFields.Any(x => x.Name == "_searchEmail") == false) - { - var field = new IndexField {Name = "_searchEmail"}; - var policy = IndexFieldPolicies.FirstOrDefault(x => x.Name == "_searchEmail"); - if (policy != null) - { - field.Type = policy.Type; - field.EnableSorting = policy.EnableSorting; - } + return new IndexCriteria( + indexerData.StandardFields, + indexerData.UserFields.Concat(new[] { field }), + indexerData.IncludeNodeTypes, + indexerData.ExcludeNodeTypes, + indexerData.ParentNodeId + ); + } + } - return new IndexCriteria( - indexerData.StandardFields, - indexerData.UserFields.Concat(new[] {field}), - indexerData.IncludeNodeTypes, - indexerData.ExcludeNodeTypes, - indexerData.ParentNodeId - ); - } - } + return indexerData; + } - return indexerData; - } + /// + /// The supported types for this indexer + /// + protected override IEnumerable SupportedTypes + { + get + { + return new string[] { IndexTypes.Member }; + } + } - /// - /// The supported types for this indexer - /// - protected override IEnumerable SupportedTypes - { - get - { - return new string[] { IndexTypes.Member }; - } - } + /// + /// Reindex all members + /// + /// + protected override void PerformIndexAll(string type) + { + //This only supports members + if (SupportedTypes.Contains(type) == false) + return; - /// - /// Reindex all members - /// - /// - protected override void PerformIndexAll(string type) - { - //This only supports members - if (SupportedTypes.Contains(type) == false) - return; - - const int pageSize = 1000; - var pageIndex = 0; + const int pageSize = 1000; + var pageIndex = 0; - IMember[] members; + IMember[] members; - if (IndexerData.IncludeNodeTypes.Any()) - { - //if there are specific node types then just index those - foreach (var nodeType in IndexerData.IncludeNodeTypes) - { - do - { - long total; - members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName", Direction.Ascending, nodeType).ToArray(); + if (IndexerData.IncludeNodeTypes.Any()) + { + //if there are specific node types then just index those + foreach (var nodeType in IndexerData.IncludeNodeTypes) + { + do + { + long total; + members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName" + , Direction.Ascending, true, nodeType).ToArray(); - AddNodesToIndex(GetSerializedMembers(members), type); + AddNodesToIndex(GetSerializedMembers(members), type); - pageIndex++; - } while (members.Length == pageSize); - } - } - else - { - //no node types specified, do all members - do - { - int total; - members = _memberService.GetAll(pageIndex, pageSize, out total).ToArray(); + pageIndex++; + } while (members.Length == pageSize); + } + } + else + { + //no node types specified, do all members + do + { + int total; + members = _memberService.GetAll(pageIndex, pageSize, out total).ToArray(); - AddNodesToIndex(GetSerializedMembers(members), type); + AddNodesToIndex(GetSerializedMembers(members), type); - pageIndex++; - } while (members.Length == pageSize); - } - } + pageIndex++; + } while (members.Length == pageSize); + } + } - private IEnumerable GetSerializedMembers(IEnumerable members) - { - var serializer = new EntityXmlSerializer(); - return members.Select(member => serializer.Serialize(_dataTypeService, member)); - } + private IEnumerable GetSerializedMembers(IEnumerable members) + { + var serializer = new EntityXmlSerializer(); + return members.Select(member => serializer.Serialize(_dataTypeService, member)); + } - protected override XDocument GetXDocument(string xPath, string type) - { - throw new NotSupportedException(); - } - - protected override Dictionary GetSpecialFieldsToIndex(Dictionary allValuesForIndexing) - { - var fields = base.GetSpecialFieldsToIndex(allValuesForIndexing); + protected override XDocument GetXDocument(string xPath, string type) + { + throw new NotSupportedException(); + } - //adds the special path property to the index - fields.Add("__key", allValuesForIndexing["__key"]); - - return fields; + protected override Dictionary GetSpecialFieldsToIndex(Dictionary allValuesForIndexing) + { + var fields = base.GetSpecialFieldsToIndex(allValuesForIndexing); - } + //adds the special path property to the index + fields.Add("__key", allValuesForIndexing["__key"]); - /// - /// Add the special __key and _searchEmail fields - /// - /// - protected override void OnGatheringNodeData(IndexingNodeDataEventArgs e) - { - base.OnGatheringNodeData(e); + return fields; - if (e.Node.Attribute("key") != null) - { - if (e.Fields.ContainsKey("__key") == false) - e.Fields.Add("__key", e.Node.Attribute("key").Value); - } + } - if (e.Node.Attribute("email") != null) - { - //NOTE: the single underscore = it's not a 'special' field which means it will be indexed normally - if (e.Fields.ContainsKey("_searchEmail") == false) - e.Fields.Add("_searchEmail", e.Node.Attribute("email").Value.Replace(".", " ").Replace("@", " ")); - } - - if (e.Fields.ContainsKey(IconFieldName) == false) - e.Fields.Add(IconFieldName, (string)e.Node.Attribute("icon")); - } + /// + /// Add the special __key and _searchEmail fields + /// + /// + protected override void OnGatheringNodeData(IndexingNodeDataEventArgs e) + { + base.OnGatheringNodeData(e); - private static XElement GetMemberItem(int nodeId) - { - //TODO: Change this so that it is not using the LegacyLibrary, just serialize manually! - var nodes = LegacyLibrary.GetMember(nodeId); - return XElement.Parse(nodes.Current.OuterXml); - } - } + if (e.Node.Attribute("key") != null) + { + if (e.Fields.ContainsKey("__key") == false) + e.Fields.Add("__key", e.Node.Attribute("key").Value); + } + + if (e.Node.Attribute("email") != null) + { + //NOTE: the single underscore = it's not a 'special' field which means it will be indexed normally + if (e.Fields.ContainsKey("_searchEmail") == false) + e.Fields.Add("_searchEmail", e.Node.Attribute("email").Value.Replace(".", " ").Replace("@", " ")); + } + + if (e.Fields.ContainsKey(IconFieldName) == false) + e.Fields.Add(IconFieldName, (string)e.Node.Attribute("icon")); + } + + private static XElement GetMemberItem(int nodeId) + { + //TODO: Change this so that it is not using the LegacyLibrary, just serialize manually! + var nodes = LegacyLibrary.GetMember(nodeId); + return XElement.Parse(nodes.Current.OuterXml); + } + } } From 54394f2ce9fd106994d5257be957938d67496b2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ferreira?= Date: Wed, 2 Mar 2016 09:47:43 +0000 Subject: [PATCH 029/101] fixes --- .../components/umbtable.directive.js | 114 ++++++++--------- .../src/views/components/umb-table.html | 120 ++++++++---------- 2 files changed, 109 insertions(+), 125 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js index fe80d915da..8c74abf590 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js @@ -1,71 +1,71 @@ -(function() { - 'use strict'; +(function () { + 'use strict'; - function TableDirective() { + function TableDirective() { - function link(scope, el, attr, ctrl) { + function link(scope, el, attr, ctrl) { - scope.clickItem = function(item, $event) { - if(scope.onClick) { - scope.onClick(item); - $event.stopPropagation(); - } - }; + scope.clickItem = function (item, $event) { + if (scope.onClick) { + scope.onClick(item); + $event.stopPropagation(); + } + }; - scope.selectItem = function(item, $index, $event) { - if(scope.onSelect) { - scope.onSelect(item, $index, $event); - $event.stopPropagation(); - } - }; + scope.selectItem = function (item, $index, $event) { + if (scope.onSelect) { + scope.onSelect(item, $index, $event); + $event.stopPropagation(); + } + }; - scope.selectAll = function($event) { - if(scope.onSelectAll) { - scope.onSelectAll($event); - } - }; + scope.selectAll = function ($event) { + if (scope.onSelectAll) { + scope.onSelectAll($event); + } + }; - scope.isSelectedAll = function() { - if(scope.onSelectedAll && scope.items && scope.items.length > 0) { - return scope.onSelectedAll(); - } - }; + scope.isSelectedAll = function () { + if (scope.onSelectedAll && scope.items && scope.items.length > 0) { + return scope.onSelectedAll(); + } + }; - scope.isSortDirection = function (col, direction) { - if (scope.onSortingDirection) { - return scope.onSortingDirection(col, direction); - } - }; + scope.isSortDirection = function (col, direction) { + if (scope.onSortingDirection) { + return scope.onSortingDirection(col, direction); + } + }; - scope.sort = function(field, allow) { - if(scope.onSort) { - scope.onSort(field, allow); - } - }; + scope.sort = function (field, allow, isSystem) { + if (scope.onSort) { + scope.onSort(field, allow, isSystem); + } + }; - } + } - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-table.html', - scope: { - items: '=', - itemProperties: '=', - allowSelectAll: '=', - onSelect: '=', - onClick: '=', - onSelectAll: '=', - onSelectedAll: '=', - onSortingDirection: '=', - onSort: '=' - }, - link: link - }; + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-table.html', + scope: { + items: '=', + itemProperties: '=', + allowSelectAll: '=', + onSelect: '=', + onClick: '=', + onSelectAll: '=', + onSelectedAll: '=', + onSortingDirection: '=', + onSort: '=' + }, + link: link + }; - return directive; - } + return directive; + } - angular.module('umbraco.directives').directive('umbTable', TableDirective); + angular.module('umbraco.directives').directive('umbTable', TableDirective); })(); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html index fdd7dd23c9..1ab22eb406 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html @@ -1,76 +1,60 @@
- -
- - -
-
- -
- -
- - - - - -
-
- - -
-
+ +
+
+
+ +
+ + +
+
+ +
+
- - -
- - -
- -
- - -
- -
- {{item[column.alias]}} -
- -
-
+ ng-click="selectItem(item, $index, $event)"> +
+ +
- - - - There are no items show in the list. - - +
+ + +
+
+ {{item[column.alias]}} +
+
+
+
+ + + There are no items show in the list. +
From 08070e319a8dca9a722b9d86b64741d319e5c045 Mon Sep 17 00:00:00 2001 From: paulsterling Date: Mon, 28 Mar 2016 16:46:31 -0700 Subject: [PATCH 030/101] Update number of sites running --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5fb4cb868b..53070b6917 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,11 @@ If you're interested in making changes to Belle make sure to read the [Belle Rea ## Umbraco - the simple, flexible and friendly ASP.NET CMS ## -**More than 177,000 sites trust Umbraco** +**More than 350,000 sites trust Umbraco** For the first time on the Microsoft platform, there is a free user and developer friendly CMS that makes it quick and easy to create websites - or a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, out of the box. It's a developer's dream and your users will love it too. -Used by more than 177,000 active websites including [http://daviscup.com](http://daviscup.com), [http://heinz.com](http://heinz.com), [http://peugeot.com](http://peugeot.com), [http://www.hersheys.com/](http://www.hersheys.com/) and **The Official ASP.NET and IIS.NET website from Microsoft** ([http://asp.net](http://asp.net) / [http://iis.net](http://iis.net)), you can be sure that the technology is proven, stable and scales. +Used by more than 350,000 active websites including [http://daviscup.com](http://daviscup.com), [http://heinz.com](http://heinz.com), [http://peugeot.com](http://peugeot.com), [http://www.hersheys.com/](http://www.hersheys.com/) and **The Official ASP.NET and IIS.NET website from Microsoft** ([http://asp.net](http://asp.net) / [http://iis.net](http://iis.net)), you can be sure that the technology is proven, stable and scales. To view more examples, please visit [http://umbraco.com/why-umbraco/#caseStudies](http://umbraco.com/why-umbraco/#caseStudies) From c427c51053a970fa440209ef166f26e100a2968d Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 29 Mar 2016 14:29:27 +0200 Subject: [PATCH 031/101] U4-7862 Brackets cause issues in the stylesheet editor --- src/Umbraco.Core/StringExtensions.cs | 24 +++++++++++++++++++ .../Strings/Css/StylesheetHelper.cs | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 5aae1dbd4a..b92df5f1cf 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -1380,6 +1380,30 @@ namespace Umbraco.Core */ } + public static string EscapeRegexSpecialCharacters(this string text) + { + var regexSpecialCharacters = new Dictionary + { + {".", @"\."}, + {"(", @"\("}, + {")", @"\)"}, + {"]", @"\]"}, + {"[", @"\["}, + {"{", @"\{"}, + {"}", @"\}"}, + {"?", @"\?"}, + {"!", @"\!"}, + {"$", @"\$"}, + {"^", @"\^"}, + {"+", @"\+"}, + {"*", @"\*"}, + {"|", @"\|"}, + {"<", @"\<"}, + {">", @"\>"} + }; + return ReplaceMany(text, regexSpecialCharacters); + } + public static bool ContainsAny(this string haystack, IEnumerable needles, StringComparison comparison = StringComparison.CurrentCulture) { if (haystack == null) throw new ArgumentNullException("haystack"); diff --git a/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs b/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs index ff7591c6b5..260e8e5159 100644 --- a/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs +++ b/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs @@ -39,7 +39,7 @@ namespace Umbraco.Core.Strings.Css public static string ReplaceRule(string input, string oldRuleName, StylesheetRule rule) { var contents = input; - var ruleRegex = new Regex(string.Format(RuleRegexFormat, oldRuleName), RegexOptions.IgnoreCase | RegexOptions.Singleline); + var ruleRegex = new Regex(string.Format(RuleRegexFormat, oldRuleName.EscapeRegexSpecialCharacters()), RegexOptions.IgnoreCase | RegexOptions.Singleline); contents = ruleRegex.Replace(contents, rule != null ? rule.ToString() : ""); return contents; } From d560223ecadb85b7f69741a0159b14a7849fde02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ferreira?= Date: Tue, 29 Mar 2016 20:13:53 +0100 Subject: [PATCH 032/101] Fixes to make it work --- src/Umbraco.Web/Editors/ContentController.cs | 29 +++++---- .../Filters/DisableBrowserCacheAttribute.cs | 64 ++++++++++--------- 2 files changed, 49 insertions(+), 44 deletions(-) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index ff811ce4b2..217d3123b8 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -184,16 +184,17 @@ namespace Umbraco.Web.Editors int pageSize = 0, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, + int? orderBySystemField = 2, string filter = "") { + var orderBySystemFieldBool = orderBySystemField == 1 || orderBySystemField == 2; long totalChildren; IContent[] children; if (pageNumber > 0 && pageSize > 0) { children = Services.ContentService .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren - , orderBy, orderDirection, orderBySystemField, filter).ToArray(); + , orderBy, orderDirection, orderBySystemFieldBool, filter).ToArray(); } else { @@ -219,18 +220,18 @@ namespace Umbraco.Web.Editors return HasPermission(permissionToCheck, nodeId); } - /// - /// Returns permissions for all nodes passed in for the current user - /// - /// - /// - [HttpPost] - public Dictionary GetPermissions(int[] nodeIds) - { - return Services.UserService - .GetPermissions(Security.CurrentUser, nodeIds) - .ToDictionary(x => x.EntityId, x => x.AssignedPermissions); - } + /// + /// Returns permissions for all nodes passed in for the current user + /// + /// + /// + [HttpPost] + public Dictionary GetPermissions(int[] nodeIds) + { + return Services.UserService + .GetPermissions(Security.CurrentUser, nodeIds) + .ToDictionary(x => x.EntityId, x => x.AssignedPermissions); + } [HttpGet] public bool HasPermission(string permissionToCheck, int nodeId) diff --git a/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs b/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs index e1890326fb..92709916ac 100644 --- a/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs @@ -13,38 +13,42 @@ using Umbraco.Core; namespace Umbraco.Web.WebApi.Filters { - /// - /// Ensures that the request is not cached by the browser - /// - public class DisableBrowserCacheAttribute : ActionFilterAttribute - { - public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) - { - //See: http://stackoverflow.com/questions/17755239/how-to-stop-chrome-from-caching-rest-response-from-webapi + /// + /// Ensures that the request is not cached by the browser + /// + public class DisableBrowserCacheAttribute : ActionFilterAttribute + { + public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) + { + //See: http://stackoverflow.com/questions/17755239/how-to-stop-chrome-from-caching-rest-response-from-webapi - base.OnActionExecuted(actionExecutedContext); + base.OnActionExecuted(actionExecutedContext); + if (actionExecutedContext == null || actionExecutedContext.Response == null || + actionExecutedContext.Response.Headers == null) + { + return; + } + //NOTE: Until we upgraded to WebApi 2, this didn't work correctly and we had to revert to using + // HttpContext.Current responses. I've changed this back to what it should be now since it works + // and now with WebApi2, the HttpContext.Current responses dont! Anyways, all good now. + actionExecutedContext.Response.Headers.CacheControl = new CacheControlHeaderValue() + { + NoCache = true, + NoStore = true, + MaxAge = new TimeSpan(0), + MustRevalidate = true + }; - //NOTE: Until we upgraded to WebApi 2, this didn't work correctly and we had to revert to using - // HttpContext.Current responses. I've changed this back to what it should be now since it works - // and now with WebApi2, the HttpContext.Current responses dont! Anyways, all good now. - actionExecutedContext.Response.Headers.CacheControl = new CacheControlHeaderValue() - { - NoCache = true, - NoStore = true, - MaxAge = new TimeSpan(0), - MustRevalidate = true - }; + actionExecutedContext.Response.Headers.Pragma.Add(new NameValueHeaderValue("no-cache")); + if (actionExecutedContext.Response.Content != null) + { + actionExecutedContext.Response.Content.Headers.Expires = + //Mon, 01 Jan 1990 00:00:00 GMT + new DateTimeOffset(1990, 1, 1, 0, 0, 0, TimeSpan.Zero); + } - actionExecutedContext.Response.Headers.Pragma.Add(new NameValueHeaderValue("no-cache")); - if (actionExecutedContext.Response.Content != null) - { - actionExecutedContext.Response.Content.Headers.Expires = - //Mon, 01 Jan 1990 00:00:00 GMT - new DateTimeOffset(1990, 1, 1, 0, 0, 0, TimeSpan.Zero); - } - - - } - } + + } + } } From d1c865480f9586e777dd8bbeb2d750e27d38a251 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Tue, 29 Mar 2016 23:22:42 +0200 Subject: [PATCH 033/101] Add tooltip for listview layout + cursor style for listview table row. --- .../src/less/components/umb-table.less | 4 +--- .../src/views/components/umb-layout-selector.html | 7 +++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less index e94aa81898..e53fe2ba94 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less @@ -85,7 +85,7 @@ input.umb-table__input { .umb-table-body .umb-table-row { color: fade(@gray, 75%); border-top: 1px solid @grayLight; - + cursor: pointer; font-size: 13px; &:hover { @@ -205,8 +205,6 @@ input.umb-table__input { overflow: hidden; white-space: nowrap; //NOTE Disable/Enable this to keep textstring on one line text-overflow: ellipsis; - - cursor: default; } .umb-table-cell:first-of-type { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-layout-selector.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-layout-selector.html index a7b2e0e556..c7dba13bae 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-layout-selector.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-layout-selector.html @@ -5,10 +5,9 @@
- -
- -
+
+ +
From 74e1078c31baddf6cdff8bdb668c97ae95b36376 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 30 Mar 2016 16:07:45 +0200 Subject: [PATCH 034/101] Merge branch 'U4-8198_file_system' of https://github.com/yannisgu/Umbraco-CMS into yannisgu-U4-8198_file_system Conflicts: src/Umbraco.Web/Umbraco.Web.csproj --- src/Umbraco.Core/Constants-Applications.cs | 8 +- .../lib/umbraco/LegacyUmbClientMgr.js | 3 + .../config/trees.Release.config | 25 +-- src/Umbraco.Web.UI/config/trees.config | 11 +- .../umbraco/config/create/UI.xml | 23 +++ .../umbraco/settings/views/EditView.aspx.cs | 6 +- src/Umbraco.Web/Models/Trees/MenuItem.cs | 20 ++- .../Trees/FileSystemTreeController.cs | 163 +++++++++++++----- .../Trees/PartialViewMacrosTree.cs | 48 ------ .../Trees/PartialViewMacrosTreeController.cs | 24 +++ src/Umbraco.Web/Trees/PartialViewsTree.cs | 87 ---------- .../Trees/PartialViewsTreeController.cs | 49 ++++++ .../Trees/ScriptsTreeController.cs | 48 ++++++ .../Trees/StylesheetsTreeController.cs | 107 ++++++++++++ src/Umbraco.Web/Trees/XsltTreeController.cs | 41 +++++ src/Umbraco.Web/Umbraco.Web.csproj | 16 +- .../umbraco/Trees/FileSystemTree.cs | 134 -------------- .../umbraco/Trees/loadScripts.cs | 92 ---------- .../umbraco/Trees/loadStylesheetProperty.cs | 64 ------- .../umbraco/Trees/loadStylesheets.cs | 74 -------- .../umbraco/Trees/loadXslt.cs | 66 ------- .../umbraco/developer/Xslt/editXslt.aspx.cs | 2 +- .../settings/scripts/editScript.aspx.cs | 2 +- .../stylesheet/editstylesheet.aspx.cs | 2 +- .../property/EditStyleSheetProperty.aspx.cs | 8 +- 25 files changed, 476 insertions(+), 647 deletions(-) delete mode 100644 src/Umbraco.Web/Trees/PartialViewMacrosTree.cs create mode 100644 src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs delete mode 100644 src/Umbraco.Web/Trees/PartialViewsTree.cs create mode 100644 src/Umbraco.Web/Trees/PartialViewsTreeController.cs create mode 100644 src/Umbraco.Web/Trees/ScriptsTreeController.cs create mode 100644 src/Umbraco.Web/Trees/StylesheetsTreeController.cs create mode 100644 src/Umbraco.Web/Trees/XsltTreeController.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadScripts.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadStylesheetProperty.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadStylesheets.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadXslt.cs diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index 1c15083a81..5fb2fd7d8a 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -114,7 +114,13 @@ /// public const string UserTypes = "userTypes"; - //TODO: Fill in the rest! + public const string Scripts = "scripts"; + + public const string PartialViews = "partialViews"; + + public const string PartialViewMacros = "partialViewMacros"; + + //TODO: Fill in the rest! } } diff --git a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js index 06d6c11c71..51d437b9cb 100644 --- a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js +++ b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js @@ -106,6 +106,9 @@ Umbraco.Sys.registerNamespace("Umbraco.Application"); treeService.clearCache(); }); }, + childNodeCreated: function() { + //no-op, just needs to be here for legacy reasons + }, reloadActionNode: function () { angularHelper.safeApply($rootScope, function() { var currentMenuNode = appState.getMenuState("currentNode"); diff --git a/src/Umbraco.Web.UI/config/trees.Release.config b/src/Umbraco.Web.UI/config/trees.Release.config index bdf30ed8f3..91efd6bc8b 100644 --- a/src/Umbraco.Web.UI/config/trees.Release.config +++ b/src/Umbraco.Web.UI/config/trees.Release.config @@ -3,47 +3,38 @@ - - - - - - - + + + + - - - - + + - + - - - + - - \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/trees.config b/src/Umbraco.Web.UI/config/trees.config index 364ecfd4fd..1bad226db8 100644 --- a/src/Umbraco.Web.UI/config/trees.config +++ b/src/Umbraco.Web.UI/config/trees.config @@ -9,10 +9,9 @@ - - - - + + + @@ -21,8 +20,8 @@ - - + + diff --git a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml index f6859c6423..da74f6527b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/create/UI.xml +++ b/src/Umbraco.Web.UI/umbraco/config/create/UI.xml @@ -40,6 +40,7 @@
Xslt
/create/xslt.ascx + @@ -72,6 +73,14 @@ + +
Stylesheet
+ /create/simple.ascx + + + + +
XSLT file
/create/xslt.ascx @@ -229,6 +238,13 @@
+ +
Macro
+ /Create/PartialView.ascx + + + +
Macro
/Create/PartialViewMacro.ascx @@ -237,6 +253,13 @@
+ +
Macro
+ /Create/PartialViewMacro.ascx + + + +
Macro
/Create/PartialView.ascx diff --git a/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx.cs b/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx.cs index 58c78827ec..a91414fd02 100644 --- a/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx.cs +++ b/src/Umbraco.Web.UI/umbraco/settings/views/EditView.aspx.cs @@ -64,9 +64,9 @@ namespace Umbraco.Web.UI.Umbraco.Settings.Views get { if (Request.QueryString["treeType"].IsNullOrWhiteSpace()) - { - return TreeDefinitionCollection.Instance.FindTree().Tree.Alias; - } + { + return Constants.Trees.PartialViews; + } return Request.CleanForXss("treeType"); } } diff --git a/src/Umbraco.Web/Models/Trees/MenuItem.cs b/src/Umbraco.Web/Models/Trees/MenuItem.cs index 8e0f581e78..e1a656857a 100644 --- a/src/Umbraco.Web/Models/Trees/MenuItem.cs +++ b/src/Umbraco.Web/Models/Trees/MenuItem.cs @@ -191,7 +191,25 @@ namespace Umbraco.Web.Models.Trees view => LaunchDialogView( view, ApplicationContext.Current.Services.TextService.Localize("defaultdialogs/confirmdelete") + " '" + (item == null ? "" : item.Name) + "' ?")); - } + } + + internal void ConvertLegacyFileSystemMenuItem(string path, string nodeType, string currentSection) + { + //First try to get a URL/title from the legacy action, + // if that doesn't work, try to get the legacy confirm view + + //in some edge cases, item can be null so we'll just convert those to "-1" and "" for id and name since these edge cases don't need that. + Attempt + .Try(LegacyTreeDataConverter.GetUrlAndTitleFromLegacyAction(Action, + path, + nodeType, + path, currentSection), + action => LaunchDialogUrl(action.Url, action.DialogTitle)) + .OnFailure(() => LegacyTreeDataConverter.GetLegacyConfirmView(Action, currentSection), + view => LaunchDialogView( + view, + ApplicationContext.Current.Services.TextService.Localize("defaultdialogs/confirmdelete") + " '" + path + "' ?")); + } #endregion } diff --git a/src/Umbraco.Web/Trees/FileSystemTreeController.cs b/src/Umbraco.Web/Trees/FileSystemTreeController.cs index f9ebaaf2d6..3c612281f5 100644 --- a/src/Umbraco.Web/Trees/FileSystemTreeController.cs +++ b/src/Umbraco.Web/Trees/FileSystemTreeController.cs @@ -2,35 +2,50 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Http.Formatting; using System.Text; using System.Threading.Tasks; +using ClientDependency.Core; +using Umbraco.Core; using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Services; using Umbraco.Web.Models.Trees; +using Umbraco.Web._Legacy.Actions; +using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { public abstract class FileSystemTreeController : TreeController { protected abstract string FilePath { get; } - protected abstract string FileSearchPattern { get; } + protected abstract IEnumerable FileSearchPattern { get; } + protected abstract string EditFormUrl { get; } + protected abstract bool EnableCreateOnFolder { get; } /// /// Inheritors can override this method to modify the file node that is created. /// /// - protected virtual void OnRenderFileNode(ref TreeNode treeNode) { } + protected virtual void OnRenderFileNode(TreeNode treeNode, FileInfo file) + { + } /// /// Inheritors can override this method to modify the folder node that is created. /// /// - protected virtual void OnRenderFolderNode(ref TreeNode treeNode) { } - - protected override Models.Trees.TreeNodeCollection GetTreeNodes(string id, System.Net.Http.Formatting.FormDataCollection queryStrings) + protected virtual void OnRenderFolderNode(TreeNode treeNode) { + } + + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var nodes = new TreeNodeCollection(); + string orgPath = ""; string path = ""; - if (!string.IsNullOrEmpty(id) && id != "-1") + if (!string.IsNullOrEmpty(id) && id != Constants.System.Root.ToInvariantString()) { orgPath = id; path = IOHelper.MapPath(FilePath + "/" + orgPath); @@ -41,54 +56,124 @@ namespace Umbraco.Web.Trees path = IOHelper.MapPath(FilePath); } - DirectoryInfo dirInfo = new DirectoryInfo(path); - DirectoryInfo[] dirInfos = dirInfo.GetDirectories(); + if (!Directory.Exists(path) && !System.IO.File.Exists(path)) + { + return nodes; + } + + if (System.IO.File.Exists(path)) + { + return GetTreeNodesForFile(path, id, queryStrings); + } + + DirectoryInfo dirInfo = new DirectoryInfo(path); + DirectoryInfo[] dirInfos = new DirectoryInfo(path).GetDirectories(); - var nodes = new TreeNodeCollection(); foreach (DirectoryInfo dir in dirInfos) { - if ((dir.Attributes & FileAttributes.Hidden) == 0) + if ((dir.Attributes.HasFlag(FileAttributes.Hidden)) == false) { - var HasChildren = dir.GetFiles().Length > 0 || dir.GetDirectories().Length > 0; - var node = CreateTreeNode(orgPath + dir.Name, orgPath, queryStrings, dir.Name, "icon-folder", HasChildren); + var hasChildren = dir.GetFiles().Length > 0 || dir.GetDirectories().Length > 0; + var node = CreateTreeNode(orgPath + dir.Name, orgPath, queryStrings, dir.Name, "icon-folder", + hasChildren); - OnRenderFolderNode(ref node); - if(node != null) - nodes.Add(node); + //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. + node.AdditionalData["jsClickCallback"] = "javascript:void(0);"; + OnRenderFolderNode(node); + + nodes.Add(node); } } - //this is a hack to enable file system tree to support multiple file extension look-up - //so the pattern both support *.* *.xml and xml,js,vb for lookups - string[] allowedExtensions = new string[0]; - bool filterByMultipleExtensions = FileSearchPattern.Contains(","); - FileInfo[] fileInfo; + var files = FileSearchPattern + .SelectMany(p => dirInfo.GetFiles("*." + p)) + .Where(f => !f.Attributes.HasFlag(FileAttributes.Hidden)); - if (filterByMultipleExtensions) + foreach (FileInfo file in files) { - fileInfo = dirInfo.GetFiles(); - allowedExtensions = FileSearchPattern.ToLower().Split(','); - } - else - fileInfo = dirInfo.GetFiles(FileSearchPattern); + var nodeId = orgPath + file.Name; - foreach (FileInfo file in fileInfo) - { - if ((file.Attributes & FileAttributes.Hidden) == 0) - { - if (filterByMultipleExtensions && Array.IndexOf(allowedExtensions, file.Extension.ToLower().Trim('.')) < 0) - continue; + var node = CreateTreeNode( + nodeId, + orgPath, queryStrings, + file.Name.StripFileExtension(), + "icon-file", + false, + "/" + queryStrings.GetValue("application") + "/framed/" + + Uri.EscapeDataString(string.Format(EditFormUrl, nodeId))); - var node = CreateTreeNode(orgPath + file.Name, orgPath, queryStrings, file.Name, "icon-file", false); + OnRenderFileNode(node, file); - OnRenderFileNode(ref node); - - if(node != null) - nodes.Add(node); - } + nodes.Add(node); } return nodes; - } + } + + protected virtual TreeNodeCollection GetTreeNodesForFile(string path, string id, FormDataCollection queryStrings) + { + return new TreeNodeCollection(); + } + + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + + var menu = new MenuItemCollection(); + + OnBeforeRenderMenu(menu, id, queryStrings); + + if (id == Constants.System.Root.ToInvariantString()) + { + //Create the normal create action + menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)) + //Since we haven't implemented anything for file systems in angular, this needs to be converted to + //use the legacy format + .ConvertLegacyFileSystemMenuItem("", "init" + TreeAlias, queryStrings.GetValue("application")); + + //refresh action + menu.Items.Add( + Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); + + return menu; + + } + + if (Directory.Exists(IOHelper.MapPath(FilePath + "/" + id))) + { + if (EnableCreateOnFolder) + { + //Create the normal create action + menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)) + //Since we haven't implemented anything for file systems in angular, this needs to be converted to + //use the legacy format + .ConvertLegacyFileSystemMenuItem(id, TreeAlias + "Folder", + queryStrings.GetValue("application")); + } + + //refresh action + menu.Items.Add( + Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); + + } + + //add delete option for all languages + menu.Items.Add(Services.TextService.Localize("actions", ActionDelete.Instance.Alias), true) + .ConvertLegacyFileSystemMenuItem( + id, TreeAlias, queryStrings.GetValue("application")); + + OnAfterRenderMenu(menu, id, queryStrings); + + return menu; + } + + protected virtual void OnBeforeRenderMenu(MenuItemCollection menu, string id, FormDataCollection queryStrings) + { + } + + protected virtual void OnAfterRenderMenu(MenuItemCollection menu, string id, FormDataCollection queryStrings) + { + + } } } diff --git a/src/Umbraco.Web/Trees/PartialViewMacrosTree.cs b/src/Umbraco.Web/Trees/PartialViewMacrosTree.cs deleted file mode 100644 index fdde9beb4c..0000000000 --- a/src/Umbraco.Web/Trees/PartialViewMacrosTree.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Text; -using Umbraco.Core.IO; -using umbraco.cms.presentation.Trees; -using Umbraco.Core; - -namespace Umbraco.Web.Trees -{ - /// - /// Tree for displaying partial view macros in the developer app - /// - [Tree(Constants.Applications.Developer, "partialViewMacros", "Partial View Macro Files", sortOrder: 6)] - public class PartialViewMacrosTree : PartialViewsTree - { - public PartialViewMacrosTree(string application) : base(application) - { - } - - protected override string FilePath - { - get { return SystemDirectories.MvcViews + "/MacroPartials/"; } - } - - public override void RenderJS(ref StringBuilder javascript) - { - javascript.Append( - @" - function openMacroPartialView(id) { - UmbClientMgr.contentFrame('Settings/Views/EditView.aspx?treeType=partialViewMacros&file=' + id); - } - "); - - }/// - /// Ensures that no folders can be added - /// - /// - protected override void OnRenderFolderNode(ref XmlTreeNode xNode) - { - base.OnRenderFolderNode(ref xNode); - - xNode.NodeType = "partialViewMacrosFolder"; - } - - protected override void ChangeNodeAction(XmlTreeNode xNode) - { - xNode.Action = xNode.Action.Replace("openFile", "openMacroPartialView"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs new file mode 100644 index 0000000000..d4213c7996 --- /dev/null +++ b/src/Umbraco.Web/Trees/PartialViewMacrosTreeController.cs @@ -0,0 +1,24 @@ +using System.Text; +using Umbraco.Core.IO; +using umbraco.cms.presentation.Trees; +using Umbraco.Core; + +namespace Umbraco.Web.Trees +{ + /// + /// Tree for displaying partial view macros in the developer app + /// + [Tree(Constants.Applications.Developer, Constants.Trees.PartialViewMacros, "Partial View Macro Files", sortOrder: 6)] + public class PartialViewMacrosTreeController : PartialViewsTreeController + { + protected override string FilePath + { + get { return SystemDirectories.MvcViews + "/MacroPartials/"; } + } + + protected override string EditFormUrl + { + get { return "Settings/Views/EditView.aspx?treeType=partialViewMacros&file={0}"; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/PartialViewsTree.cs b/src/Umbraco.Web/Trees/PartialViewsTree.cs deleted file mode 100644 index ff7edd8fb7..0000000000 --- a/src/Umbraco.Web/Trees/PartialViewsTree.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Web; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; - -using umbraco.cms.businesslogic.template; -using umbraco.cms.presentation.Trees; -using Umbraco.Web._Legacy.Actions; - -namespace Umbraco.Web.Trees -{ - /// - /// Tree for displaying partial views in the settings app - /// - [Tree(Constants.Applications.Settings, "partialViews", "Partial Views", sortOrder: 2)] - public class PartialViewsTree : FileSystemTree - { - public PartialViewsTree(string application) : base(application) { } - - public override void RenderJS(ref StringBuilder javascript) - { - javascript.Append( - @" - function openPartialView(id) { - UmbClientMgr.contentFrame('Settings/Views/EditView.aspx?treeType=partialViews&file=' + id); - } - "); - } - - protected override void CreateRootNode(ref XmlTreeNode rootNode) - { - rootNode.NodeType = TreeAlias; - rootNode.NodeID = "init"; - } - - protected override string FilePath - { - get { return SystemDirectories.MvcViews + "/Partials/"; } - } - - protected override string FileSearchPattern - { - get { return "*.cshtml"; } - } - - /// - /// Ensures that no folders can be added - /// - /// - protected override void OnRenderFolderNode(ref XmlTreeNode xNode) - { - // We should allow folder hierarchy for organization in large sites. - xNode.Action = "javascript:void(0);"; - xNode.NodeType = "partialViewsFolder"; - xNode.Menu = new List(new IAction[] - { - ActionNew.Instance, - ContextMenuSeperator.Instance, - ActionDelete.Instance, - ContextMenuSeperator.Instance, - ActionRefresh.Instance - }); - - } - - protected virtual void ChangeNodeAction(XmlTreeNode xNode) - { - xNode.Action = xNode.Action.Replace("openFile", "openPartialView"); - } - - protected override void OnRenderFileNode(ref XmlTreeNode xNode) - { - ChangeNodeAction(xNode); - xNode.Icon = "icon-article"; - xNode.OpenIcon = "icon-article"; - - xNode.Text = xNode.Text.StripFileExtension(); - } - - - } -} diff --git a/src/Umbraco.Web/Trees/PartialViewsTreeController.cs b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs new file mode 100644 index 0000000000..3255c87e40 --- /dev/null +++ b/src/Umbraco.Web/Trees/PartialViewsTreeController.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Web; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; + +using umbraco.cms.businesslogic.template; +using umbraco.cms.presentation.Trees; +using Umbraco.Web.Models.Trees; +using Umbraco.Web._Legacy.Actions; + +namespace Umbraco.Web.Trees +{ + /// + /// Tree for displaying partial views in the settings app + /// + [Tree(Constants.Applications.Settings, Constants.Trees.PartialViews, "Partial Views", sortOrder: 2)] + public class PartialViewsTreeController : FileSystemTreeController + { + protected override string FilePath + { + get { return SystemDirectories.MvcViews + "/Partials/"; } + } + + protected override IEnumerable FileSearchPattern + { + get { return new[] {"cshtml"}; } + } + + protected override string EditFormUrl + { + get { return "Settings/Views/EditView.aspx?treeType=partialViews&file={0}"; } + } + + protected override bool EnableCreateOnFolder + { + get { return true; } + } + + protected override void OnRenderFileNode(TreeNode treeNode, FileInfo file) + { + treeNode.Icon = "icon-article"; + } + } +} diff --git a/src/Umbraco.Web/Trees/ScriptsTreeController.cs b/src/Umbraco.Web/Trees/ScriptsTreeController.cs new file mode 100644 index 0000000000..7409230d24 --- /dev/null +++ b/src/Umbraco.Web/Trees/ScriptsTreeController.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http.Formatting; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Web.Models.Trees; + +namespace Umbraco.Web.Trees +{ + [Tree(Constants.Applications.Settings, Constants.Trees.Scripts, "Scripts", "icon-folder", "icon-folder", sortOrder: 2)] + public class ScriptsTreeController : FileSystemTreeController + { + protected override string FilePath + { + get { return SystemDirectories.Scripts + "/"; } + } + + protected override IEnumerable FileSearchPattern + { + get { return UmbracoConfig.For.UmbracoSettings().Content.ScriptFileTypes; } + } + + protected override string EditFormUrl + { + get { return "settings/scripts/editScript.aspx?file={0}"; } + } + + protected override bool EnableCreateOnFolder + { + get { return true; } + } + + protected override void OnRenderFolderNode(TreeNode treeNode) + { + } + + protected override void OnRenderFileNode(TreeNode treeNode, FileInfo file) + { + treeNode.Icon = + file.Name.EndsWith(".js", StringComparison.OrdinalIgnoreCase) ? + "icon-script" : + "icon-code"; + + } + } +} diff --git a/src/Umbraco.Web/Trees/StylesheetsTreeController.cs b/src/Umbraco.Web/Trees/StylesheetsTreeController.cs new file mode 100644 index 0000000000..786bfe45bc --- /dev/null +++ b/src/Umbraco.Web/Trees/StylesheetsTreeController.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http.Formatting; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Services; +using Umbraco.Web.Models.Trees; +using Umbraco.Web._Legacy.Actions; +using File = System.IO.File; + +namespace Umbraco.Web.Trees +{ + [Tree(Constants.Applications.Settings, Constants.Trees.Stylesheets, "Stylesheets", "icon-folder", "icon-folder", sortOrder: 3)] + public class StylesheetsTreeController : FileSystemTreeController + { + + protected override string FilePath + { + get { return SystemDirectories.Css + "/"; } + } + + protected override IEnumerable FileSearchPattern + { + get { return new [] {"css"}; } + } + + protected override string EditFormUrl + { + get { return "settings/stylesheet/editStylesheet.aspx?id={0}"; } + } + + protected override bool EnableCreateOnFolder + { + get { return false; } + } + + + protected override void OnBeforeRenderMenu(MenuItemCollection menu, string id, FormDataCollection queryStrings) + { + if (File.Exists((IOHelper.MapPath(FilePath + "/" + id)))) + { + menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)) + //Since we haven't implemented anything for file systems in angular, this needs to be converted to + //use the legacy format + .ConvertLegacyFileSystemMenuItem(id, "stylesheet", queryStrings.GetValue("application")); + } + } + + protected override void OnAfterRenderMenu(MenuItemCollection menu, string id, FormDataCollection queryStrings) + { + if (File.Exists((IOHelper.MapPath(FilePath + "/" + id)))) + { + menu.Items.Add(Services.TextService.Localize("actions", ActionSort.Instance.Alias), true) + .ConvertLegacyFileSystemMenuItem(id, "stylesheet", queryStrings.GetValue("application")); + + //refresh action + menu.Items.Add( + Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); + } + } + + protected override void OnRenderFileNode(TreeNode treeNode, FileInfo file) + { + treeNode.Icon = "icon-brackets"; + treeNode.NodeType = "stylesheet"; + var styleSheet = Services.FileService.GetStylesheetByName(treeNode.Id.ToString().EnsureEndsWith(".css")); + if (styleSheet != null) + { + treeNode.HasChildren = styleSheet.Properties.Any(); + } + + } + + protected override TreeNodeCollection GetTreeNodesForFile(string path, string id, FormDataCollection queryStrings) + { + var nodes = new TreeNodeCollection(); + + var sheet = Services.FileService.GetStylesheetByName(id.EnsureEndsWith(".css")); + + foreach (var prop in sheet.Properties) + { + var node = CreateTreeNode( + id + "_" + prop.Name, + id, queryStrings, + prop.Name, + "icon-brackets", + false, + "/" + queryStrings.GetValue("application") + "/framed/" + + Uri.EscapeDataString("settings/stylesheet/property/editStylesheetProperty.aspx?id=" + + sheet.Path + "&prop=" + prop.Name)); + node.NodeType = "stylesheetProperty"; + nodes.Add(node); + + + + } + + return nodes; + } + } +} diff --git a/src/Umbraco.Web/Trees/XsltTreeController.cs b/src/Umbraco.Web/Trees/XsltTreeController.cs new file mode 100644 index 0000000000..0c0bb8f637 --- /dev/null +++ b/src/Umbraco.Web/Trees/XsltTreeController.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Web.Models.Trees; + +namespace Umbraco.Web.Trees +{ + [Tree(Constants.Applications.Settings, Constants.Trees.Xslt, "XSLT Files", "icon-folder", "icon-folder", sortOrder: 2)] + public class XsltTreeController : FileSystemTreeController + { + protected override string FilePath + { + get { return SystemDirectories.Xslt + "/"; } + } + + protected override IEnumerable FileSearchPattern + { + get { return new [] {"xslt"}; } + } + + protected override string EditFormUrl + { + get { return "developer/xslt/editXslt.aspx?file={0}"; } + } + + protected override bool EnableCreateOnFolder + { + get { return false; } + } + + protected override void OnRenderFileNode(TreeNode treeNode, FileInfo file) + { + treeNode.Icon = "icon-code"; + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 2f1dcef7ce..965c1f2531 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -327,7 +327,10 @@ + + + @@ -1047,8 +1050,8 @@ - - + + @@ -1546,16 +1549,11 @@ xml.aspx - - - - - @@ -1759,7 +1757,9 @@ ASPXCodeBehind - + + ASPXCodeBehind + diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs deleted file mode 100644 index aed6009d86..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/FileSystemTree.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Data; -using System.Configuration; -using System.Web; -using System.Web.Security; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.WebControls.WebParts; -using System.Web.UI.HtmlControls; -using System.IO; -using Umbraco.Core.IO; - -namespace umbraco.cms.presentation.Trees -{ - public abstract class FileSystemTree : BaseTree - { - - public FileSystemTree(string application) : base(application) { } - - public override abstract void RenderJS(ref System.Text.StringBuilder Javascript); - protected override abstract void CreateRootNode(ref XmlTreeNode rootNode); - - protected abstract string FilePath { get; } - protected abstract string FileSearchPattern { get; } - - /// - /// Inheritors can override this method to modify the file node that is created. - /// - /// - protected virtual void OnRenderFileNode(ref XmlTreeNode xNode) { } - - /// - /// Inheritors can override this method to modify the folder node that is created. - /// - /// - protected virtual void OnRenderFolderNode(ref XmlTreeNode xNode) { } - - public override void Render(ref XmlTree tree) - { - string orgPath = ""; - string path = ""; - if (!string.IsNullOrEmpty(this.NodeKey)) - { - orgPath = this.NodeKey; - path = IOHelper.MapPath(FilePath + orgPath); - orgPath += "/"; - } - else - { - path = IOHelper.MapPath(FilePath); - } - - DirectoryInfo dirInfo = new DirectoryInfo(path); - - - DirectoryInfo[] dirInfos = dirInfo.Exists ? dirInfo.GetDirectories() : new DirectoryInfo[] { }; - - var args = new TreeEventArgs(tree); - OnBeforeTreeRender(dirInfo, args); - - foreach (DirectoryInfo dir in dirInfos) - { - if ((dir.Attributes & FileAttributes.Hidden) == 0) - { - XmlTreeNode xDirNode = XmlTreeNode.Create(this); - xDirNode.NodeID = orgPath + dir.Name; - xDirNode.Menu.Clear(); - xDirNode.Text = dir.Name; - xDirNode.Action = string.Empty; - xDirNode.Source = GetTreeServiceUrl(orgPath + dir.Name); - xDirNode.Icon = FolderIcon; - xDirNode.OpenIcon = FolderIconOpen; - xDirNode.HasChildren = dir.GetFiles().Length > 0 || dir.GetDirectories().Length > 0; - - OnRenderFolderNode(ref xDirNode); - OnBeforeNodeRender(ref tree, ref xDirNode, EventArgs.Empty); - if (xDirNode != null) - { - tree.Add(xDirNode); - OnAfterNodeRender(ref tree, ref xDirNode, EventArgs.Empty); - } - - - } - } - - //this is a hack to enable file system tree to support multiple file extension look-up - //so the pattern both support *.* *.xml and xml,js,vb for lookups - string[] allowedExtensions = new string[0]; - bool filterByMultipleExtensions = FileSearchPattern.Contains(","); - FileInfo[] fileInfo; - - if (filterByMultipleExtensions) - { - fileInfo = dirInfo.Exists ? dirInfo.GetFiles() : new FileInfo[] {}; - allowedExtensions = FileSearchPattern.ToLower().Split(','); - } - else - { - fileInfo = dirInfo.Exists ? dirInfo.GetFiles(FileSearchPattern) : new FileInfo[] { }; - } - - foreach (FileInfo file in fileInfo) - { - if ((file.Attributes & FileAttributes.Hidden) == 0) - { - if (filterByMultipleExtensions && Array.IndexOf(allowedExtensions, file.Extension.ToLower().Trim('.')) < 0) - continue; - - XmlTreeNode xFileNode = XmlTreeNode.Create(this); - xFileNode.NodeID = orgPath + file.Name; - xFileNode.Text = file.Name; - if (!((orgPath == ""))) - xFileNode.Action = "javascript:openFile('" + orgPath + file.Name + "');"; - else - xFileNode.Action = "javascript:openFile('" + file.Name + "');"; - xFileNode.Icon = "doc.gif"; - xFileNode.OpenIcon = "doc.gif"; - - OnRenderFileNode(ref xFileNode); - OnBeforeNodeRender(ref tree, ref xFileNode, EventArgs.Empty); - if (xFileNode != null) - { - tree.Add(xFileNode); - OnAfterNodeRender(ref tree, ref xFileNode, EventArgs.Empty); - } - - - } - } - OnAfterTreeRender(dirInfo, args); - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadScripts.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadScripts.cs deleted file mode 100644 index 296d7b1db4..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadScripts.cs +++ /dev/null @@ -1,92 +0,0 @@ - -using Umbraco.Core.Services; -using System.Collections.Generic; -using System.Text; -using Umbraco.Core; - -using Umbraco.Core.Configuration; -using umbraco.cms.presentation.Trees; -using Umbraco.Core.IO; -using Umbraco.Web.Trees; -using Umbraco.Web._Legacy.Actions; - - -namespace umbraco -{ - [Tree(Constants.Applications.Settings, "scripts", "Scripts", "icon-folder", "icon-folder", sortOrder: 2)] - public class loadScripts : FileSystemTree - { - public loadScripts(string application) : base(application) { } - protected override void CreateRootNode(ref XmlTreeNode rootNode) - { - rootNode.NodeType = "init" + TreeAlias; - rootNode.NodeID = "init"; - rootNode.Text = Services.TextService.Localize("treeHeaders/scripts"); - } - - public override void RenderJS(ref StringBuilder Javascript) - { - Javascript.Append( - @" - function openScriptEditor(id) { - UmbClientMgr.contentFrame('settings/scripts/editScript.aspx?file=' + id); - } - function openScriptFolder(id) { - return false; - } - "); - } - - protected override string FilePath - { - get - { - return SystemDirectories.Scripts + "/"; - } - } - - protected override string FileSearchPattern - { - - get { return string.Join(",", UmbracoConfig.For.UmbracoSettings().Content.ScriptFileTypes); } - } - - protected override void OnRenderFolderNode(ref XmlTreeNode xNode) - { - - xNode.Menu = new List(new IAction[] - { - ActionNew.Instance, - ContextMenuSeperator.Instance, - ActionDelete.Instance, - ContextMenuSeperator.Instance, - ActionRefresh.Instance - }); - xNode.Action = "javascript:void(0)"; - xNode.NodeType = "scriptsFolder"; - xNode.Action = "javascript:void(0);"; - } - - protected override void OnRenderFileNode(ref XmlTreeNode xNode) - { - xNode.Action = xNode.Action.Replace("openFile", "openScriptEditor"); - - // add special icons for javascript files - if (xNode.Text.Contains(".js")) - { - xNode.Icon = "icon-script"; - xNode.OpenIcon = "icon-script"; - } - else - { - xNode.Icon = "icon-code"; - xNode.OpenIcon = "icon-code"; - } - - xNode.Text = xNode.Text.StripFileExtension(); - } - - - } - -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadStylesheetProperty.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadStylesheetProperty.cs deleted file mode 100644 index 372b490e9a..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadStylesheetProperty.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Globalization; -using System.Text; -using System.Web; - -using umbraco.cms.businesslogic.web; -using umbraco.cms.presentation.Trees; -using Umbraco.Core; -using Umbraco.Web.Trees; - - -namespace umbraco -{ - [Tree(Constants.Applications.Settings, "stylesheetProperty", "Stylesheet Property", "", "", initialize: false)] - public class loadStylesheetProperty : BaseTree - { - public loadStylesheetProperty(string application) : base(application) { } - - protected override void CreateRootNode(ref XmlTreeNode rootNode) - { - rootNode.NodeType = "init" + TreeAlias; - rootNode.NodeID = "init"; - } - - public override void RenderJS(ref StringBuilder Javascript) - { - Javascript.Append( - @" - function openStylesheetProperty(name, prop) { - UmbClientMgr.contentFrame('settings/stylesheet/property/editStylesheetProperty.aspx?id=' + name + '&prop=' + prop); - } - "); - } - - public override void Render(ref XmlTree tree) - { - var sheet = Services.FileService.GetStylesheetByName(NodeKey.EnsureEndsWith(".css")); - - foreach (var prop in sheet.Properties) - { - var sheetId = sheet.Path.TrimEnd(".css"); - var xNode = XmlTreeNode.Create(this); - xNode.NodeID = sheetId + "_" + prop.Name; - xNode.Text = prop.Name; - xNode.Action = "javascript:openStylesheetProperty('" + - //Needs to be escaped for JS - HttpUtility.UrlEncode(sheet.Path) + - "','" + prop.Name + "');"; - xNode.Icon = "icon-brackets"; - xNode.OpenIcon = "icon-brackets"; - - OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty); - if (xNode != null) - { - tree.Add(xNode); - OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty); - } - - } - } - - } - -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadStylesheets.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadStylesheets.cs deleted file mode 100644 index 649212315e..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadStylesheets.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Umbraco.Core.Services; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web; -using umbraco.cms.presentation.Trees; -using Umbraco.Core; -using Umbraco.Web.Trees; -using Umbraco.Web._Legacy.Actions; - - -namespace umbraco -{ - [Tree(Constants.Applications.Settings, Constants.Trees.Stylesheets, "Stylesheets")] - public class loadStylesheets : BaseTree - { - public loadStylesheets(string application) : base(application) { } - - protected override void CreateRootNode(ref XmlTreeNode rootNode) - { - rootNode.NodeType = "init" + TreeAlias; - rootNode.NodeID = "init"; - rootNode.Text = Services.TextService.Localize("treeHeaders/stylesheets"); - } - - public override void RenderJS(ref StringBuilder Javascript) - { - Javascript.Append( - @" - function openStylesheet(name) { - UmbClientMgr.contentFrame('settings/stylesheet/editStylesheet.aspx?id=' + name); - } - "); - } - - protected override void CreateAllowedActions(ref List actions) - { - actions.Clear(); - actions.AddRange(new IAction[] { ActionNew.Instance, ActionDelete.Instance, - ActionSort.Instance, ContextMenuSeperator.Instance, ActionRefresh.Instance }); - } - - public override void Render(ref XmlTree tree) - { - foreach (var sheet in Services.FileService.GetStylesheets()) - { - var nodeId = sheet.Path.TrimEnd(".css"); - var xNode = XmlTreeNode.Create(this); - xNode.NodeID = nodeId; - xNode.Text = nodeId; - xNode.Action = "javascript:openStylesheet('" + - //Needs to be escaped for JS - HttpUtility.UrlEncode(sheet.Path) + "');"; - var styleSheetPropertyTree = new loadStylesheetProperty(this.app); - xNode.Source = styleSheetPropertyTree.GetTreeServiceUrl(nodeId); - xNode.HasChildren = sheet.Properties.Any(); - xNode.Icon = " icon-brackets"; - xNode.OpenIcon = "icon-brackets"; - xNode.NodeType = "stylesheet"; //this shouldn't be like this, it should be this.TreeAlias but the ui.config file points to this name. - - OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty); - if (xNode != null) - { - tree.Add(xNode); - OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty); - } - - } - } - - } - -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadXslt.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadXslt.cs deleted file mode 100644 index 46fbb8c2cd..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadXslt.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Collections.Generic; -using System.Text; -using umbraco.cms.presentation.Trees; -using Umbraco.Core.IO; -using Umbraco.Core; -using Umbraco.Web.Trees; -using Umbraco.Web._Legacy.Actions; - -namespace umbraco -{ - /// - /// Handles loading of the xslt files into the application tree - /// - [Tree(Constants.Applications.Developer, "xslt", "XSLT Files", sortOrder: 5)] - public class loadXslt : FileSystemTree - { - - public loadXslt(string application) : base(application) { } - - protected override void CreateRootNode(ref XmlTreeNode rootNode) - { - rootNode.NodeType = "init" + TreeAlias; - rootNode.NodeID = "init"; - } - - /// - /// Renders the Javascript - /// - /// The javascript. - public override void RenderJS(ref StringBuilder Javascript) - { - Javascript.Append( - @" -function openXslt(id) { - UmbClientMgr.contentFrame('developer/xslt/editXslt.aspx?file=' + id); -} -"); - } - - protected override string FilePath - { - get { return SystemDirectories.Xslt + "/"; } - } - - protected override string FileSearchPattern - { - get { return "*.xslt"; } - } - - protected override void OnRenderFileNode(ref XmlTreeNode xNode) - { - xNode.Action = xNode.Action.Replace("openFile", "openXslt"); - xNode.Icon = "icon-code"; - xNode.OpenIcon = "icon-code"; - - xNode.Text = xNode.Text.StripFileExtension(); - } - - protected override void OnRenderFolderNode(ref XmlTreeNode xNode) - { - xNode.Menu = new List(new IAction[] { ActionDelete.Instance, ContextMenuSeperator.Instance, ActionRefresh.Instance }); - xNode.NodeType = "xsltFolder"; - } - } - -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Xslt/editXslt.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Xslt/editXslt.aspx.cs index 32aee74725..f1ad0e705d 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Xslt/editXslt.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Xslt/editXslt.aspx.cs @@ -30,7 +30,7 @@ namespace umbraco.cms.presentation.developer string file = Request.QueryString["file"]; string path = BaseTree.GetTreePathFromFilePath(file); ClientTools - .SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) + .SetActiveTreeType(Constants.Trees.Xslt) .SyncTree(path, false); } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx.cs index b6db5cdf8c..cd6d7159aa 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/scripts/editScript.aspx.cs @@ -65,7 +65,7 @@ namespace umbraco.cms.presentation.settings.scripts if (IsPostBack == false) { ClientTools - .SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) + .SetActiveTreeType(Constants.Trees.Scripts) .SyncTree(ScriptTreeSyncPath, false); } } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/editstylesheet.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/editstylesheet.aspx.cs index 5d24cef82a..9b2a5590ce 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/editstylesheet.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/editstylesheet.aspx.cs @@ -60,7 +60,7 @@ namespace umbraco.cms.presentation.settings.stylesheet lttPath.Text = "" + stylesheet.VirtualPath + ""; editorSource.Text = stylesheet.Content; - TreeSyncPath = BaseTree.GetTreePathFromFilePath(filename).TrimEnd(".css"); + TreeSyncPath = BaseTree.GetTreePathFromFilePath(filename); // name derives from path, without the .css extension, clean for xss NameTxt.Text = stylesheet.Path.TrimEnd(".css").CleanForXss('\\', '/'); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs index eb284c5ea5..780f63e469 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/stylesheet/property/EditStyleSheetProperty.aspx.cs @@ -41,7 +41,7 @@ namespace umbraco.cms.presentation.settings.stylesheet protected override void OnLoad(EventArgs e) { base.OnLoad(e); - + _sheet = Services.FileService.GetStylesheetByName(Request.QueryString["id"]); if (_sheet == null) throw new InvalidOperationException("No stylesheet found with name: " + Request.QueryString["id"]); @@ -70,9 +70,9 @@ namespace umbraco.cms.presentation.settings.stylesheet prStyles.Attributes["style"] = _stylesheetproperty.Value; - var nodePath = string.Format("-1,init,{0},{0}_{1}", _sheet.Path - //needs a double escape to work with JS - .Replace("\\", "\\\\").TrimEnd(".css"), _stylesheetproperty.Name); + var path = _sheet.Path.Replace('\\', '/'); + var nodePath = string.Format(BaseTree.GetTreePathFromFilePath(path) + + ",{0}_{1}", path, _stylesheetproperty.Name); ClientTools .SetActiveTreeType(Constants.Trees.Stylesheets) From 61fe0a4a93ed55518804a83d2387cfe9607f84c0 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 30 Mar 2016 17:56:34 +0200 Subject: [PATCH 035/101] U4-2954 - refresh domain cache when emptying recycle bin --- src/Umbraco.Web/Cache/DomainCacheRefresher.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Umbraco.Web/Cache/DomainCacheRefresher.cs b/src/Umbraco.Web/Cache/DomainCacheRefresher.cs index 955d87ff59..d19d4f6ea0 100644 --- a/src/Umbraco.Web/Cache/DomainCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DomainCacheRefresher.cs @@ -28,6 +28,12 @@ namespace Umbraco.Web.Cache get { return "Domain cache refresher"; } } + public override void RefreshAll() + { + ClearCache(); + base.RefreshAll(); + } + public override void Refresh(int id) { ClearCache(); From 5e7db1047be50f395dc9cddfd310df1bf22030d8 Mon Sep 17 00:00:00 2001 From: Chriztian Steinmeier Date: Wed, 30 Mar 2016 21:09:14 +0200 Subject: [PATCH 036/101] Add the missing 't' :zap: --- .../src/views/dashboard/settings/settingsdashboardintro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html index 16bb80c0d1..32e7c01920 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html @@ -1,6 +1,6 @@

Start here

This section contains the building blocks for your Umbraco site

-

Follow the below links to find out more about working with the items in the Setings section:

+

Follow the below links to find out more about working with the items in the Settings section:

Find out more:

From e325e06efc9be7e65a12d829a389bcffe9eed1dd Mon Sep 17 00:00:00 2001 From: Chriztian Steinmeier Date: Wed, 30 Mar 2016 21:10:41 +0200 Subject: [PATCH 037/101] Update link to Our on Backoffice sections --- .../src/views/dashboard/settings/settingsdashboardintro.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html index 32e7c01920..f573ace7d4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html @@ -5,9 +5,9 @@

Find out more:

    -
  • Read more about working with the Items in Settings in the Community Wiki
  • Download the Editors Manual for details on working with the Umbraco UI
  • Ask a question in the Community Forum
  • +
  • Read more about working with the Items in Settings in the Documentation section of Our Umbraco
  • Watch our tutorial videos (some are free, some require a subscription)
  • Find out about our productivity boosting tools and commercial support
  • Find out about real-life training and certification opportunities
  • From 5f0fe69c82ebb07e38db20a5646b018ec9b61a17 Mon Sep 17 00:00:00 2001 From: Chriztian Steinmeier Date: Wed, 30 Mar 2016 21:11:29 +0200 Subject: [PATCH 038/101] Update links to https --- .../src/views/dashboard/settings/settingsdashboardintro.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html index f573ace7d4..fa9849022c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/settings/settingsdashboardintro.html @@ -5,9 +5,9 @@

    Find out more:

      -
    • Download the Editors Manual for details on working with the Umbraco UI
    • -
    • Ask a question in the Community Forum
    • Read more about working with the Items in Settings in the Documentation section of Our Umbraco
    • +
    • Download the Editors Manual for details on working with the Umbraco UI
    • +
    • Ask a question in the Community Forum
    • Watch our tutorial videos (some are free, some require a subscription)
    • Find out about our productivity boosting tools and commercial support
    • Find out about real-life training and certification opportunities
    • From 9e0ed94d77947bda6af9f1d7357f7dbace6705f7 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 31 Mar 2016 12:56:24 +0200 Subject: [PATCH 039/101] Fix typo in lang xml files --- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 +- .../umbraco/config/lang/en_us.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/ja.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/ru.xml | 58 +++++++++---------- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 0842755da3..3fdaf8110b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -271,7 +271,7 @@ External login providers Exception Details - Stacktrace + Stacktrace Link your Un-Link your diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index ce25e990c8..dca808c4c8 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -271,7 +271,7 @@ External login providers Exception Details - Stacktrace + Stacktrace Link your Un-Link your diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml index a59a020455..44d2433664 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml @@ -267,7 +267,7 @@ 外部ログイン プロバイダー 例外の詳細 - スタックトレース + スタックトレース 次をリンク: 次をリンク解除: diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index fec3e5f897..a8c69ea0f4 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -189,10 +189,10 @@ Унаследован от Добавить свойство Обязательная метка - + Представление в формате списка Устанавливает представление документа данного типа в виде сортируемого списка дочерних документов с функцией поиска, в отличие от обычного представления дочерних документов в виде дерева - + Допустимые шаблоны Выберите перечень допустимых шаблонов для сопоставления документам данного типа Разрешить в качестве корневого @@ -206,31 +206,31 @@ Унаследовать вкладки и свойства из уже существующего типа документов. Вкладки будут либо добавлены в создаваемый тип, либо в случае совпадения названий вкладок будут добавлены наследуемые свойства. Этот тип документов уже участвует в композиции другого типа, поэтому сам не может быть композицией. В настоящее время нет типов документов, допустимых для построения композиции. - + Доступные редакторы Переиспользовать Установки редактора - + Конфигурирование - + ДА, удалить - + была перемещена внутрь Выбрать папку для перемещения в структуре дерева - + Все типы документов Все документы Все медиа-элементы - + , использующие этот тип документов, будут безвозвратно удалены, пожалуйста, подтвердите это действие. , использующие этот тип медиа, будут безвозвратно удалены, пожалуйста, подтвердите это действие. , использующие этот тип участников, будут безвозвратно удалены, пожалуйста, подтвердите это действие. - + и все документы, использующие данный тип и все медиа-элементы, использующие данный тип и все участники, использующие данный тип - + , использующие этот редактор, будут обновлены с применением этих установок Участник может изменить @@ -302,18 +302,18 @@ Выберите элемент Просмотр элемента кэша Создать папку... - + Связать с оригиналом Самое дружелюбное сообщество - + Ссылка на страницу - + Открывает документ по ссылке в новом окне или вкладке браузера Открывает документ по ссылке в полноэкранном режиме Открывает документ по ссылке в родительском фрейме - + Ссылка на медиа-файл - + Выбрать медиа Выбрать значок Выбрать элемент @@ -322,23 +322,23 @@ Выбрать содержимое Выбрать участника Выбрать группу участников - + Это макрос без параметров - + Провайдеры аутентификации Подробное сообщение об ошибке - Трассировка стека - + Трассировка стека + Связать Разорвать связь - + учетную запись - + Выбрать редактор %0%'
      Добавить другие языки можно, воспользовавшись пунктом 'Языки' в меню слева + Ниже Вы можете указать различные переводы данной статьи словаря '%0%'
      Добавить другие языки можно, воспользовавшись пунктом 'Языки' в меню слева ]]>
      Название языка (культуры) @@ -397,7 +397,7 @@ Ошибка загрузки внешнего типа (сборка: %0%, тип: '%1%') Ошибка загрузки макроса (файл: %0%) "Ошибка разбора кода XSLT в файле: %0% - "Ошибка чтения XSLT-файла: %0% + "Ошибка чтения XSLT-файла: %0% Ошибка в конфигурации типа данных, используемого для свойства, проверьте тип данных Укажите заголовок Выберите тип @@ -876,7 +876,7 @@ ]]> + ]]> @@ -959,14 +959,14 @@ Добавить шаблон Добавить дочерний узел Добавить дочерний - + Изменить тип данных - + Навигация по разделам - + Ярлыки показать ярлыки - + В формате списка Разрешить в качестве корневого @@ -1108,7 +1108,7 @@ http://%3%. Удачи! - + Генератор уведомлений Umbraco. ]]> [%0%] Задание по переводу %1% From b22fda1e80ae0e2ae3f2f990a27db0f9d469c877 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 31 Mar 2016 14:28:41 +0200 Subject: [PATCH 040/101] fixes some ysods --- src/Umbraco.Core/Configuration/GlobalSettings.cs | 3 ++- .../umbraco/developer/Packages/installedPackage.aspx | 7 ++++--- .../umbraco/developer/Packages/installer.aspx | 1 + .../developer/RelationTypes/EditRelationType.aspx | 2 +- src/Umbraco.Web.UI/web.Template.Debug.config | 2 +- src/Umbraco.Web.UI/web.Template.config | 8 ++++---- .../UI/Controls/InsertMacroSplitButton.cs | 3 ++- .../umbraco/settings/editTemplate.aspx.cs | 12 ++++++++++-- 8 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index 525bff2999..b718edbf39 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -26,7 +26,8 @@ namespace Umbraco.Core.Configuration /// /// The GlobalSettings Class contains general settings information for the entire Umbraco instance based on information from web.config appsettings /// - internal class GlobalSettings + [Obsolete("TODO: Need to move this configuration class into the proper configuration accesors for v8!")] + public class GlobalSettings { #region Private static fields diff --git a/src/Umbraco.Web.UI/umbraco/developer/Packages/installedPackage.aspx b/src/Umbraco.Web.UI/umbraco/developer/Packages/installedPackage.aspx index 5939e1abbd..22db3e58cf 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/Packages/installedPackage.aspx +++ b/src/Umbraco.Web.UI/umbraco/developer/Packages/installedPackage.aspx @@ -1,11 +1,12 @@ <%@ Page Language="C#" MasterPageFile="../../masterpages/umbracoPage.Master" AutoEventWireup="true" CodeBehind="installedPackage.aspx.cs" Inherits="umbraco.presentation.developer.packages.installedPackage" %> +<%@ Import Namespace="Umbraco.Core.Configuration" %> <%@ Register TagPrefix="cc2" Namespace="umbraco.uicontrols" Assembly="controls" %> @@ -159,7 +160,7 @@ //This is all a bit zany with double encoding because we have a URL in a hash (#) url part // but it works and maintains query strings - var umbPath = "<%=umbraco.GlobalSettings.Path%>"; + var umbPath = "<%=GlobalSettings.Path%>"; setTimeout(function () { var mainWindow = UmbClientMgr.mainWindow(); diff --git a/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx b/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx index 05f2de9666..f452d7d0e4 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx +++ b/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx @@ -2,6 +2,7 @@ AutoEventWireup="True" Inherits="umbraco.presentation.developer.packages.Installer" Trace="false" ValidateRequest="false" %> <%@ Import Namespace="umbraco" %> +<%@ Import Namespace="Umbraco.Core.Configuration" %> <%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %> diff --git a/src/Umbraco.Web.UI/umbraco/developer/RelationTypes/EditRelationType.aspx b/src/Umbraco.Web.UI/umbraco/developer/RelationTypes/EditRelationType.aspx index e0559a3807..d8f0954331 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/RelationTypes/EditRelationType.aspx +++ b/src/Umbraco.Web.UI/umbraco/developer/RelationTypes/EditRelationType.aspx @@ -1,4 +1,4 @@ -<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="EditRelationType.aspx.cs" Inherits="umbraco.cms.presentation.developer.RelationTypes.EditRelationType" MasterPageFile="../../masterpages/umbracoPage.Master" %> +<%@ Page Language="C#" AutoEventWireup="true" Inherits="umbraco.cms.presentation.developer.RelationTypes.EditRelationType" MasterPageFile="../../masterpages/umbracoPage.Master" %> <%@ Register TagPrefix="umb" Namespace="umbraco.uicontrols" Assembly="controls" %> diff --git a/src/Umbraco.Web.UI/web.Template.Debug.config b/src/Umbraco.Web.UI/web.Template.Debug.config index 93ed17e14d..6d91008f79 100644 --- a/src/Umbraco.Web.UI/web.Template.Debug.config +++ b/src/Umbraco.Web.UI/web.Template.Debug.config @@ -371,7 +371,7 @@ xdt:Locator="Condition(_defaultNamespace:assemblyIdentity[@name='HtmlAgilityPack']])" /> - + - - - - + + + + diff --git a/src/Umbraco.Web/UI/Controls/InsertMacroSplitButton.cs b/src/Umbraco.Web/UI/Controls/InsertMacroSplitButton.cs index 5c6db74513..4a97eac6d1 100644 --- a/src/Umbraco.Web/UI/Controls/InsertMacroSplitButton.cs +++ b/src/Umbraco.Web/UI/Controls/InsertMacroSplitButton.cs @@ -71,7 +71,8 @@ namespace Umbraco.Web.UI.Controls var divMacroItemContainer = new TagBuilder("div"); divMacroItemContainer.Attributes.Add("style", "width: 285px;display:none;"); divMacroItemContainer.Attributes.Add("class", "sbMenu"); - var macros = ApplicationContext.DatabaseContext.Database.Fetch("select id, macroAlias, macroName from cmsMacro order by macroName"); + + var macros = Services.MacroService.GetAll().OrderBy(x => x.Name); foreach (var macro in macros) { var divMacro = new TagBuilder("div"); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/editTemplate.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/editTemplate.aspx.cs index 77883c7312..9e0ed6d4bc 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/settings/editTemplate.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/settings/editTemplate.aspx.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Web.UI; using System.Web.UI.WebControls; using Umbraco.Core; @@ -194,8 +195,15 @@ namespace umbraco.cms.presentation.settings private void LoadMacros() { - var macroRenderings = - DatabaseContext.Database.Fetch("select id, macroAlias, macroName from cmsMacro order by macroName"); + ; + var macroRenderings = + Services.MacroService.GetAll() + .Select(x => new TempMacroClass() + { + id = x.Id, + macroAlias = x.Alias, + macroName = x.Name + }); rpt_macros.DataSource = macroRenderings; rpt_macros.DataBind(); From b6f88895dc555e7d7292d4e65bd8ba4392cbb5ef Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 31 Mar 2016 14:35:49 +0200 Subject: [PATCH 041/101] re-adds images that are still required for some of the legacy file editors --- .../Umbraco/Images/editor/dictionaryItem.gif | Bin 0 -> 1040 bytes .../Umbraco/Images/editor/help.png | Bin 0 -> 377 bytes .../Images/editor/insChildTemplateNew.gif | Bin 0 -> 914 bytes .../Umbraco/Images/editor/insField.gif | Bin 0 -> 648 bytes .../Umbraco/Images/editor/insFieldByLevel.gif | Bin 0 -> 626 bytes .../Umbraco/Images/editor/insFieldByTree.gif | Bin 0 -> 621 bytes .../Umbraco/Images/editor/insMacro.gif | Bin 0 -> 610 bytes .../Umbraco/Images/editor/insMacroSB.png | Bin 0 -> 474 bytes .../Umbraco/Images/editor/insMemberItem.gif | Bin 0 -> 627 bytes .../Umbraco/Images/editor/insRazorMacro.png | Bin 0 -> 529 bytes .../Umbraco/Images/editor/inshtml.GIF | Bin 0 -> 191 bytes .../Umbraco/Images/editor/masterpageContent.gif | Bin 0 -> 132 bytes .../Images/editor/masterpagePlaceHolder.gif | Bin 0 -> 286 bytes .../Umbraco/Images/editor/renderbody.gif | Bin 0 -> 1336 bytes .../Umbraco/Images/editor/xslVisualize.gif | Bin 0 -> 663 bytes 15 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/Umbraco.Web.UI/Umbraco/Images/editor/dictionaryItem.gif create mode 100644 src/Umbraco.Web.UI/Umbraco/Images/editor/help.png create mode 100644 src/Umbraco.Web.UI/Umbraco/Images/editor/insChildTemplateNew.gif create mode 100644 src/Umbraco.Web.UI/Umbraco/Images/editor/insField.gif create mode 100644 src/Umbraco.Web.UI/Umbraco/Images/editor/insFieldByLevel.gif create mode 100644 src/Umbraco.Web.UI/Umbraco/Images/editor/insFieldByTree.gif create mode 100644 src/Umbraco.Web.UI/Umbraco/Images/editor/insMacro.gif create mode 100644 src/Umbraco.Web.UI/Umbraco/Images/editor/insMacroSB.png create mode 100644 src/Umbraco.Web.UI/Umbraco/Images/editor/insMemberItem.gif create mode 100644 src/Umbraco.Web.UI/Umbraco/Images/editor/insRazorMacro.png create mode 100644 src/Umbraco.Web.UI/Umbraco/Images/editor/inshtml.GIF create mode 100644 src/Umbraco.Web.UI/Umbraco/Images/editor/masterpageContent.gif create mode 100644 src/Umbraco.Web.UI/Umbraco/Images/editor/masterpagePlaceHolder.gif create mode 100644 src/Umbraco.Web.UI/Umbraco/Images/editor/renderbody.gif create mode 100644 src/Umbraco.Web.UI/Umbraco/Images/editor/xslVisualize.gif diff --git a/src/Umbraco.Web.UI/Umbraco/Images/editor/dictionaryItem.gif b/src/Umbraco.Web.UI/Umbraco/Images/editor/dictionaryItem.gif new file mode 100644 index 0000000000000000000000000000000000000000..e9dc737967820c2eacff5ccae0a6b8ddc60f6d3f GIT binary patch literal 1040 zcmeH`YfF=106^buUJDBm%tCC6go*@Ff@#nyHOmmAK8TIb5VbHcA^l)p*w!p4%?wPi zT1=aoQ)kn;T&8Z`CL21OCf>F=-PO%)y1m!uE&CIF>HL8M2hQ~y8Oaw5!l4t8j|HVM zE4qi3Y0S#Bdll)-N&=AW0}Aoj{PZe3w+^c}k&bVZ8Uyl0JjEButu7qHNw=3!X?6So z#kK=?>=QjwW%uyxx&;*sPPGU|1cTF{mkWA0ptD-F2@^sNij#VSS!ctXc9(J;UfxDl zc2JU^ota;?X~hs6^p4COAYgQM*^2-~#zBR}mL0RxWbvZFsr`u);QQp_veCDsKY(p| zzDV(`q5XM_&^j+$&?A_nrJVbrPo{!VAo`*5YWiRd_)tLfy)e6Q1Os*t?lJ9PIB+=j zJuo*0~?qjZT7dwCn(8JzakQO|B-S|oTsQTUBXp>J;v#9eR z!iRP@?)DLS8)mfmfS}5}hr7IlacRzG)U9k-@vr%SBv;L7w`6u`LPK;hbBbzB+SzR< zzGg<%n=mp^u2}IA{Uu(9-MLTL+}N)j)Z)Sy^f37s=s*9WKs4efcsYql<=NzexQ$*{|74oPz zOWx=`yzEvQEsxc9HNF00LiLb%yo^z&rbahhil<%+h)Sv9L?po?M)R?}x>sG43`KB! zI22i$15pFxZWZ-3u?<-br^|9rU4RN^qGGe=-@8KDA-v-!%hgG5{W;CCxpzB5<%}yY z1MVEnE@|R&>d)Wa&;{}76qf&kkd$PkZXzi4OvIY2t+;-T6%d35?DVB6cUq=Rp^(V|(yIunMk|nMY zCBgY=CFO}lsSJ)O`AMk?p1FzXsX?iUDV2pMQ*D5XBm#UwTz!20PnmMc$7k99|NmQB z{x4g$?8S>0K=ww{#dm>fI7)*2f`MERV7S-3Hxejz+0(@_q~cam!f`GJrVhs41`Z4l z1=u(gWDYSfwm3^DG(0$zqvLSk7~4}u76G3bt*wkK5|WBB4Gc*R9h)|7I&_o~s4I&p zV9LyyGpBYo3g+-RFgUh2su>tJ3k1lhFbFtsB!*4k5oQ*S02(H6NFgw6Mpgqu6SuLW ztKup~CJv=D9FL3}p3G1HTF6q$sqAAad_)z*G*=Fgd19xwVmDCHGjBIHHGzhoLO>%D u4{+F7Q^!T|g{Kw7&1_p-z z|NjHYQ7{?;Lm~tef3h$#Fi0`zfLsg86AT>r45l119vc=MY~~QwiaD`i;o%kmm4FQm z4;LN)3nTyBj+{C!d(emK<~? rAUNUVWKG_CcOnW>Pfus7?NZ4UTIw}T&pPYwsjaK8uV-auVz34Pz2|1g literal 0 HcmV?d00001 diff --git a/src/Umbraco.Web.UI/Umbraco/Images/editor/insField.gif b/src/Umbraco.Web.UI/Umbraco/Images/editor/insField.gif new file mode 100644 index 0000000000000000000000000000000000000000..3a3721dd352923b0dd762d8144f383770eb9f040 GIT binary patch literal 648 zcmZ?wbhEHb6k`x)cvi&l@83UD&*aIAwm*IQ```Ds|IWAn`u%t6lATL89li7X*X2h) z-+un{|3vxUm$h#`{dxQ8&;Q@YAMOwS@?!nvhd=Lc)BE!M@Bd{=pTGY-aP{k~6?^}$ zHU9r`^118JfByb^==9D1pQoL={r$lX!-q!_YkHUbcsYC8(p}Gv#lF4K^M8Thx7S;~ zznJy;K$^U;J zwhzvkzHGN&V%3w~CjUQ9czh`0&l1Hick@5r$@zC}#-A%Qeyn%?v&s42tJ6>3{(k=a z)xVE--rlUS^v!s5w*TG5{l%`*S`%T=MsITYORX^8=O}_nr&SY?{7o z*X?y`3)UU_|FP%hDy0{XZnaHY`}hBc{|D2ypZ_{{_5PJxPrQG2YvH;>4~`TRwaoed zVe-#cvp?S2`S0a)ARlIiR=@nZ~{Qv)-fjFS}lZBCip@=~Tq#P6{4D3%E zikg~RTHD$?I=i}?($%w~EP{M{nu8T%?CnGhrZ%a_3m93-dð1cXM|rP9$Nm<7V_vmP5yuk@r_P*fHdNvb%XKnmm6CF2 z;bZ2EzTKkFz~U^%%EH5{U9)A-LdDbGvrI2?YaZ f<->i@83UD&*aIAwm*IQo8gS{uit;CF4_6{K6EzYW*ymsg5?=NP3xtss<)$E@yCeK~H|Nrm9&rU`^ zI8yNO;gY|v+uq*j`Tt{ocxF?4QTMc^yZ(Qe?3Y;eWVgw)W3fM8&Yrb$-<=~BE4Q9# zo3{4<$DVJmw>)|K`}qM&``{c)-;B36YwmA1_khd`6;EHb>(8s^d29B6e!8}(WzM6s{r^8sSh()czwd7!9!ad}UGnbk%Tc4~RrOGF z3+idsj<$9WmWi0!WG2rapds&Q6&|Q)pl|GB@6hTcCFg1*Wx}v?7b}MgW2>^bo*cip z&aRyzd;&cCTNIplO^sw!*tvF{I&-esB8=D0&spdMHaE8M7k{+!xTth2R!HSb#D#^- s({-2((oQUJb>U+Rndjq?A~9p8ol2_Nkb{b`tARR$we)5zQ33?clG{->kj>S)%@Z9oPXcn&Retp%iVnY;GF*-C#>9h;>p|J zcaBut-)^vC>xqX)692w#dwZj2`}wcam+hXuY?pgf@weAo9vmt7{B-S)m$M)2F#P{x z|IJlO|NsAIpd(QH$->CM5Y3GqMMpqTHD$?I=i}?LjBduzr+}a^ z&;AxuZy7@y30DsGU8l~RYxeS$(R8#_JmJjB#AbD;Mg0WBb%y6J>|ZzQzGGnd#3si3 zwTbyotgu5sBdg~BZ7g@TI?QY05)o2~nbCdyK)ay&yQ&Wgos679We$=C7aNE literal 0 HcmV?d00001 diff --git a/src/Umbraco.Web.UI/Umbraco/Images/editor/insMacro.gif b/src/Umbraco.Web.UI/Umbraco/Images/editor/insMacro.gif new file mode 100644 index 0000000000000000000000000000000000000000..eeb3cdb444ac6581d41b75834279ecf941fbd9f6 GIT binary patch literal 610 zcmZ?wbhEHb6k`x)c*el+{Q1kbpZbFz3zh8d% zbLG|(Z$AAwaP{lrO($lp*t>k&nYpX?FIabI;krXhHyvHE^~B{zKiBR&y>aikL#J;( zdHehA=Rcpn|26eYp1f%L^kuuJE!{PB$n^m$|Gmo3p>wk3YqnelaR!MB6u-wsuOJ5u-USj+blZQoCJeLvOn{dE8Lvy*?E zpZ??GoS#>h{Jg&M=dJa>?rs0|V9)Qzhkidj_WSAafB*jd|Noz1pn>8~7Dfh!5C$EP zLqKujz`nmBq^Y^3wXIbkxU0LHSw>X0Jw#4YLY!MfSe0L1QBH2+q(zIH6uL~c^qIAF zELg<&7cSYnWvfO|V1U1$uaCEvr-z%yB&A?I(;iDhV-}%Jt#ZMZ!F@bzY+S6oiWj!t zfAH|pW)1geZZ1v^_I9>5)>f0Ay1P0%zIku;>qt9aPmdZ)&VmQI7dkZ6V^(Z1WOd_` zWZ-bo(C~=j7FTDB*?q97ja5iQT~kUxp@EHwQ%J#r!AocbpF#4e7Rz0004@NklPr%n+>$8H^hPNon+hbT)*dQnR%Evd}PvaQUQR6$J(}Q^-Akyd+A`%z6$Pn^)tv}By82qf%f3iUR3>PVpnO4r(|4{7 zwtG(5W}Ngxy`K3COFZPb1)t$jq3#!gG2vIZg z`8Nj7uN`ZHr=9zacBCkwZW51{@N%GjJ0u)<`n(- zQt--G@Ig?DKOdSHY<2DHgXt`rPrnY+q#tD@vw4Iv0;U`Ro$wFJ6OiVGfb{occM?k| zBf_5&K-X)Ax%dsXf=O%y;&79DO0U!+Ieq*3F`KE?b-|3CKp|FPfFH)GbyeSV2mfB%1&w`PA< z_2joVdLA4pIB@mbqqF@V?$7!E`|z`4v5yZ$e7T#y{ruOzuiI{}Qu=%+XTiEdpTGZI zvGv5XrMv!rm>izjRMWd;;krW`_nv$5_V>q!OV;i@{r2AC>C1LcU$*Q2#|ieqIqp%# zcaBuNy;<{MhvB=sGrzx>_4(=A=LalruT%T+a`vBB&EH;cX`8n8=d0QGw;Md!ZSwF) z;<@Y3|NsBbKu4hXlZBCiA(}x4WG5(27}zf~L^n0Jw6?W(bar(&>1c-q%IoX(G&}hy z8@WsPPHhU3mhm@`_71g^(^9lm_w-P0H4~TA2oaB9*tv_>(u%Rwk6%SnhTnA8P99+q z?)@z;;ZlY+62=TnOeasDZS{(j3J5lrWfBy;$;QfQajsSE1QRne)5}*1Z(FQST>tWw zOY{euV1v59Ln9NHhyH`=I2JW9Mu2fjrGiNgM%QrJ;80I6+%%$LtnjzeF;&>tof}4fM(S`H9 zRxshjqf?8s`B>nw85gEZDYwBf7mbILj_|;*H=O{l0{k1`uXIvgr8Q@(L8D5Cnieg} z8oE$a%_6s~2^ocTNXx52a#lH#(uG&-0YM^l9oYdl7qasw0pk4{e@c|4@uYC{ZP6y!7$3VQP z#r1j);9=<;5S~of0T|jLtS)^C{q_*lAH_*pY4=qurWc{DPWKk$${v+N2>JY_Z T!Jt=y00000NkvXXu0mjfyIlNb literal 0 HcmV?d00001 diff --git a/src/Umbraco.Web.UI/Umbraco/Images/editor/inshtml.GIF b/src/Umbraco.Web.UI/Umbraco/Images/editor/inshtml.GIF new file mode 100644 index 0000000000000000000000000000000000000000..3442c48cd48c9fdb78d08459a533baef47997cdd GIT binary patch literal 191 zcmZ?wbhEHb6k`x)*v!E2|NsB$>gtx3mIDV4R5LKN07XE8KvoNc0t+y}f#OdVMg|6E z1|5(H$P5RToPractGB8*oV8B4zMxVig0-E)!%?CyC4E;(ZZgBH<=-c;F&#@ZcF5s5 z7U{~Mp<>3!9P;sHFVz3j_fxPWUvMRMVdi) literal 0 HcmV?d00001 diff --git a/src/Umbraco.Web.UI/Umbraco/Images/editor/masterpageContent.gif b/src/Umbraco.Web.UI/Umbraco/Images/editor/masterpageContent.gif new file mode 100644 index 0000000000000000000000000000000000000000..ad993382348565d4f7c7acdee5121d833c70bc9e GIT binary patch literal 132 zcmZ?wbhEHb6k`x)Sj586($aG5*f9nMhW}syWI%x8PZmZ71{MY#5ErDDf!Whz*PVX` zr#!U<8D5_;WN{YgOVV_3@?3Xd`zkfA{m#D|jBD&P*WTirqrav2OObMda|e%z^znt0 Z4t}wf417{@@nzn+X&Wkh^t^=_tN{TBF7^Nb literal 0 HcmV?d00001 diff --git a/src/Umbraco.Web.UI/Umbraco/Images/editor/masterpagePlaceHolder.gif b/src/Umbraco.Web.UI/Umbraco/Images/editor/masterpagePlaceHolder.gif new file mode 100644 index 0000000000000000000000000000000000000000..00de9bed9ad59806e696ccad106b3f608e16cbf3 GIT binary patch literal 286 zcmZ?wbhEHb6k`x)I3mWdbm`Kbo}RsX_pV*Lwxy+|y1M$$Pjwrc9Y~ z?%cWW-~WIA|Ns5>|IeR4zjp8W^Y=t=Vsbbj&ZE|)?%Z0yEF;3`$xUry@sI#Z?ciQrZ@QJW!usc-IIg#cFVINM%8)eo$(0erZv1Dp0vH$f^P>=c3falKi5O{QMkPCP( zUIa|kjQ{`r{qy_R&mZ5vef{$J)5j0*-@SeF`qj%9&!0Vg^7zri2lwyYy>t84%^TOR zUA=Po(!~qs&z(JU`qar2$B!L7a`@1}1N-;w-Lrew&K=vgZQZhY)5ZeMTG_VdAT{+S(zE>X{jm6Nr?&Zaj`McQIQehVWA9RT!WL(IBz?v24h5IWKTi^73s~Zhm@J&d&XZ9=&+|^yTZ0iBns8CoWmH zsek&cY4aBDIdo+D{Dtc_ZGQgx&88hYCQYAy<@)t|_wVmNa`@oUgW-{(D<;JLJ(l=( zbNHqi`DN+;lZ%}f_7z2k2R>im{$z&Zg?iJy@=g*(tzkmPw_3P))pFe*5`2PL-w{PFReEIU>!-x0p-@kkJ?(N&RZ{ECl{rdH* zSFc{aeEH(Vi|5auKYRA<>C>lAo;=yKY177y8#ip&uzvmeb?erxUAuP8nl-CeuU@rk zRd;uHRaI4DVxotK2QVbTAxZ+F_>+Z^fgztk2c!oSCk*Ug8uFW(TUy)NJK9sbds0*S zlarF#(-@~spTRh}EzLV1z;B_ix3?c-a(lW@c2stBc6MZTh<8eRhHp$xY+O!!Y)+`p zidLTtznI+kgv8vq+%Ugwt=?Gyd9itM@#gwkngOXkN3#OspT`^N8LDf`%zYB{!c2{k zLxfk7>v7A~PtSD&?(9BrfT>gU=p~Pfn_9WM1w~{WA_5p$4mojcO*zS=e1wHlP_DyZ z!jTJ-nh` Date: Thu, 31 Mar 2016 16:36:36 +0200 Subject: [PATCH 042/101] U4-8103 - refactor YSOD overlay to display inner exceptions --- .../common/overlays/ysod/ysod.controller.js | 12 ++++++++++ .../src/views/common/overlays/ysod/ysod.html | 8 +++++++ src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 1 + .../umbraco/config/lang/en_us.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/ja.xml | 23 ++++++++++--------- src/Umbraco.Web.UI/umbraco/config/lang/ru.xml | 1 + 6 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.controller.js index a6d66a0f99..5d42edd3a5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.controller.js @@ -10,4 +10,16 @@ angular.module("umbraco") $scope.model.error.data.StackTrace = $scope.model.error.data.StackTrace.trim(); } + if ($scope.model.error && $scope.model.error.data) { + $scope.model.error.data.InnerExceptions = []; + var ex = $scope.model.error.data.InnerException; + while (ex) { + if (ex.StackTrace) { + ex.StackTrace = ex.StackTrace.trim(); + } + $scope.model.error.data.InnerExceptions.push(ex); + ex = ex.InnerException; + } + } + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.html index 6c35763069..9a4ff7cb81 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/ysod/ysod.html @@ -16,4 +16,12 @@
      {{model.error.data.StackTrace}}
      + +
      +
      + Inner Exception: +
      +
      {{e.ExceptionType}}: {{e.ExceptionMessage}}
      +
      {{e.StackTrace}}
      +
      diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 3fdaf8110b..b810183d42 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -272,6 +272,7 @@ External login providers Exception Details Stacktrace + Inner Exception Link your Un-Link your diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index dca808c4c8..aa2518a372 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -272,6 +272,7 @@ External login providers Exception Details Stacktrace + Inner Exception Link your Un-Link your diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml index 44d2433664..e1b675b208 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml @@ -242,7 +242,7 @@ 項目の選択 キャッシュされている項目の表示 フォルダーの作成... - + オリジナルに関連付ける フレンドリーなコミュニティ @@ -268,6 +268,7 @@ 外部ログイン プロバイダー 例外の詳細 スタックトレース + Inner Exception 次をリンク: 次をリンク解除: @@ -475,7 +476,7 @@ 移動 埋め込み - + ブラック グリーン @@ -484,7 +485,7 @@ ブルー レッド - + タブの追加 プロパティの追加 @@ -503,7 +504,7 @@ リスト ビューの切り替え ルートとして許可に切り替え - + 背景色 太字 @@ -537,7 +538,7 @@ アップグレードボタンを押すとUmbraco %0% 用にデータベースをアップグレードします。

      心配ありません。 - コンテントが消える事はありませんし、後で続けることもできます。 -

      +

      ]]> 次へ を押して続行してください。]]> @@ -583,7 +584,7 @@ ]]> スクラッチから始めたい どうしたらいいの?) 後からRunwayをインストールする事もできます。そうしたくなった時は、Developerセクションのパッケージへどうぞ。 ]]> @@ -597,8 +598,8 @@ 簡単なウェブサイトから始めたい - "Runway"(≈滑走路)は幾つかの基本的なテンプレートから簡単なウェブサイトを用意します。このインストーラーは自動的にRnwayをセットアップできますが、 - これを編集したり、拡張したり、削除する事も簡単にできます。もしUmbracoを完璧に使いこなせるならばこれは不要です。とはいえ、 + "Runway"(≈滑走路)は幾つかの基本的なテンプレートから簡単なウェブサイトを用意します。このインストーラーは自動的にRnwayをセットアップできますが、 + これを編集したり、拡張したり、削除する事も簡単にできます。もしUmbracoを完璧に使いこなせるならばこれは不要です。とはいえ、 Runwayを使う事は、手間なく簡単にUmbracoを始める為には良い選択肢です。 Runwayをインストールすれば、必要に応じてRunwayによる基本的な構成のページをRunwayのモジュールから選択できます。

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

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

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

      @@ -1064,7 +1065,7 @@ Runwayをインストールして作られた新しいウェブサイトがど このエディターを使用すると新しい設定で更新されます - + 代替フィールド 代替テキスト diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index a8c69ea0f4..fbf0e0adf2 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -328,6 +328,7 @@ Провайдеры аутентификации Подробное сообщение об ошибке Трассировка стека + Inner Exception Связать Разорвать связь From c3055ca43fc6f10ee8b79ad89599c189cfac1fcd Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 31 Mar 2016 16:37:16 +0200 Subject: [PATCH 043/101] U4-8103 - fix DisableBrowserCacheAttribute to handle exceptions --- .../Filters/DisableBrowserCacheAttribute.cs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs b/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs index e1890326fb..0bb283bd1a 100644 --- a/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs @@ -1,15 +1,6 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; using System.Net.Http.Headers; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Web; -using System.Web.Http.Controllers; using System.Web.Http.Filters; -using Umbraco.Core; namespace Umbraco.Web.WebApi.Filters { @@ -24,6 +15,9 @@ namespace Umbraco.Web.WebApi.Filters base.OnActionExecuted(actionExecutedContext); + // happens if exception + if (actionExecutedContext.Response == null) return; + //NOTE: Until we upgraded to WebApi 2, this didn't work correctly and we had to revert to using // HttpContext.Current responses. I've changed this back to what it should be now since it works // and now with WebApi2, the HttpContext.Current responses dont! Anyways, all good now. @@ -42,9 +36,6 @@ namespace Umbraco.Web.WebApi.Filters //Mon, 01 Jan 1990 00:00:00 GMT new DateTimeOffset(1990, 1, 1, 0, 0, 0, TimeSpan.Zero); } - - - } } } From 4df5ddcb4cf0249037f8840c9b8b2d8163d8669b Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 31 Mar 2016 18:50:25 +0200 Subject: [PATCH 044/101] Removes old serializers for old tree formats --- .../umbraco/Trees/XmlTree.cs | 298 +----------------- 1 file changed, 2 insertions(+), 296 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/XmlTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/XmlTree.cs index c6dfd4c758..9b62e5ea1f 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/XmlTree.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/XmlTree.cs @@ -11,13 +11,6 @@ using Action = Umbraco.Web._Legacy.Actions.Action; namespace umbraco.cms.presentation.Trees { - - public enum SerializedTreeType - { - JSONTree, - JsTree - } - /// /// Used for serializing data to XML as the data structure for the JavaScript tree /// @@ -27,73 +20,13 @@ namespace umbraco.cms.presentation.Trees public XmlTree() { - //set to the XTree provider by default. - //m_TreeType = SerializedTreeType.XmlTree; - //m_TreeType = SerializedTreeType.JSONTree; - m_TreeType = SerializedTreeType.JsTree; - Init(); } - - /// - /// Use this constructor to force a tree provider to be used - /// - /// - public XmlTree(SerializedTreeType treeType) - { - m_TreeType = treeType; - Init(); - } - + private void Init() { - m_JSSerializer = new JavaScriptSerializer { MaxJsonLength = int.MaxValue }; - - switch (m_TreeType) - { - case SerializedTreeType.JSONTree: - m_JSSerializer.RegisterConverters(new List() - { - new JSONTreeConverter(), - new JSONTreeNodeConverter() - }); - break; - case SerializedTreeType.JsTree: - m_JSSerializer.RegisterConverters(new List() - { - new JsTreeNodeConverter() - }); - break; - } - - } - - private JavaScriptSerializer m_JSSerializer; - private SerializedTreeType m_TreeType; - - /// - /// Returns the string representation of the tree structure depending on the SerializedTreeType - /// specified. - /// - /// - public override string ToString() - { - return ToString(m_TreeType); - } - - public string ToString(SerializedTreeType type) - { - switch (type) - { - case SerializedTreeType.JsTree: - return m_JSSerializer.Serialize(this.treeCollection); - case SerializedTreeType.JSONTree: - return m_JSSerializer.Serialize(this); - } - return ""; - } - + public void Add(XmlTreeNode obj) { treeCollection.Add(obj); @@ -532,231 +465,4 @@ namespace umbraco.cms.presentation.Trees } - /// - /// Used to serialize an XmlTree object to JSON for supporting a JSON Tree View control. - /// - internal class JSONTreeConverter : JavaScriptConverter - { - /// - /// Not implemented as we never need to Deserialize - /// - /// - /// - /// - /// - public override object Deserialize(IDictionary dictionary, Type type, JavaScriptSerializer serializer) - { - throw new NotImplementedException(); - } - - /// - /// Serializes an XmlTree object with the relevant values. - /// - /// - /// - /// - public override IDictionary Serialize(object obj, JavaScriptSerializer serializer) - { - XmlTree tree = obj as XmlTree; - - Dictionary resultSet = new Dictionary(); - Dictionary resultTree = new Dictionary(); - - if (tree != null) - { - //add a count property for the count of total nodes - resultTree.Add("Count", tree.Count); - - List nodes = new List(); - foreach (XmlTreeNode node in tree.treeCollection) - nodes.Add(node); - - resultTree.Add("Nodes", nodes); - } - - resultSet.Add("Tree", resultTree); - - return resultSet; - } - - public override IEnumerable SupportedTypes - { - get { return new Type[] { typeof(XmlTree) }; } - } - } - - /// - /// Used to serialize an XmlTreeNode object to JSON for supporting a JS Tree View control. - /// - internal class JSONTreeNodeConverter : JavaScriptConverter - { - - /// - /// Not implemented as we never need to Deserialize - /// - /// - /// - /// - /// - public override object Deserialize(IDictionary dictionary, Type type, JavaScriptSerializer serializer) - { - throw new NotImplementedException(); - } - - /// - /// Serializes an XmlTreeNode object with the relevant values. - /// - /// - /// - /// - public override IDictionary Serialize(object obj, JavaScriptSerializer serializer) - { - XmlTreeNode node = obj as XmlTreeNode; - - Dictionary result = new Dictionary(); - - if (node != null) - { - //add the properties - result.Add(XmlTreeNode.TreeAttributes.notPublished.ToString(), node.NotPublished); - result.Add(XmlTreeNode.TreeAttributes.isProtected.ToString(), node.IsProtected); - result.Add(XmlTreeNode.TreeAttributes.text.ToString(), node.Text); - result.Add(XmlTreeNode.TreeAttributes.action.ToString(), node.Action); - result.Add(XmlTreeNode.TreeAttributes.src.ToString(), node.Source); - result.Add(XmlTreeNode.TreeAttributes.iconClass.ToString(), node.IconClass); - result.Add(XmlTreeNode.TreeAttributes.icon.ToString(), node.Icon); - result.Add(XmlTreeNode.TreeAttributes.openIcon.ToString(), node.OpenIcon); - result.Add(XmlTreeNode.TreeAttributes.nodeType.ToString(), node.NodeType); - result.Add(XmlTreeNode.TreeAttributes.nodeID.ToString(), node.NodeID); - - //Add the menu as letters. - result.Add(XmlTreeNode.TreeAttributes.menu.ToString(), node.Menu != null && node.Menu.Count > 0 ? Action.ToString(node.Menu) : ""); - - return result; - } - - return new Dictionary(); - - } - - public override IEnumerable SupportedTypes - { - get { return new Type[] { typeof(XmlTreeNode) }; } - } - } - - /// - /// Used to serialize an XmlTreeNode object to JSON for supporting a JS Tree View control. - /// - internal class JsTreeNodeConverter : JavaScriptConverter - { - - /// - /// A reference path to where the icons are actually stored as compared to where the tree themes folder is - /// - private static string IconPath = IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/images/umbraco/"; - - /// - /// Not implemented as we never need to Deserialize - /// - /// - /// - /// - /// - public override object Deserialize(IDictionary dictionary, Type type, JavaScriptSerializer serializer) - { - throw new NotImplementedException(); - } - - /// - /// Serializes an XmlTreeNode object with the relevant values. - /// - /// - /// - /// - public override IDictionary Serialize(object obj, JavaScriptSerializer serializer) - { - XmlTreeNode node = obj as XmlTreeNode; - - Dictionary result = new Dictionary(); - - if (node != null) - { - //the data object to build the node - Dictionary data = new Dictionary(); - - data.Add("title", node.Text); - - - - //the attributes object fot the tree node link (a) object created - Dictionary dataAttributes = new Dictionary(); - string cssClass = ""; - if (node.Icon.StartsWith(".spr")) - cssClass = "sprTree " + node.Icon.TrimStart('.'); - else - { - //there is no sprite so add the noSpr class - cssClass = "sprTree noSpr"; - data.Add("icon", IconPath + node.Icon); - } - dataAttributes.Add("class", cssClass + (string.IsNullOrEmpty(node.IconClass) ? "" : " " + node.IconClass)); - dataAttributes.Add("href", node.Action); - - //add a metadata dictionary object, we can store whatever we want in this! - //in this case we're going to store permissions & the tree type & the child node source url - //For whatever reason jsTree requires this JSON output to be quoted!?! - //This also needs to be stored in the attributes object with the class above. - Dictionary metadata = new Dictionary(); - //the menu: - metadata.Add("menu", node.Menu == null ? "" : Action.ToString(node.Menu)); - //the tree type: - metadata.Add("nodeType", node.NodeType); - //the data url for child nodes: - metadata.Add("source", node.Source); - - //the metadata/jsTree requires this property to be in a quoted JSON syntax - JavaScriptSerializer jsSerializer = new JavaScriptSerializer(); - string strMetaData = jsSerializer.Serialize(metadata).Replace("\"", "'"); - dataAttributes.Add("umb:nodedata", strMetaData); - - data.Add("attributes", dataAttributes); - - //add the data structure - result.Add("data", data); - - //state is nothing if no children - if ((node.HasChildren || node.IsRoot) && !string.IsNullOrEmpty(node.Source)) - result.Add("state", "closed"); - - //the attributes object for the tree node (li) object created - Dictionary attributes = new Dictionary(); - attributes.Add("id", node.NodeID); - attributes.Add("class", string.Join(" ", node.Style.AppliedClasses.ToArray())); - - if (node.IsRoot) - attributes.Add("rel", "rootNode"); - else - attributes.Add("rel", "dataNode"); - - //the tree type should always be set, however if some developers have serialized their tree into an XmlTree - //then there is no gaurantees that this will be set if they didn't use the create methods of XmlTreeNode. - //So, we'll set the treetype to the nodetype if it is null, this means that the tree on the front end may - //not function as expected when reloding a node. - attributes.Add("umb:type", string.IsNullOrEmpty(node.TreeType) ? node.NodeType : node.TreeType); - - result.Add("attributes", attributes); - - return result; - } - - return null; - - } - - public override IEnumerable SupportedTypes - { - get { return new Type[] { typeof(XmlTreeNode) }; } - } - } } From c6a586274ec6b260fb88e570724d6e4e9550bb6b Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 31 Mar 2016 19:40:07 +0200 Subject: [PATCH 045/101] Merge branch 'U4-8189' of https://github.com/danlister/Umbraco-CMS into danlister-U4-8189 Conflicts: src/Umbraco.Core/Constants-Applications.cs src/Umbraco.Web/Umbraco.Web.csproj --- src/Umbraco.Core/Constants-Applications.cs | 5 + src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml | 1 + src/Umbraco.Web.UI/config/trees.config | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/de.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 1 + .../umbraco/config/lang/en_us.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/es.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/fr.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/he.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/it.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/ja.xml | 3 +- src/Umbraco.Web.UI/umbraco/config/lang/ko.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/nl.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/pl.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/pt.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/ru.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/sv.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/zh.xml | 1 + src/Umbraco.Web/Trees/UsersTreeController.cs | 103 ++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 2 +- .../umbraco/Trees/loadUsers.cs | 112 ------------------ .../umbraco/users/EditUser.aspx.cs | 4 +- 24 files changed, 131 insertions(+), 117 deletions(-) create mode 100644 src/Umbraco.Web/Trees/UsersTreeController.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadUsers.cs diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index 5fb2fd7d8a..2feae59bad 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -114,6 +114,11 @@ /// public const string UserTypes = "userTypes"; + /// + /// alias for the users tree. + /// + public const string Users = "users"; + public const string Scripts = "scripts"; public const string PartialViews = "partialViews"; diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml index c1e806177a..20e252c431 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/cs.xml @@ -964,6 +964,7 @@ Šablony XSLT soubory Typy Uživatelů + Uživatelé Nová aktualizace je připrvena diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml index 0d527fe4d8..12031dc0e0 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/nb.xml @@ -929,6 +929,7 @@ Vennlig hilsen Umbraco roboten XSLT Filer Analytics Brukertyper typer + Brukere Ny oppdatering er klar diff --git a/src/Umbraco.Web.UI/config/trees.config b/src/Umbraco.Web.UI/config/trees.config index b1820c3460..e26259c357 100644 --- a/src/Umbraco.Web.UI/config/trees.config +++ b/src/Umbraco.Web.UI/config/trees.config @@ -23,7 +23,7 @@ - + diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index aad5e0d69b..df4318ec2a 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -999,6 +999,7 @@ Mange hilsner fra Umbraco robotten XSLT-filer Analytics Bruger Typer + Brugere Ny opdatering er klar diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml index 158bb06287..4bb906f983 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/de.xml @@ -947,6 +947,7 @@ Ihr freundlicher Umbraco-Robot XSLT-Dateien Auswertungen Benutzertypen + Benutzer Neues Update verfügbar diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index f6d092ba31..b8f362954d 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1189,6 +1189,7 @@ To manage your website, simply open the Umbraco back office and start adding con XSLT Files Analytics User Types + Users New update ready diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index f89eb06b94..dc82daaa49 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -1193,6 +1193,7 @@ To manage your website, simply open the Umbraco back office and start adding con XSLT Files Analytics User Types + Users New update ready diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml index ef67c6e6d4..097c028d2b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/es.xml @@ -851,6 +851,7 @@ Plantillas Archivos XSLT Tipos de Usuarios + Usuarios Existe una nueva actualización diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml index 4045056567..853f1b11f1 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/fr.xml @@ -985,6 +985,7 @@ Pour gérer votre site, ouvrez simplement le backoffice Umbraco et commencez à Modèles Fichiers XSLT Types d'utilisateurs + Utilisateurs Nouvelle mise à jour prête diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/he.xml b/src/Umbraco.Web.UI/umbraco/config/lang/he.xml index 8722008a2e..e42c382b20 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/he.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/he.xml @@ -870,6 +870,7 @@ To manage your website, simply open the Umbraco back office and start adding con תבניות קבצי XSLT משתמש מקליד + משתמש עידכון חדש זמין diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/it.xml b/src/Umbraco.Web.UI/umbraco/config/lang/it.xml index f4475cb23c..039d727044 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/it.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/it.xml @@ -843,6 +843,7 @@ Per gestire il tuo sito web, è sufficiente aprire il back office di Umbraco e i Templates Files XSLT Tipi di Utente + Utenti diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml index b92f17a57e..b74676dc50 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ja.xml @@ -1159,7 +1159,7 @@ Runwayをインストールして作られた新しいウェブサイトがど 役割 メンバーの種類 ドキュメントタイプ - 関連タイプ + 関連タイプ パッケージ パッケージ Python ファイル @@ -1173,6 +1173,7 @@ Runwayをインストールして作られた新しいウェブサイトがど XSLT ファイル アナリティクス ユーザータイプ + ユーザー 新しい更新があります diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml index 9743954202..8435cb9c48 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ko.xml @@ -847,6 +847,7 @@ 템플릿 XSLT 파일 사용자 유형 + 사용자 새 업데이트가 준비되었습니다. diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml index 9cf8e76337..d69d5954dd 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/nl.xml @@ -952,6 +952,7 @@ Om een vertalingstaak te sluiten, ga aub naar het detailoverzicht en klik op de Sjablonen XSLT Bestanden Gebruiker Types + Gebruikers Nieuwe update beschikbaar diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml b/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml index 132f5ee65e..dbdefcb6c2 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/pl.xml @@ -743,6 +743,7 @@ Miłego dnia!]]> Szablony Pliki XSLT Typy Użytkowników + Użytkowników Aktualizacja jest gotowa diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml b/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml index 9cf9181c80..86887b00df 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/pt.xml @@ -831,6 +831,7 @@ Para fechar a tarefa de tradução vá até os detalhes e clique no botão "Fech Modelos Arquivos XSLT Tipos de Usuários + Usuários Nova atualização pronta diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index 5075fd8eb9..454216ed9d 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -1162,6 +1162,7 @@ Шаблоны Файлы XSLT Типы пользователей + пользователи Доступны обновления diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml index 3c7947ccee..ae7e8a479e 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml @@ -852,6 +852,7 @@ Sidmallar XSLT-filer Användartyper + Användare Ny uppdatering tillgänglig diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml index 9ced516644..4e25e92ef2 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/zh.xml @@ -921,6 +921,7 @@ 模板 XSLT文件 用户类型 + Users 有可用更新 diff --git a/src/Umbraco.Web/Trees/UsersTreeController.cs b/src/Umbraco.Web/Trees/UsersTreeController.cs new file mode 100644 index 0000000000..2cd2ec562f --- /dev/null +++ b/src/Umbraco.Web/Trees/UsersTreeController.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net.Http.Formatting; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Web.WebApi.Filters; +using Umbraco.Web._Legacy.Actions; +using Umbraco.Core.Services; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Mvc; +using Constants = Umbraco.Core.Constants; + +namespace Umbraco.Web.Trees +{ + [UmbracoTreeAuthorize(Constants.Trees.Users)] + [Tree(Constants.Applications.Users, Constants.Trees.Users, null)] + [PluginController("UmbracoTrees")] + [CoreTree] + public class UsersTreeController : TreeController + { + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var nodes = new TreeNodeCollection(); + + int totalusers; + var users = new List(Services.UserService.GetAll(0, int.MaxValue, out totalusers)); + + var currentUser = UmbracoContext.Current.Security.CurrentUser; + var currentUserIsAdmin = currentUser.IsAdmin(); + + foreach (var user in users.OrderBy(x => x.IsApproved == false)) + { + if (UmbracoConfig.For.UmbracoSettings().Security.HideDisabledUsersInBackoffice && + (UmbracoConfig.For.UmbracoSettings().Security.HideDisabledUsersInBackoffice == false || + user.IsApproved == false)) + { + continue; + } + + var node = CreateTreeNode( + user.Id.ToString(CultureInfo.InvariantCulture), + "-1", + queryStrings, + user.Name, + "icon-user", + false, + "/" + queryStrings.GetValue("application") + "/framed/" + + Uri.EscapeDataString("users/EditUser.aspx?id=" + user.Id)); + + if (user.Id == 0) + { + if (currentUser.Id != 0) + continue; + } + else if (currentUserIsAdmin == false && user.IsAdmin()) + continue; + + if (user.IsApproved == false) + node.CssClasses.Add("not-published"); + + nodes.Add(node); + } + + return nodes; + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + + if (id == Constants.System.Root.ToInvariantString()) + { + // Root actions + menu.Items.Add(Services.TextService.Localize("actions", ActionNew.Instance.Alias)) + .ConvertLegacyMenuItem(null, "users", queryStrings.GetValue("application")); + + menu.Items.Add( + Services.TextService.Localize("actions", ActionRefresh.Instance.Alias), true); + return menu; + } + + // If administator, don't create a menu + if (id == "0") + return menu; + + // Disable user + menu.Items.Add( + Services.TextService.Localize("actions", ActionDisable.Instance.Alias), + false, + new Dictionary + { + {MenuItem.JsActionKey, ActionDisable.Instance.JsFunctionName} + } + ); + + return menu; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 318c23414b..cc7bc3082b 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -332,6 +332,7 @@ + @@ -1532,7 +1533,6 @@ - diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadUsers.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadUsers.cs deleted file mode 100644 index e5418c37be..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadUsers.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Umbraco.Core.Configuration; -using umbraco.cms.presentation.Trees; -using System.Linq; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Web; -using Umbraco.Web.Trees; -using Umbraco.Web._Legacy.Actions; - - -namespace umbraco -{ - /// - /// Handles loading of all umbraco users into the users application tree - /// - [Tree(Constants.Applications.Users, "users", "Users")] - public class loadUsers : BaseTree - { - public loadUsers(string application) : base(application) { } - - protected override void CreateRootNode(ref XmlTreeNode rootNode) - { - } - - /// - /// Renders the Javascript. - /// - /// The javascript. - public override void RenderJS(ref StringBuilder Javascript) - { - Javascript.Append( - @" -function openUser(id) { - UmbClientMgr.contentFrame('users/editUser.aspx?id=' + id); -} -"); - } - - protected override void CreateAllowedActions(ref List actions) - { - actions.Clear(); - actions.Add(ActionDisable.Instance); - } - - public override void Render(ref XmlTree tree) - { - int totalusers; - var users = new List(Services.UserService.GetAll(0, int.MaxValue, out totalusers)); - - var currUser = UmbracoContext.Current.Security.CurrentUser; - - bool currUserIsAdmin = currUser.IsAdmin(); - foreach (var u in users.OrderBy(x => x.IsApproved == false)) - { - if (UmbracoConfig.For.UmbracoSettings().Security.HideDisabledUsersInBackoffice == false - || (UmbracoConfig.For.UmbracoSettings().Security.HideDisabledUsersInBackoffice && u.IsApproved)) - { - - XmlTreeNode xNode = XmlTreeNode.Create(this); - - // special check for ROOT user - if (u.Id == 0) - { - //if its the administrator, don't create a menu - xNode.Menu = null; - //if the current user is not the administrator, then don't add this node. - if (currUser.Id != 0) - continue; - } - // Special check for admins in general (only show admins to admins) - else if (!currUserIsAdmin && u.IsAdmin()) - { - continue; - } - - - - - - //xNode.IconClass = "umbraco-tree-icon-grey"; - - xNode.NodeID = u.Id.ToString(); - xNode.Text = u.Name; - xNode.Action = "javascript:openUser(" + u.Id + ");"; - xNode.Icon = "icon-user"; - xNode.OpenIcon = "icon-user"; - - if (u.IsApproved == false) { - xNode.Style.DimNode(); - } - - OnBeforeNodeRender(ref tree, ref xNode, EventArgs.Empty); - if (xNode != null) - { - tree.Add(xNode); - OnAfterNodeRender(ref tree, ref xNode, EventArgs.Empty); - } - - - } - - - } - } - - } - -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs index bbae68379c..7ec80470a7 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/users/EditUser.aspx.cs @@ -214,8 +214,8 @@ namespace umbraco.cms.presentation.user SetupForm(); ClientTools - .SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) - .SyncTree(UID.ToString(), IsPostBack); + .SetActiveTreeType(Constants.Trees.Users) + .SyncTree(UID.ToString(), false); } From 1b8747bd6d026ae3ddd29365da729770714dcd1b Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 31 Mar 2016 19:41:42 +0200 Subject: [PATCH 046/101] removes legacy disable user menu item and uses the new/normal way of dealing with menu items. --- .../lib/umbraco/LegacyUmbClientMgr.js | 14 --- .../common/services/menuactions.service.js | 13 ++- .../Application/UmbracoApplicationActions.js | 12 --- src/Umbraco.Web/Models/Trees/DisableUser.cs | 15 +++ src/Umbraco.Web/Trees/UsersTreeController.cs | 15 +-- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- .../_Legacy/Actions/ActionDisable.cs | 91 ------------------- 7 files changed, 33 insertions(+), 129 deletions(-) create mode 100644 src/Umbraco.Web/Models/Trees/DisableUser.cs delete mode 100644 src/Umbraco.Web/_Legacy/Actions/ActionDisable.cs diff --git a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js index 51d437b9cb..71e7afec12 100644 --- a/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js +++ b/src/Umbraco.Web.UI.Client/lib/umbraco/LegacyUmbClientMgr.js @@ -190,20 +190,6 @@ Umbraco.Sys.registerNamespace("Umbraco.Application"); var actions = { openDashboard : function(section){ navService.changeSection(section); - }, - actionDisable: function () { - localizationService.localize("defaultdialogs_confirmdisable").then(function (txtConfirmDisable) { - var currentMenuNode = UmbClientMgr.mainTree().getActionNode(); - if (currentMenuNode) { - if (confirm(txtConfirmDisable + ' "' + UmbClientMgr.mainTree().getActionNode().nodeName + '"?\n\n')) { - angularHelper.safeApply($rootScope, function () { - userResource.disableUser(currentMenuNode.nodeId).then(function () { - UmbClientMgr.mainTree().syncTree("-1," + currentMenuNode.nodeId, true); - }); - }); - } - } - }); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js b/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js index bcfb5df0a7..c7438a1c47 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js @@ -8,10 +8,21 @@ * @description * Defines the methods that are called when menu items declare only an action to execute */ -function umbracoMenuActions($q, treeService, $location, navigationService, appState) { +function umbracoMenuActions($q, treeService, $location, navigationService, appState, localizationService, userResource) { return { + "DisableUser": function(args) { + localizationService.localize("defaultdialogs_confirmdisable").then(function (txtConfirmDisable) { + var currentMenuNode = UmbClientMgr.mainTree().getActionNode(); + if (confirm(txtConfirmDisable + ' "' + args.entity.name + '"?\n\n')) { + userResource.disableUser(args.entity.id).then(function () { + navigationService.syncTree({ tree: args.treeAlias, path: [args.entity.parentId, args.entity.id], forceReload: true }); + }); + } + }); + }, + /** * @ngdoc method * @name umbraco.services.umbracoMenuActions#RefreshNode diff --git a/src/Umbraco.Web.UI/umbraco_client/Application/UmbracoApplicationActions.js b/src/Umbraco.Web.UI/umbraco_client/Application/UmbracoApplicationActions.js index 46b7a15e48..e40e59e792 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Application/UmbracoApplicationActions.js +++ b/src/Umbraco.Web.UI/umbraco_client/Application/UmbracoApplicationActions.js @@ -415,18 +415,6 @@ Umbraco.Application.Actions = function() { }, - actionDisable: function() { - /// - /// Used for users when disable is selected. - /// - - if (confirm(uiKeys['defaultdialogs_confirmdisable'] + ' "' + UmbClientMgr.mainTree().getActionNode().nodeName + '"?\n\n')) { - umbraco.presentation.webservices.legacyAjaxCalls.DisableUser(UmbClientMgr.mainTree().getActionNode().nodeId, function() { - UmbClientMgr.mainTree().reloadActionNode(); - }); - } - }, - actionMove: function() { /// diff --git a/src/Umbraco.Web/Models/Trees/DisableUser.cs b/src/Umbraco.Web/Models/Trees/DisableUser.cs new file mode 100644 index 0000000000..b28b2043ab --- /dev/null +++ b/src/Umbraco.Web/Models/Trees/DisableUser.cs @@ -0,0 +1,15 @@ +namespace Umbraco.Web.Models.Trees +{ + /// + /// Represents the disable user menu item + /// + [ActionMenuItem("umbracoMenuActions")] + public sealed class DisableUser : ActionMenuItem + { + public DisableUser() + { + Alias = "disable"; + Icon = "remove"; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/UsersTreeController.cs b/src/Umbraco.Web/Trees/UsersTreeController.cs index 2cd2ec562f..1ac0f25e27 100644 --- a/src/Umbraco.Web/Trees/UsersTreeController.cs +++ b/src/Umbraco.Web/Trees/UsersTreeController.cs @@ -87,16 +87,11 @@ namespace Umbraco.Web.Trees if (id == "0") return menu; - // Disable user - menu.Items.Add( - Services.TextService.Localize("actions", ActionDisable.Instance.Alias), - false, - new Dictionary - { - {MenuItem.JsActionKey, ActionDisable.Instance.JsFunctionName} - } - ); - + menu.Items.Add(new DisableUser() + { + Name = Services.TextService.Localize("actions", "disable") + }); + return menu; } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index cc7bc3082b..5ae1a5ec51 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -326,6 +326,7 @@ + @@ -340,7 +341,6 @@ - diff --git a/src/Umbraco.Web/_Legacy/Actions/ActionDisable.cs b/src/Umbraco.Web/_Legacy/Actions/ActionDisable.cs deleted file mode 100644 index 0e72694fe2..0000000000 --- a/src/Umbraco.Web/_Legacy/Actions/ActionDisable.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using Umbraco.Web.UI.Pages; - -namespace Umbraco.Web._Legacy.Actions -{ - /// - /// This action is invoked when a document is disabled. - /// - public class ActionDisable : IAction - { - //create singleton -#pragma warning disable 612,618 - private static readonly ActionDisable m_instance = new ActionDisable(); -#pragma warning restore 612,618 - - /// - /// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons. - /// All Umbraco assemblies should use the singleton instantiation (this.Instance) - /// When this applicatio is refactored, this constuctor should be made private. - /// - [Obsolete("Use the singleton instantiation instead of a constructor")] - public ActionDisable() { } - - public static ActionDisable Instance - { - get { return m_instance; } - } - - #region IAction Members - - public char Letter - { - get - { - return 'E'; - } - } - - public string JsFunctionName - { - get - { - return string.Format("{0}.actionDisable()", ClientTools.Scripts.GetAppActions); - } - } - - public string JsSource - { - get - { - return null; - } - } - - public string Alias - { - get - { - - return "disable"; - } - } - - public string Icon - { - get - { - - return "remove"; - } - } - - public bool ShowInNotifier - { - get - { - - return false; - } - } - public bool CanBePermissionAssigned - { - get - { - - return false; - } - } - #endregion - } -} From 9376731533e90fbe591df196634dd2222fc19408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ferreira?= Date: Fri, 1 Apr 2016 11:24:30 +0100 Subject: [PATCH 047/101] Adhering to Umbraco's Coding Standards --- .../Repositories/ContentRepository.cs | 1906 +++---- .../Interfaces/IContentRepository.cs | 144 +- .../Interfaces/IMediaRepository.cs | 58 +- .../Interfaces/IMemberRepository.cs | 116 +- .../Repositories/MediaRepository.cs | 1142 ++-- .../Repositories/MemberRepository.cs | 1488 +++--- .../Repositories/VersionableRepositoryBase.cs | 974 ++-- .../SqlSyntax/ISqlSyntaxProvider.cs | 138 +- .../SqlSyntax/MySqlSyntaxProvider.cs | 650 +-- .../SqlSyntax/SqlSyntaxProviderBase.cs | 1062 ++-- src/Umbraco.Core/Services/ContentService.cs | 4634 ++++++++--------- src/Umbraco.Core/Services/IContentService.cs | 1150 ++-- src/Umbraco.Core/Services/IMediaService.cs | 646 +-- src/Umbraco.Core/Services/IMemberService.cs | 376 +- src/Umbraco.Core/Services/MediaService.cs | 2628 +++++----- src/Umbraco.Core/Services/MemberService.cs | 2584 ++++----- .../Repositories/ContentRepositoryTest.cs | 1582 +++--- .../Repositories/MediaRepositoryTest.cs | 1086 ++-- .../UmbracoExamine/IndexInitializer.cs | 208 +- .../components/umbtable.directive.js | 142 +- .../src/common/resources/content.resource.js | 1222 ++--- .../src/common/resources/media.resource.js | 890 ++-- .../src/common/resources/member.resource.js | 442 +- .../src/views/components/umb-table.html | 120 +- .../list/list.listviewlayout.controller.js | 152 +- .../listview/listview.controller.js | 1038 ++-- src/Umbraco.Web/Editors/ContentController.cs | 1530 +++--- src/Umbraco.Web/Editors/MediaController.cs | 1342 ++--- src/Umbraco.Web/Editors/MemberController.cs | 1276 ++--- .../Filters/DisableBrowserCacheAttribute.cs | 108 +- src/UmbracoExamine/UmbracoMemberIndexer.cs | 344 +- 31 files changed, 15589 insertions(+), 15589 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 30887281f1..3ef1df3bc5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -26,957 +26,957 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - /// - /// Represents a repository for doing CRUD operations for - /// - internal class ContentRepository : RecycleBinRepository, IContentRepository - { - private readonly IContentTypeRepository _contentTypeRepository; - private readonly ITemplateRepository _templateRepository; - private readonly ITagRepository _tagRepository; - private readonly CacheHelper _cacheHelper; - private readonly ContentPreviewRepository _contentPreviewRepository; - private readonly ContentXmlRepository _contentXmlRepository; - - public ContentRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider syntaxProvider, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection) - : base(work, cacheHelper, logger, syntaxProvider, contentSection) - { - if (contentTypeRepository == null) throw new ArgumentNullException("contentTypeRepository"); - if (templateRepository == null) throw new ArgumentNullException("templateRepository"); - if (tagRepository == null) throw new ArgumentNullException("tagRepository"); - _contentTypeRepository = contentTypeRepository; - _templateRepository = templateRepository; - _tagRepository = tagRepository; - _cacheHelper = cacheHelper; - _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, syntaxProvider); - _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, syntaxProvider); - - EnsureUniqueNaming = true; - } - - public bool EnsureUniqueNaming { get; set; } - - #region Overrides of RepositoryBase - - protected override IContent PerformGet(int id) - { - var sql = GetBaseQuery(false) - .Where(GetBaseWhereClause(), new { Id = id }) - .Where(x => x.Newest) - .OrderByDescending(x => x.VersionDate); - - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - return null; - - var content = CreateContentFromDto(dto, dto.ContentVersionDto.VersionId, sql); - - return content; - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - var sql = GetBaseQuery(false); - if (ids.Any()) - { - sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); - } - - //we only want the newest ones with this method - sql.Where(x => x.Newest); - - return ProcessQuery(sql); - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate() - .Where(x => x.Newest) - .OrderByDescending(x => x.VersionDate) - .OrderBy(x => x.SortOrder); - - return ProcessQuery(sql); - } - - #endregion - - #region Overrides of PetaPocoRepositoryBase - - protected override Sql GetBaseQuery(bool isCount) - { - var sqlx = string.Format("LEFT OUTER JOIN {0} {1} ON ({1}.{2}={0}.{2} AND {1}.{3}=1)", - SqlSyntax.GetQuotedTableName("cmsDocument"), - SqlSyntax.GetQuotedTableName("cmsDocument2"), - SqlSyntax.GetQuotedColumnName("nodeId"), - SqlSyntax.GetQuotedColumnName("published")); - - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From() - .InnerJoin() - .On(left => left.VersionId, right => right.VersionId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - - // cannot do this because PetaPoco does not know how to alias the table - //.LeftOuterJoin() - //.On(left => left.NodeId, right => right.NodeId) - // so have to rely on writing our own SQL - .Append(sqlx/*, new { @published = true }*/) - - .Where(x => x.NodeObjectType == NodeObjectTypeId); - return sql; - } - - protected override string GetBaseWhereClause() - { - return "umbracoNode.id = @Id"; - } - - protected override IEnumerable GetDeleteClauses() - { - var list = new List - { - "DELETE FROM cmsTask WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", - "DELETE FROM umbracoRelation WHERE parentId = @Id", - "DELETE FROM umbracoRelation WHERE childId = @Id", - "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", - "DELETE FROM umbracoDomains WHERE domainRootStructureID = @Id", - "DELETE FROM cmsDocument WHERE nodeId = @Id", - "DELETE FROM cmsPropertyData WHERE contentNodeId = @Id", - "DELETE FROM cmsPreviewXml WHERE nodeId = @Id", - "DELETE FROM cmsContentVersion WHERE ContentId = @Id", - "DELETE FROM cmsContentXml WHERE nodeId = @Id", - "DELETE FROM cmsContent WHERE nodeId = @Id", - "DELETE FROM umbracoAccess WHERE nodeId = @Id", - "DELETE FROM umbracoNode WHERE id = @Id" - }; - return list; - } - - protected override Guid NodeObjectTypeId - { - get { return 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 = new 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 = new 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.Builder.Where(x => x.Published == true); - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); - } - else - { - foreach (var contentTypeId in contentTypeIds) - { - //copy local - var id = contentTypeId; - var query = Query.Builder.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, Transaction 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, - new Tuple("cmsDocument", "nodeId"), - ProcessQuery, "Path", Direction.Ascending, orderBySystemField: 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(xmlItems, tr); - - processed += xmlItems.Length; - - pageIndex++; - } while (processed < total); - } - - public override IContent GetByVersion(Guid versionId) - { - var sql = GetBaseQuery(false); - sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(x => x.VersionDate); - - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - return null; - - var content = CreateContentFromDto(dto, versionId, sql); - - return content; - } - - public override void DeleteVersion(Guid versionId) - { - var sql = new Sql() - .Select("*") - .From() - .InnerJoin().On(left => left.VersionId, right => right.VersionId) - .Where(x => x.VersionId == versionId) - .Where(x => x.Newest != true); - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) return; - - using (var transaction = Database.GetTransaction()) - { - PerformDeleteVersion(dto.NodeId, versionId); - - transaction.Complete(); - } - } - - public override void DeleteVersions(int id, DateTime versionDate) - { - var sql = new Sql() - .Select("*") - .From() - .InnerJoin().On(left => left.VersionId, right => right.VersionId) - .Where(x => x.NodeId == id) - .Where(x => x.VersionDate < versionDate) - .Where(x => x.Newest != true); - var list = Database.Fetch(sql); - if (list.Any() == false) return; - - using (var transaction = Database.GetTransaction()) - { - foreach (var dto in list) - { - PerformDeleteVersion(id, dto.VersionId); - } - - transaction.Complete(); - } - } - - protected override void PerformDeleteVersion(int id, Guid versionId) - { - Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - } - - #endregion - - #region Unit of Work Implementation - - protected override void PersistDeletedItem(IContent entity) - { - //We need to clear out all access rules but we need to do this in a manual way since - // nothing in that table is joined to a content id - var subQuery = new Sql() - .Select("umbracoAccessRule.accessId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.AccessId, right => right.Id) - .Where(dto => dto.NodeId == entity.Id); - Database.Execute(SqlSyntax.GetDeleteSubquery("umbracoAccessRule", "accessId", subQuery)); - - //now let the normal delete clauses take care of everything else - base.PersistDeletedItem(entity); - } - - protected override void PersistNewItem(IContent entity) - { - ((Content)entity).AddingEntity(); - - //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); - - //Ensure that strings don't contain characters that are invalid in XML - entity.SanitizeEntityPropertiesForXmlStorage(); - - var factory = new ContentFactory(NodeObjectTypeId, entity.Id); - var dto = factory.BuildDto(entity); - - //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 level = parent.Level + 1; - var maxSortOrder = Database.ExecuteScalar( - "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", - new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); - var sortOrder = maxSortOrder + 1; - - //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); - - //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; - - //Assign the same permissions to it as the parent node - // http://issues.umbraco.org/issue/U4-2161 - var permissionsRepo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - var parentPermissions = permissionsRepo.GetPermissionsForEntity(entity.ParentId).ToArray(); - //if there are parent permissions then assign them, otherwise leave null and permissions will become the - // user's default permissions. - if (parentPermissions.Any()) - { - var userPermissions = ( - from perm in parentPermissions - from p in perm.AssignedPermissions - select new EntityPermissionSet.UserPermission(perm.UserId, p)).ToList(); - - permissionsRepo.ReplaceEntityPermissions(new EntityPermissionSet(entity.Id, userPermissions)); - //flag the entity's permissions changed flag so we can track those changes. - //Currently only used for the cache refreshers to detect if we should refresh all user permissions cache. - ((Content)entity).PermissionsChanged = true; - } - - //Create the Content specific data - cmsContent - var contentDto = dto.ContentVersionDto.ContentDto; - contentDto.NodeId = nodeDto.NodeId; - Database.Insert(contentDto); - - //Create the first version - cmsContentVersion - //Assumes a new Version guid and Version date (modified date) has been set - var contentVersionDto = dto.ContentVersionDto; - contentVersionDto.NodeId = nodeDto.NodeId; - Database.Insert(contentVersionDto); - - //Create the Document specific data for this version - cmsDocument - //Assumes a new Version guid has been generated - dto.NodeId = nodeDto.NodeId; - Database.Insert(dto); - - //Create the PropertyData for this version - cmsPropertyData - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); - var keyDictionary = new Dictionary(); - - //Add Properties - foreach (var propertyDataDto in propertyDataDtos) - { - var primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } - - //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); - } - - // published => update published version infos, else leave it blank - if (entity.Published) - { - dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto - { - VersionId = dto.VersionId, - Newest = true, - NodeId = dto.NodeId, - Published = true - }; - ((Content)entity).PublishedVersionGuid = dto.VersionId; - } - - entity.ResetDirtyProperties(); - } - - protected override void PersistUpdatedItem(IContent entity) - { - var publishedState = ((Content)entity).PublishedState; - - //check if we need to make any database changes at all - if (entity.RequiresSaving(publishedState) == false) - { - entity.ResetDirtyProperties(); - return; - } - - //check if we need to create a new version - bool shouldCreateNewVersion = entity.ShouldCreateNewVersion(publishedState); - if (shouldCreateNewVersion) - { - //Updates Modified date and Version Guid - ((Content)entity).UpdatingEntity(); - } - else - { - entity.UpdateDate = DateTime.Now; - } - - //Ensure unique name on the same level - entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id); - - //Ensure that strings don't contain characters that are invalid in XML - entity.SanitizeEntityPropertiesForXmlStorage(); - - //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 }); - 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; - - //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. - // Gonna just leave it as is for now, and not re-propogate permissions. - } - - 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 }); - factory.SetPrimaryKey(contentDto.PrimaryKey); - var dto = factory.BuildDto(entity); - - //Updates the (base) node data - umbracoNode - var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; - var o = Database.Update(nodeDto); - - //Only update this DTO if the contentType has actually changed - if (contentDto.ContentTypeId != entity.ContentTypeId) - { - //Create the Content specific data - cmsContent - var newContentDto = dto.ContentVersionDto.ContentDto; - 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; - } - - //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); - } - - var contentVersionDto = dto.ContentVersionDto; - if (shouldCreateNewVersion) - { - //Create a new version - cmsContentVersion - //Assumes a new Version guid and Version date (modified date) has been set - Database.Insert(contentVersionDto); - //Create the Document specific data for this version - cmsDocument - //Assumes a new Version guid has been generated - Database.Insert(dto); - } - 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 }); - contentVersionDto.Id = contentVerDto.Id; - - Database.Update(contentVersionDto); - Database.Update(dto); - } - - //Create the PropertyData for this version - cmsPropertyData - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); - var keyDictionary = new Dictionary(); - - //Add Properties - foreach (var propertyDataDto in propertyDataDtos) - { - if (shouldCreateNewVersion == false && propertyDataDto.Id > 0) - { - Database.Update(propertyDataDto); - } - else - { - int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } - } - - //Update Properties with its newly set Id - if (keyDictionary.Any()) - { - foreach (var property in entity.Properties) - { - if (keyDictionary.ContainsKey(property.PropertyTypeId) == false) continue; - - property.Id = keyDictionary[property.PropertyTypeId]; - } - } - - //lastly, check if we are a newly published version and then update the tags table - if (publishedStateChanged && entity.Published) - { - 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); - } - - // published => update published version infos, - // else if unpublished then clear published version infos - if (entity.Published) - { - dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto - { - VersionId = dto.VersionId, - Newest = true, - NodeId = dto.NodeId, - Published = true - }; - ((Content)entity).PublishedVersionGuid = dto.VersionId; - } - else if (publishedStateChanged) - { - dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto - { - VersionId = default(Guid), - Newest = false, - NodeId = dto.NodeId, - Published = false - }; - ((Content)entity).PublishedVersionGuid = default(Guid); - } - - entity.ResetDirtyProperties(); - } - - - #endregion - - #region Implementation of IContentRepository - - 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 - - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate() - .Where(x => x.Published) - .OrderBy(x => x.Level, SqlSyntax) - .OrderBy(x => x.SortOrder, SqlSyntax); - - //NOTE: This doesn't allow properties to be part of the query - var dtos = Database.Fetch(sql); - - 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) - 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); - } - } - } - - public int CountPublished() - { - var sql = GetBaseQuery(true).Where(x => x.Trashed == false) - .Where(x => x.Published == true); - return Database.ExecuteScalar(sql); - } - - public void ReplaceContentPermissions(EntityPermissionSet permissionSet) - { - var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - repo.ReplaceEntityPermissions(permissionSet); - } - - public void ClearPublished(IContent content) - { - // race cond! - var documentDtos = Database.Fetch("WHERE nodeId=@id AND published=@published", new { id = content.Id, published = true }); - foreach (var documentDto in documentDtos) - { - documentDto.Published = false; - Database.Update(documentDto); - } - } - - /// - /// Assigns a single permission to the current content item for the specified user ids - /// - /// - /// - /// - public void AssignEntityPermission(IContent entity, char permission, IEnumerable userIds) - { - var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - repo.AssignEntityPermission(entity, permission, userIds); - } - - public IEnumerable GetPermissionsForEntity(int entityId) - { - var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); - 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 - /// - /// Query to excute - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// An Enumerable list of objects - public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") - { - - //NOTE: This uses the GetBaseQuery method but that does not take into account the required 'newest' field which is - // what we always require for a paged result, so we'll ensure it's included in the filter - - var args = new List(); - var sbWhere = new StringBuilder("AND (cmsDocument.newest = 1)"); - - if (filter.IsNullOrWhiteSpace() == false) - { - sbWhere.Append(" AND (cmsDocument." + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ")"); - args.Add("%" + filter + "%"); - } - - Func> filterCallback = () => new Tuple(sbWhere.ToString(), args.ToArray()); - - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - new Tuple("cmsDocument", "nodeId"), - ProcessQuery, orderBy, orderDirection, orderBySystemField, - filterCallback); - - } - - #endregion - - #region IRecycleBinRepository members - - protected override int RecycleBinId - { - get { return Constants.System.RecycleBinContent; } - } - - #endregion - - protected override string GetDatabaseFieldNameForOrderBy(string orderBy) - { - //Some custom ones - switch (orderBy.ToUpperInvariant()) - { - case "NAME": - return "cmsDocument.text"; - case "UPDATER": - //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter - return "cmsDocument.documentUser"; - } - - return base.GetDatabaseFieldNameForOrderBy(orderBy); - } - - private IEnumerable ProcessQuery(Sql sql) - { - //NOTE: This doesn't allow properties to be part of the query - var dtos = Database.Fetch(sql); - - //nothing found - if (dtos.Any() == false) return Enumerable.Empty(); - - //content types - //NOTE: This should be ok for an SQL 'IN' statement, there shouldn't be an insane amount of content types - var contentTypes = _contentTypeRepository.GetAll(dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray()) - .ToArray(); - - - var ids = dtos - .Where(dto => dto.TemplateId.HasValue && dto.TemplateId.Value > 0) - .Select(x => x.TemplateId.Value).ToArray(); - - //NOTE: This should be ok for an SQL 'IN' statement, there shouldn't be an insane amount of content types - var templates = ids.Length == 0 ? Enumerable.Empty() : _templateRepository.GetAll(ids).ToArray(); - - var dtosWithContentTypes = dtos - //This select into and null check are required because we don't have a foreign damn key on the contentType column - // http://issues.umbraco.org/issue/U4-5503 - .Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentVersionDto.ContentDto.ContentTypeId) }) - .Where(x => x.contentType != null) - .ToArray(); - - //Go get the property data for each document - var docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition( - d.dto.NodeId, - d.dto.VersionId, - d.dto.ContentVersionDto.VersionDate, - d.dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, - d.contentType)); - - var propertyData = GetPropertyCollection(sql, docDefs); - - 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)), - propertyData[d.dto.NodeId])); - } - - /// - /// Private method to create a content object from a DocumentDto, which is used by Get and GetByVersion. - /// - /// - /// - /// - /// - /// - private IContent CreateContentFromDto(DocumentDto dto, - IContentType contentType, - ITemplate template, - Models.PropertyCollection propCollection) - { - var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId); - var content = factory.BuildEntity(dto); - - //Check if template id is set on DocumentDto, and get ITemplate if it is. - if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) - { - content.Template = template ?? _templateRepository.Get(dto.TemplateId.Value); - } - else - { - //ensure there isn't one set. - content.Template = null; - } - - content.Properties = propCollection; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)content).ResetDirtyProperties(false); - return content; - } - - /// - /// Private method to create a content object from a DocumentDto, which is used by Get and GetByVersion. - /// - /// - /// - /// - /// - private IContent CreateContentFromDto(DocumentDto dto, Guid versionId, Sql docSql) - { - var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); - - var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId); - var content = factory.BuildEntity(dto); - - //Check if template id is set on DocumentDto, and get ITemplate if it is. - if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) - { - content.Template = _templateRepository.Get(dto.TemplateId.Value); - } - - var docDef = new DocumentDefinition(dto.NodeId, versionId, content.UpdateDate, content.CreateDate, contentType); - - var properties = GetPropertyCollection(docSql, new[] { docDef }); - - content.Properties = properties[dto.NodeId]; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)content).ResetDirtyProperties(false); - return content; - } - - private string EnsureUniqueNodeName(int parentId, string nodeName, int id = 0) - { - if (EnsureUniqueNaming == false) - return nodeName; - - var sql = new Sql(); - sql.Select("*") - .From() - .Where(x => x.NodeObjectType == NodeObjectTypeId && x.ParentId == parentId && x.Text.StartsWith(nodeName)); - - int uniqueNumber = 1; - var currentName = nodeName; - - var dtos = Database.Fetch(sql); - if (dtos.Any()) - { - var results = dtos.OrderBy(x => x.Text, new SimilarNodeNameComparer()); - foreach (var dto in results) - { - if (id != 0 && id == dto.NodeId) continue; - - if (dto.Text.ToLowerInvariant().Equals(currentName.ToLowerInvariant())) - { - currentName = nodeName + string.Format(" ({0})", uniqueNumber); - uniqueNumber++; - } - } - } - - return currentName; - } - - /// - /// Dispose disposable properties - /// - /// - /// Ensure the unit of work is disposed - /// - protected override void DisposeResources() - { - _contentTypeRepository.Dispose(); - _templateRepository.Dispose(); - _tagRepository.Dispose(); - _contentPreviewRepository.Dispose(); - _contentXmlRepository.Dispose(); - } - } + /// + /// Represents a repository for doing CRUD operations for + /// + internal class ContentRepository : RecycleBinRepository, IContentRepository + { + private readonly IContentTypeRepository _contentTypeRepository; + private readonly ITemplateRepository _templateRepository; + private readonly ITagRepository _tagRepository; + private readonly CacheHelper _cacheHelper; + private readonly ContentPreviewRepository _contentPreviewRepository; + private readonly ContentXmlRepository _contentXmlRepository; + + public ContentRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider syntaxProvider, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection) + : base(work, cacheHelper, logger, syntaxProvider, contentSection) + { + if (contentTypeRepository == null) throw new ArgumentNullException("contentTypeRepository"); + if (templateRepository == null) throw new ArgumentNullException("templateRepository"); + if (tagRepository == null) throw new ArgumentNullException("tagRepository"); + _contentTypeRepository = contentTypeRepository; + _templateRepository = templateRepository; + _tagRepository = tagRepository; + _cacheHelper = cacheHelper; + _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, syntaxProvider); + _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, syntaxProvider); + + EnsureUniqueNaming = true; + } + + public bool EnsureUniqueNaming { get; set; } + + #region Overrides of RepositoryBase + + protected override IContent PerformGet(int id) + { + var sql = GetBaseQuery(false) + .Where(GetBaseWhereClause(), new { Id = id }) + .Where(x => x.Newest) + .OrderByDescending(x => x.VersionDate); + + var dto = Database.Fetch(sql).FirstOrDefault(); + + if (dto == null) + return null; + + var content = CreateContentFromDto(dto, dto.ContentVersionDto.VersionId, sql); + + return content; + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + var sql = GetBaseQuery(false); + if (ids.Any()) + { + sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); + } + + //we only want the newest ones with this method + sql.Where(x => x.Newest); + + return ProcessQuery(sql); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + var sqlClause = GetBaseQuery(false); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate() + .Where(x => x.Newest) + .OrderByDescending(x => x.VersionDate) + .OrderBy(x => x.SortOrder); + + return ProcessQuery(sql); + } + + #endregion + + #region Overrides of PetaPocoRepositoryBase + + protected override Sql GetBaseQuery(bool isCount) + { + var sqlx = string.Format("LEFT OUTER JOIN {0} {1} ON ({1}.{2}={0}.{2} AND {1}.{3}=1)", + SqlSyntax.GetQuotedTableName("cmsDocument"), + SqlSyntax.GetQuotedTableName("cmsDocument2"), + SqlSyntax.GetQuotedColumnName("nodeId"), + SqlSyntax.GetQuotedColumnName("published")); + + var sql = new Sql(); + sql.Select(isCount ? "COUNT(*)" : "*") + .From() + .InnerJoin() + .On(left => left.VersionId, right => right.VersionId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + + // cannot do this because PetaPoco does not know how to alias the table + //.LeftOuterJoin() + //.On(left => left.NodeId, right => right.NodeId) + // so have to rely on writing our own SQL + .Append(sqlx/*, new { @published = true }*/) + + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; + } + + protected override string GetBaseWhereClause() + { + return "umbracoNode.id = @Id"; + } + + protected override IEnumerable GetDeleteClauses() + { + var list = new List + { + "DELETE FROM cmsTask WHERE nodeId = @Id", + "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", + "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", + "DELETE FROM umbracoRelation WHERE parentId = @Id", + "DELETE FROM umbracoRelation WHERE childId = @Id", + "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", + "DELETE FROM umbracoDomains WHERE domainRootStructureID = @Id", + "DELETE FROM cmsDocument WHERE nodeId = @Id", + "DELETE FROM cmsPropertyData WHERE contentNodeId = @Id", + "DELETE FROM cmsPreviewXml WHERE nodeId = @Id", + "DELETE FROM cmsContentVersion WHERE ContentId = @Id", + "DELETE FROM cmsContentXml WHERE nodeId = @Id", + "DELETE FROM cmsContent WHERE nodeId = @Id", + "DELETE FROM umbracoAccess WHERE nodeId = @Id", + "DELETE FROM umbracoNode WHERE id = @Id" + }; + return list; + } + + protected override Guid NodeObjectTypeId + { + get { return 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 = new 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 = new 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.Builder.Where(x => x.Published == true); + RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); + } + else + { + foreach (var contentTypeId in contentTypeIds) + { + //copy local + var id = contentTypeId; + var query = Query.Builder.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, Transaction 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, + new Tuple("cmsDocument", "nodeId"), + ProcessQuery, "Path", Direction.Ascending, orderBySystemField: 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(xmlItems, tr); + + processed += xmlItems.Length; + + pageIndex++; + } while (processed < total); + } + + public override IContent GetByVersion(Guid versionId) + { + var sql = GetBaseQuery(false); + sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); + sql.OrderByDescending(x => x.VersionDate); + + var dto = Database.Fetch(sql).FirstOrDefault(); + + if (dto == null) + return null; + + var content = CreateContentFromDto(dto, versionId, sql); + + return content; + } + + public override void DeleteVersion(Guid versionId) + { + var sql = new Sql() + .Select("*") + .From() + .InnerJoin().On(left => left.VersionId, right => right.VersionId) + .Where(x => x.VersionId == versionId) + .Where(x => x.Newest != true); + var dto = Database.Fetch(sql).FirstOrDefault(); + + if (dto == null) return; + + using (var transaction = Database.GetTransaction()) + { + PerformDeleteVersion(dto.NodeId, versionId); + + transaction.Complete(); + } + } + + public override void DeleteVersions(int id, DateTime versionDate) + { + var sql = new Sql() + .Select("*") + .From() + .InnerJoin().On(left => left.VersionId, right => right.VersionId) + .Where(x => x.NodeId == id) + .Where(x => x.VersionDate < versionDate) + .Where(x => x.Newest != true); + var list = Database.Fetch(sql); + if (list.Any() == false) return; + + using (var transaction = Database.GetTransaction()) + { + foreach (var dto in list) + { + PerformDeleteVersion(id, dto.VersionId); + } + + transaction.Complete(); + } + } + + protected override void PerformDeleteVersion(int id, Guid versionId) + { + Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + } + + #endregion + + #region Unit of Work Implementation + + protected override void PersistDeletedItem(IContent entity) + { + //We need to clear out all access rules but we need to do this in a manual way since + // nothing in that table is joined to a content id + var subQuery = new Sql() + .Select("umbracoAccessRule.accessId") + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.AccessId, right => right.Id) + .Where(dto => dto.NodeId == entity.Id); + Database.Execute(SqlSyntax.GetDeleteSubquery("umbracoAccessRule", "accessId", subQuery)); + + //now let the normal delete clauses take care of everything else + base.PersistDeletedItem(entity); + } + + protected override void PersistNewItem(IContent entity) + { + ((Content)entity).AddingEntity(); + + //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); + + //Ensure that strings don't contain characters that are invalid in XML + entity.SanitizeEntityPropertiesForXmlStorage(); + + var factory = new ContentFactory(NodeObjectTypeId, entity.Id); + var dto = factory.BuildDto(entity); + + //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 level = parent.Level + 1; + var maxSortOrder = Database.ExecuteScalar( + "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", + new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); + var sortOrder = maxSortOrder + 1; + + //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); + + //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; + + //Assign the same permissions to it as the parent node + // http://issues.umbraco.org/issue/U4-2161 + var permissionsRepo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); + var parentPermissions = permissionsRepo.GetPermissionsForEntity(entity.ParentId).ToArray(); + //if there are parent permissions then assign them, otherwise leave null and permissions will become the + // user's default permissions. + if (parentPermissions.Any()) + { + var userPermissions = ( + from perm in parentPermissions + from p in perm.AssignedPermissions + select new EntityPermissionSet.UserPermission(perm.UserId, p)).ToList(); + + permissionsRepo.ReplaceEntityPermissions(new EntityPermissionSet(entity.Id, userPermissions)); + //flag the entity's permissions changed flag so we can track those changes. + //Currently only used for the cache refreshers to detect if we should refresh all user permissions cache. + ((Content)entity).PermissionsChanged = true; + } + + //Create the Content specific data - cmsContent + var contentDto = dto.ContentVersionDto.ContentDto; + contentDto.NodeId = nodeDto.NodeId; + Database.Insert(contentDto); + + //Create the first version - cmsContentVersion + //Assumes a new Version guid and Version date (modified date) has been set + var contentVersionDto = dto.ContentVersionDto; + contentVersionDto.NodeId = nodeDto.NodeId; + Database.Insert(contentVersionDto); + + //Create the Document specific data for this version - cmsDocument + //Assumes a new Version guid has been generated + dto.NodeId = nodeDto.NodeId; + Database.Insert(dto); + + //Create the PropertyData for this version - cmsPropertyData + var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); + var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); + var keyDictionary = new Dictionary(); + + //Add Properties + foreach (var propertyDataDto in propertyDataDtos) + { + var primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); + keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); + } + + //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); + } + + // published => update published version infos, else leave it blank + if (entity.Published) + { + dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto + { + VersionId = dto.VersionId, + Newest = true, + NodeId = dto.NodeId, + Published = true + }; + ((Content)entity).PublishedVersionGuid = dto.VersionId; + } + + entity.ResetDirtyProperties(); + } + + protected override void PersistUpdatedItem(IContent entity) + { + var publishedState = ((Content)entity).PublishedState; + + //check if we need to make any database changes at all + if (entity.RequiresSaving(publishedState) == false) + { + entity.ResetDirtyProperties(); + return; + } + + //check if we need to create a new version + bool shouldCreateNewVersion = entity.ShouldCreateNewVersion(publishedState); + if (shouldCreateNewVersion) + { + //Updates Modified date and Version Guid + ((Content)entity).UpdatingEntity(); + } + else + { + entity.UpdateDate = DateTime.Now; + } + + //Ensure unique name on the same level + entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id); + + //Ensure that strings don't contain characters that are invalid in XML + entity.SanitizeEntityPropertiesForXmlStorage(); + + //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 }); + 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; + + //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. + // Gonna just leave it as is for now, and not re-propogate permissions. + } + + 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 }); + factory.SetPrimaryKey(contentDto.PrimaryKey); + var dto = factory.BuildDto(entity); + + //Updates the (base) node data - umbracoNode + var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; + var o = Database.Update(nodeDto); + + //Only update this DTO if the contentType has actually changed + if (contentDto.ContentTypeId != entity.ContentTypeId) + { + //Create the Content specific data - cmsContent + var newContentDto = dto.ContentVersionDto.ContentDto; + 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; + } + + //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); + } + + var contentVersionDto = dto.ContentVersionDto; + if (shouldCreateNewVersion) + { + //Create a new version - cmsContentVersion + //Assumes a new Version guid and Version date (modified date) has been set + Database.Insert(contentVersionDto); + //Create the Document specific data for this version - cmsDocument + //Assumes a new Version guid has been generated + Database.Insert(dto); + } + 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 }); + contentVersionDto.Id = contentVerDto.Id; + + Database.Update(contentVersionDto); + Database.Update(dto); + } + + //Create the PropertyData for this version - cmsPropertyData + var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); + var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); + var keyDictionary = new Dictionary(); + + //Add Properties + foreach (var propertyDataDto in propertyDataDtos) + { + if (shouldCreateNewVersion == false && propertyDataDto.Id > 0) + { + Database.Update(propertyDataDto); + } + else + { + int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); + keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); + } + } + + //Update Properties with its newly set Id + if (keyDictionary.Any()) + { + foreach (var property in entity.Properties) + { + if (keyDictionary.ContainsKey(property.PropertyTypeId) == false) continue; + + property.Id = keyDictionary[property.PropertyTypeId]; + } + } + + //lastly, check if we are a newly published version and then update the tags table + if (publishedStateChanged && entity.Published) + { + 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); + } + + // published => update published version infos, + // else if unpublished then clear published version infos + if (entity.Published) + { + dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto + { + VersionId = dto.VersionId, + Newest = true, + NodeId = dto.NodeId, + Published = true + }; + ((Content)entity).PublishedVersionGuid = dto.VersionId; + } + else if (publishedStateChanged) + { + dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto + { + VersionId = default(Guid), + Newest = false, + NodeId = dto.NodeId, + Published = false + }; + ((Content)entity).PublishedVersionGuid = default(Guid); + } + + entity.ResetDirtyProperties(); + } + + + #endregion + + #region Implementation of IContentRepository + + 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 + + var sqlClause = GetBaseQuery(false); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate() + .Where(x => x.Published) + .OrderBy(x => x.Level, SqlSyntax) + .OrderBy(x => x.SortOrder, SqlSyntax); + + //NOTE: This doesn't allow properties to be part of the query + var dtos = Database.Fetch(sql); + + 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) + 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); + } + } + } + + public int CountPublished() + { + var sql = GetBaseQuery(true).Where(x => x.Trashed == false) + .Where(x => x.Published == true); + return Database.ExecuteScalar(sql); + } + + public void ReplaceContentPermissions(EntityPermissionSet permissionSet) + { + var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); + repo.ReplaceEntityPermissions(permissionSet); + } + + public void ClearPublished(IContent content) + { + // race cond! + var documentDtos = Database.Fetch("WHERE nodeId=@id AND published=@published", new { id = content.Id, published = true }); + foreach (var documentDto in documentDtos) + { + documentDto.Published = false; + Database.Update(documentDto); + } + } + + /// + /// Assigns a single permission to the current content item for the specified user ids + /// + /// + /// + /// + public void AssignEntityPermission(IContent entity, char permission, IEnumerable userIds) + { + var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); + repo.AssignEntityPermission(entity, permission, userIds); + } + + public IEnumerable GetPermissionsForEntity(int entityId) + { + var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); + 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 + /// + /// Query to excute + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") + { + + //NOTE: This uses the GetBaseQuery method but that does not take into account the required 'newest' field which is + // what we always require for a paged result, so we'll ensure it's included in the filter + + var args = new List(); + var sbWhere = new StringBuilder("AND (cmsDocument.newest = 1)"); + + if (filter.IsNullOrWhiteSpace() == false) + { + sbWhere.Append(" AND (cmsDocument." + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ")"); + args.Add("%" + filter + "%"); + } + + Func> filterCallback = () => new Tuple(sbWhere.ToString(), args.ToArray()); + + return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, + new Tuple("cmsDocument", "nodeId"), + ProcessQuery, orderBy, orderDirection, orderBySystemField, + filterCallback); + + } + + #endregion + + #region IRecycleBinRepository members + + protected override int RecycleBinId + { + get { return Constants.System.RecycleBinContent; } + } + + #endregion + + protected override string GetDatabaseFieldNameForOrderBy(string orderBy) + { + //Some custom ones + switch (orderBy.ToUpperInvariant()) + { + case "NAME": + return "cmsDocument.text"; + case "UPDATER": + //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter + return "cmsDocument.documentUser"; + } + + return base.GetDatabaseFieldNameForOrderBy(orderBy); + } + + private IEnumerable ProcessQuery(Sql sql) + { + //NOTE: This doesn't allow properties to be part of the query + var dtos = Database.Fetch(sql); + + //nothing found + if (dtos.Any() == false) return Enumerable.Empty(); + + //content types + //NOTE: This should be ok for an SQL 'IN' statement, there shouldn't be an insane amount of content types + var contentTypes = _contentTypeRepository.GetAll(dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray()) + .ToArray(); + + + var ids = dtos + .Where(dto => dto.TemplateId.HasValue && dto.TemplateId.Value > 0) + .Select(x => x.TemplateId.Value).ToArray(); + + //NOTE: This should be ok for an SQL 'IN' statement, there shouldn't be an insane amount of content types + var templates = ids.Length == 0 ? Enumerable.Empty() : _templateRepository.GetAll(ids).ToArray(); + + var dtosWithContentTypes = dtos + //This select into and null check are required because we don't have a foreign damn key on the contentType column + // http://issues.umbraco.org/issue/U4-5503 + .Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentVersionDto.ContentDto.ContentTypeId) }) + .Where(x => x.contentType != null) + .ToArray(); + + //Go get the property data for each document + var docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition( + d.dto.NodeId, + d.dto.VersionId, + d.dto.ContentVersionDto.VersionDate, + d.dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, + d.contentType)); + + var propertyData = GetPropertyCollection(sql, docDefs); + + 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)), + propertyData[d.dto.NodeId])); + } + + /// + /// Private method to create a content object from a DocumentDto, which is used by Get and GetByVersion. + /// + /// + /// + /// + /// + /// + private IContent CreateContentFromDto(DocumentDto dto, + IContentType contentType, + ITemplate template, + Models.PropertyCollection propCollection) + { + var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId); + var content = factory.BuildEntity(dto); + + //Check if template id is set on DocumentDto, and get ITemplate if it is. + if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) + { + content.Template = template ?? _templateRepository.Get(dto.TemplateId.Value); + } + else + { + //ensure there isn't one set. + content.Template = null; + } + + content.Properties = propCollection; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)content).ResetDirtyProperties(false); + return content; + } + + /// + /// Private method to create a content object from a DocumentDto, which is used by Get and GetByVersion. + /// + /// + /// + /// + /// + private IContent CreateContentFromDto(DocumentDto dto, Guid versionId, Sql docSql) + { + var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); + + var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId); + var content = factory.BuildEntity(dto); + + //Check if template id is set on DocumentDto, and get ITemplate if it is. + if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) + { + content.Template = _templateRepository.Get(dto.TemplateId.Value); + } + + var docDef = new DocumentDefinition(dto.NodeId, versionId, content.UpdateDate, content.CreateDate, contentType); + + var properties = GetPropertyCollection(docSql, new[] { docDef }); + + content.Properties = properties[dto.NodeId]; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)content).ResetDirtyProperties(false); + return content; + } + + private string EnsureUniqueNodeName(int parentId, string nodeName, int id = 0) + { + if (EnsureUniqueNaming == false) + return nodeName; + + var sql = new Sql(); + sql.Select("*") + .From() + .Where(x => x.NodeObjectType == NodeObjectTypeId && x.ParentId == parentId && x.Text.StartsWith(nodeName)); + + int uniqueNumber = 1; + var currentName = nodeName; + + var dtos = Database.Fetch(sql); + if (dtos.Any()) + { + var results = dtos.OrderBy(x => x.Text, new SimilarNodeNameComparer()); + foreach (var dto in results) + { + if (id != 0 && id == dto.NodeId) continue; + + if (dto.Text.ToLowerInvariant().Equals(currentName.ToLowerInvariant())) + { + currentName = nodeName + string.Format(" ({0})", uniqueNumber); + uniqueNumber++; + } + } + } + + return currentName; + } + + /// + /// Dispose disposable properties + /// + /// + /// Ensure the unit of work is disposed + /// + protected override void DisposeResources() + { + _contentTypeRepository.Dispose(); + _templateRepository.Dispose(); + _tagRepository.Dispose(); + _contentPreviewRepository.Dispose(); + _contentXmlRepository.Dispose(); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index 0662581b15..ab176df6d0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -9,85 +9,85 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IContentRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository - { - /// - /// Get the count of published items - /// - /// - /// - /// We require this on the repo because the IQuery{IContent} cannot supply the 'newest' parameter - /// - int CountPublished(); + public interface IContentRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository + { + /// + /// Get the count of published items + /// + /// + /// + /// We require this on the repo because the IQuery{IContent} cannot supply the 'newest' parameter + /// + int CountPublished(); - /// - /// 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. - /// - /// - void ReplaceContentPermissions(EntityPermissionSet permissionSet); + /// + /// 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. + /// + /// + void ReplaceContentPermissions(EntityPermissionSet permissionSet); - /// - /// Clears the published flag for a content. - /// - /// - void ClearPublished(IContent content); + /// + /// Clears the published flag for a content. + /// + /// + void ClearPublished(IContent content); - /// - /// Gets all published Content by the specified query - /// - /// Query to execute against published versions - /// An enumerable list of - IEnumerable GetByPublishedVersion(IQuery query); + /// + /// Gets all published Content by the specified query + /// + /// Query to execute against published versions + /// An enumerable list of + IEnumerable GetByPublishedVersion(IQuery query); - /// - /// Assigns a single permission to the current content item for the specified user ids - /// - /// - /// - /// - void AssignEntityPermission(IContent entity, char permission, IEnumerable userIds); + /// + /// Assigns a single permission to the current content item for the specified user ids + /// + /// + /// + /// + void AssignEntityPermission(IContent entity, char permission, IEnumerable userIds); - /// - /// Gets the list of permissions for the content item - /// - /// - /// - IEnumerable GetPermissionsForEntity(int entityId); + /// + /// Gets the list of permissions for the content item + /// + /// + /// + IEnumerable GetPermissionsForEntity(int entityId); - /// - /// Used to add/update published xml for the content item - /// - /// - /// - void AddOrUpdateContentXml(IContent content, Func xml); + /// + /// Used to add/update published xml for the content item + /// + /// + /// + void AddOrUpdateContentXml(IContent content, Func xml); - /// - /// Used to remove the content xml for a content item - /// - /// - void DeleteContentXml(IContent content); + /// + /// Used to remove the content xml for a content item + /// + /// + void DeleteContentXml(IContent content); - /// - /// Used to add/update preview xml for the content item - /// - /// - /// - void AddOrUpdatePreviewXml(IContent content, Func xml); + /// + /// Used to add/update preview xml for the content item + /// + /// + /// + void AddOrUpdatePreviewXml(IContent content, Func xml); - /// - /// Gets paged content results - /// - /// Query to excute - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// An Enumerable list of objects - IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); - } + /// + /// Gets paged content results + /// + /// Query to excute + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs index 358a099ea0..907f9b62c5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs @@ -7,36 +7,36 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IMediaRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository - { + public interface IMediaRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository + { - /// - /// Used to add/update published xml for the media item - /// - /// - /// - void AddOrUpdateContentXml(IMedia content, Func xml); + /// + /// Used to add/update published xml for the media item + /// + /// + /// + void AddOrUpdateContentXml(IMedia content, Func xml); - /// - /// Used to add/update preview xml for the content item - /// - /// - /// - void AddOrUpdatePreviewXml(IMedia content, Func xml); + /// + /// Used to add/update preview xml for the content item + /// + /// + /// + void AddOrUpdatePreviewXml(IMedia content, Func xml); - /// - /// Gets paged media results - /// - /// Query to excute - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// An Enumerable list of objects - IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); - } + /// + /// Gets paged media results + /// + /// Query to excute + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs index c59bf5d192..a24116f0e2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs @@ -9,70 +9,70 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IMemberRepository : IRepositoryVersionable, IDeleteMediaFilesRepository - { - /// - /// Finds members in a given role - /// - /// - /// - /// - /// - IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + public interface IMemberRepository : IRepositoryVersionable, IDeleteMediaFilesRepository + { + /// + /// Finds members in a given role + /// + /// + /// + /// + /// + IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); - /// - /// Get all members in a specific group - /// - /// - /// - IEnumerable GetByMemberGroup(string groupName); + /// + /// Get all members in a specific group + /// + /// + /// + IEnumerable GetByMemberGroup(string groupName); - /// - /// Checks if a member with the username exists - /// - /// - /// - bool Exists(string username); + /// + /// Checks if a member with the username exists + /// + /// + /// + bool Exists(string username); - /// - /// Gets the count of items based on a complex query - /// - /// - /// - int GetCountByQuery(IQuery query); + /// + /// Gets the count of items based on a complex query + /// + /// + /// + int GetCountByQuery(IQuery query); - /// - /// Gets paged member results - /// - /// The query. - /// Index of the page. - /// Size of the page. - /// The total records. - /// The order by column - /// The order direction. - /// Flag to indicate when ordering by system field - /// Search query - /// - IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); + /// + /// Gets paged member results + /// + /// The query. + /// Index of the page. + /// Size of the page. + /// The total records. + /// The order by column + /// The order direction. + /// Flag to indicate when ordering by system field + /// Search query + /// + IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); - //IEnumerable GetPagedResultsByQuery( - // Sql sql, int pageIndex, int pageSize, out int totalRecords, - // Func, int[]> resolveIds); + //IEnumerable GetPagedResultsByQuery( + // Sql sql, int pageIndex, int pageSize, out int totalRecords, + // Func, int[]> resolveIds); - /// - /// Used to add/update published xml for the media item - /// - /// - /// - void AddOrUpdateContentXml(IMember content, Func xml); + /// + /// Used to add/update published xml for the media item + /// + /// + /// + void AddOrUpdateContentXml(IMember content, Func xml); - /// - /// Used to add/update preview xml for the content item - /// - /// - /// - void AddOrUpdatePreviewXml(IMember content, Func xml); + /// + /// Used to add/update preview xml for the content item + /// + /// + /// + void AddOrUpdatePreviewXml(IMember content, Func xml); - } + } } \ 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 d425f0afda..bb54eebeec 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -22,575 +22,575 @@ using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { - /// - /// Represents a repository for doing CRUD operations for - /// - internal class MediaRepository : RecycleBinRepository, IMediaRepository - { - private readonly IMediaTypeRepository _mediaTypeRepository; - private readonly ITagRepository _tagRepository; - private readonly ContentXmlRepository _contentXmlRepository; - private readonly ContentPreviewRepository _contentPreviewRepository; - - public MediaRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, IContentSection contentSection) - : base(work, cache, logger, sqlSyntax, contentSection) - { - if (mediaTypeRepository == null) throw new ArgumentNullException("mediaTypeRepository"); - if (tagRepository == null) throw new ArgumentNullException("tagRepository"); - _mediaTypeRepository = mediaTypeRepository; - _tagRepository = tagRepository; - _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); - _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); - EnsureUniqueNaming = contentSection.EnsureUniqueNaming; - } - - public bool EnsureUniqueNaming { get; private set; } - - #region Overrides of RepositoryBase - - protected override IMedia PerformGet(int id) - { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(x => x.VersionDate); - - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - return null; - - var content = CreateMediaFromDto(dto, dto.VersionId, sql); - - return content; - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - var sql = GetBaseQuery(false); - if (ids.Any()) - { - sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); - } - - return ProcessQuery(sql); - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate() - .OrderBy(x => x.SortOrder); - - return ProcessQuery(sql); - } - - #endregion - - #region Overrides of PetaPocoRepositoryBase - - protected override Sql GetBaseQuery(bool isCount) - { - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId); - return sql; - } - - protected override string GetBaseWhereClause() - { - return "umbracoNode.id = @Id"; - } - - protected override IEnumerable GetDeleteClauses() - { - var list = new List - { - "DELETE FROM cmsTask WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", - "DELETE FROM umbracoRelation WHERE parentId = @Id", - "DELETE FROM umbracoRelation WHERE childId = @Id", - "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", - "DELETE FROM cmsDocument WHERE nodeId = @Id", - "DELETE FROM cmsPropertyData WHERE contentNodeId = @Id", - "DELETE FROM cmsPreviewXml WHERE nodeId = @Id", - "DELETE FROM cmsContentVersion WHERE ContentId = @Id", - "DELETE FROM cmsContentXml WHERE nodeId = @Id", - "DELETE FROM cmsContent WHERE nodeId = @Id", - "DELETE FROM umbracoNode WHERE id = @Id" - }; - return list; - } - - protected override Guid NodeObjectTypeId - { - get { return new Guid(Constants.ObjectTypes.Media); } - } - - #endregion - - #region Overrides of VersionableRepositoryBase - - public override IMedia GetByVersion(Guid versionId) - { - var sql = GetBaseQuery(false); - sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(x => x.VersionDate); - - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - return null; - - var mediaType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); - - var factory = new MediaFactory(mediaType, NodeObjectTypeId, dto.NodeId); - var media = factory.BuildEntity(dto); - - var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.VersionId, media.UpdateDate, media.CreateDate, mediaType) }); - - media.Properties = properties[dto.NodeId]; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)media).ResetDirtyProperties(false); - 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 = new 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 = new 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) - { - var query = Query.Builder; - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); - } - else - { - foreach (var contentTypeId in contentTypeIds) - { - //copy local - var id = contentTypeId; - var query = Query.Builder.Where(x => x.ContentTypeId == id && x.Trashed == false); - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); - } - } - - tr.Complete(); - } - } - - private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, Transaction 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, orderBySystemField: 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(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)); - } - - protected override void PerformDeleteVersion(int id, Guid versionId) - { - Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); - } - - #endregion - - #region Unit of Work Implementation - - protected override void PersistNewItem(IMedia entity) - { - ((Models.Media)entity).AddingEntity(); - - //Ensure unique name on the same level - entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name); - - //Ensure that strings don't contain characters that are invalid in XML - entity.SanitizeEntityPropertiesForXmlStorage(); - - var factory = new MediaFactory(NodeObjectTypeId, entity.Id); - var dto = factory.BuildDto(entity); - - //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 level = parent.Level + 1; - var maxSortOrder = Database.ExecuteScalar( - "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", - new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); - var sortOrder = maxSortOrder + 1; - - //Create the (base) node data - umbracoNode - var nodeDto = dto.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); - - //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; - - //Create the Content specific data - cmsContent - var contentDto = dto.ContentDto; - contentDto.NodeId = nodeDto.NodeId; - Database.Insert(contentDto); - - //Create the first version - cmsContentVersion - //Assumes a new Version guid and Version date (modified date) has been set - dto.NodeId = nodeDto.NodeId; - Database.Insert(dto); - - //Create the PropertyData for this version - cmsPropertyData - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); - var keyDictionary = new Dictionary(); - - //Add Properties - foreach (var propertyDataDto in propertyDataDtos) - { - var primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } - - //Update Properties with its newly set Id - foreach (var property in entity.Properties) - { - property.Id = keyDictionary[property.PropertyTypeId]; - } - - UpdatePropertyTags(entity, _tagRepository); - - entity.ResetDirtyProperties(); - } - - protected override void PersistUpdatedItem(IMedia entity) - { - //Updates Modified date - ((Models.Media)entity).UpdatingEntity(); - - //Ensure unique name on the same level - entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id); - - //Ensure that strings don't contain characters that are invalid in XML - entity.SanitizeEntityPropertiesForXmlStorage(); - - //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 }); - 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; - } - - 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 }); - factory.SetPrimaryKey(contentDto.PrimaryKey); - var dto = factory.BuildDto(entity); - - //Updates the (base) node data - umbracoNode - var nodeDto = dto.ContentDto.NodeDto; - var o = Database.Update(nodeDto); - - //Only update this DTO if the contentType has actually changed - if (contentDto.ContentTypeId != entity.ContentTypeId) - { - //Create the Content specific data - cmsContent - var newContentDto = dto.ContentDto; - Database.Update(newContentDto); - } - - //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 }); - dto.Id = contentVerDto.Id; - //Updates the current version - cmsContentVersion - //Assumes a Version guid exists and Version date (modified date) has been set/updated - Database.Update(dto); - - //Create the PropertyData for this version - cmsPropertyData - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); - var keyDictionary = new Dictionary(); - - //Add Properties - foreach (var propertyDataDto in propertyDataDtos) - { - if (propertyDataDto.Id > 0) - { - Database.Update(propertyDataDto); - } - else - { - int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } - } - - //Update Properties with its newly set Id - if (keyDictionary.Any()) - { - foreach (var property in entity.Properties) - { - property.Id = keyDictionary[property.PropertyTypeId]; - } - } - - UpdatePropertyTags(entity, _tagRepository); - - entity.ResetDirtyProperties(); - } - - #endregion - - #region IRecycleBinRepository members - - protected override int RecycleBinId - { - get { return Constants.System.RecycleBinMedia; } - } - - #endregion - - /// - /// Gets paged media results - /// - /// Query to excute - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// An Enumerable list of objects - public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") - { - var args = new List(); - var sbWhere = new StringBuilder(); - Func> filterCallback = null; - if (filter.IsNullOrWhiteSpace() == false) - { - sbWhere.Append("AND (umbracoNode." + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ")"); - args.Add("%" + filter + "%"); - filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray()); - } - - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - new Tuple("cmsContentVersion", "contentId"), - ProcessQuery, orderBy, orderDirection, orderBySystemField, - filterCallback); - - } - - private IEnumerable ProcessQuery(Sql sql) - { - //NOTE: This doesn't allow properties to be part of the query - var dtos = Database.Fetch(sql); - - var ids = dtos.Select(x => x.ContentDto.ContentTypeId).ToArray(); - - //content types - var contentTypes = ids.Length == 0 ? Enumerable.Empty() : _mediaTypeRepository.GetAll(ids).ToArray(); - - var dtosWithContentTypes = dtos - //This select into and null check are required because we don't have a foreign damn key on the contentType column - // http://issues.umbraco.org/issue/U4-5503 - .Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentDto.ContentTypeId) }) - .Where(x => x.contentType != null) - .ToArray(); - - //Go get the property data for each document - var docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition( - d.dto.NodeId, - d.dto.VersionId, - d.dto.VersionDate, - d.dto.ContentDto.NodeDto.CreateDate, - d.contentType)) - .ToArray(); - - var propertyData = GetPropertyCollection(sql, docDefs); - - return dtosWithContentTypes.Select(d => CreateMediaFromDto( - d.dto, - contentTypes.First(ct => ct.Id == d.dto.ContentDto.ContentTypeId), - propertyData[d.dto.NodeId])); - } - - /// - /// Private method to create a media object from a ContentDto - /// - /// - /// - /// - /// - private IMedia CreateMediaFromDto(ContentVersionDto dto, - IMediaType contentType, - PropertyCollection propCollection) - { - var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId); - var media = factory.BuildEntity(dto); - - media.Properties = propCollection; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)media).ResetDirtyProperties(false); - return media; - } - - /// - /// Private method to create a media object from a ContentDto - /// - /// - /// - /// - /// - private IMedia CreateMediaFromDto(ContentVersionDto dto, Guid versionId, Sql docSql) - { - var contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); - - var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId); - var media = factory.BuildEntity(dto); - - var docDef = new DocumentDefinition(dto.NodeId, versionId, media.UpdateDate, media.CreateDate, contentType); - - var properties = GetPropertyCollection(docSql, new[] { docDef }); - - media.Properties = properties[dto.NodeId]; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)media).ResetDirtyProperties(false); - return media; - } - - private string EnsureUniqueNodeName(int parentId, string nodeName, int id = 0) - { - if (EnsureUniqueNaming == false) - return nodeName; - - var sql = new Sql(); - sql.Select("*") - .From() - .Where(x => x.NodeObjectType == NodeObjectTypeId && x.ParentId == parentId && x.Text.StartsWith(nodeName)); - - int uniqueNumber = 1; - var currentName = nodeName; - - var dtos = Database.Fetch(sql); - if (dtos.Any()) - { - var results = dtos.OrderBy(x => x.Text, new SimilarNodeNameComparer()); - foreach (var dto in results) - { - if (id != 0 && id == dto.NodeId) continue; - - if (dto.Text.ToLowerInvariant().Equals(currentName.ToLowerInvariant())) - { - currentName = nodeName + string.Format(" ({0})", uniqueNumber); - uniqueNumber++; - } - } - } - - return currentName; - } - - /// - /// Dispose disposable properties - /// - /// - /// Ensure the unit of work is disposed - /// - protected override void DisposeResources() - { - _mediaTypeRepository.Dispose(); - _tagRepository.Dispose(); - _contentXmlRepository.Dispose(); - _contentPreviewRepository.Dispose(); - } - } + /// + /// Represents a repository for doing CRUD operations for + /// + internal class MediaRepository : RecycleBinRepository, IMediaRepository + { + private readonly IMediaTypeRepository _mediaTypeRepository; + private readonly ITagRepository _tagRepository; + private readonly ContentXmlRepository _contentXmlRepository; + private readonly ContentPreviewRepository _contentPreviewRepository; + + public MediaRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, IContentSection contentSection) + : base(work, cache, logger, sqlSyntax, contentSection) + { + if (mediaTypeRepository == null) throw new ArgumentNullException("mediaTypeRepository"); + if (tagRepository == null) throw new ArgumentNullException("tagRepository"); + _mediaTypeRepository = mediaTypeRepository; + _tagRepository = tagRepository; + _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); + _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); + EnsureUniqueNaming = contentSection.EnsureUniqueNaming; + } + + public bool EnsureUniqueNaming { get; private set; } + + #region Overrides of RepositoryBase + + protected override IMedia PerformGet(int id) + { + var sql = GetBaseQuery(false); + sql.Where(GetBaseWhereClause(), new { Id = id }); + sql.OrderByDescending(x => x.VersionDate); + + var dto = Database.Fetch(sql).FirstOrDefault(); + + if (dto == null) + return null; + + var content = CreateMediaFromDto(dto, dto.VersionId, sql); + + return content; + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + var sql = GetBaseQuery(false); + if (ids.Any()) + { + sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); + } + + return ProcessQuery(sql); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + var sqlClause = GetBaseQuery(false); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate() + .OrderBy(x => x.SortOrder); + + return ProcessQuery(sql); + } + + #endregion + + #region Overrides of PetaPocoRepositoryBase + + protected override Sql GetBaseQuery(bool isCount) + { + var sql = new Sql(); + sql.Select(isCount ? "COUNT(*)" : "*") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; + } + + protected override string GetBaseWhereClause() + { + return "umbracoNode.id = @Id"; + } + + protected override IEnumerable GetDeleteClauses() + { + var list = new List + { + "DELETE FROM cmsTask WHERE nodeId = @Id", + "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", + "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", + "DELETE FROM umbracoRelation WHERE parentId = @Id", + "DELETE FROM umbracoRelation WHERE childId = @Id", + "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", + "DELETE FROM cmsDocument WHERE nodeId = @Id", + "DELETE FROM cmsPropertyData WHERE contentNodeId = @Id", + "DELETE FROM cmsPreviewXml WHERE nodeId = @Id", + "DELETE FROM cmsContentVersion WHERE ContentId = @Id", + "DELETE FROM cmsContentXml WHERE nodeId = @Id", + "DELETE FROM cmsContent WHERE nodeId = @Id", + "DELETE FROM umbracoNode WHERE id = @Id" + }; + return list; + } + + protected override Guid NodeObjectTypeId + { + get { return new Guid(Constants.ObjectTypes.Media); } + } + + #endregion + + #region Overrides of VersionableRepositoryBase + + public override IMedia GetByVersion(Guid versionId) + { + var sql = GetBaseQuery(false); + sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); + sql.OrderByDescending(x => x.VersionDate); + + var dto = Database.Fetch(sql).FirstOrDefault(); + + if (dto == null) + return null; + + var mediaType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); + + var factory = new MediaFactory(mediaType, NodeObjectTypeId, dto.NodeId); + var media = factory.BuildEntity(dto); + + var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.VersionId, media.UpdateDate, media.CreateDate, mediaType) }); + + media.Properties = properties[dto.NodeId]; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)media).ResetDirtyProperties(false); + 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 = new 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 = new 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) + { + var query = Query.Builder; + RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); + } + else + { + foreach (var contentTypeId in contentTypeIds) + { + //copy local + var id = contentTypeId; + var query = Query.Builder.Where(x => x.ContentTypeId == id && x.Trashed == false); + RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); + } + } + + tr.Complete(); + } + } + + private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, Transaction 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, orderBySystemField: 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(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)); + } + + protected override void PerformDeleteVersion(int id, Guid versionId) + { + Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); + } + + #endregion + + #region Unit of Work Implementation + + protected override void PersistNewItem(IMedia entity) + { + ((Models.Media)entity).AddingEntity(); + + //Ensure unique name on the same level + entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name); + + //Ensure that strings don't contain characters that are invalid in XML + entity.SanitizeEntityPropertiesForXmlStorage(); + + var factory = new MediaFactory(NodeObjectTypeId, entity.Id); + var dto = factory.BuildDto(entity); + + //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 level = parent.Level + 1; + var maxSortOrder = Database.ExecuteScalar( + "SELECT coalesce(max(sortOrder),-1) FROM umbracoNode WHERE parentid = @ParentId AND nodeObjectType = @NodeObjectType", + new { /*ParentId =*/ entity.ParentId, NodeObjectType = NodeObjectTypeId }); + var sortOrder = maxSortOrder + 1; + + //Create the (base) node data - umbracoNode + var nodeDto = dto.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); + + //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; + + //Create the Content specific data - cmsContent + var contentDto = dto.ContentDto; + contentDto.NodeId = nodeDto.NodeId; + Database.Insert(contentDto); + + //Create the first version - cmsContentVersion + //Assumes a new Version guid and Version date (modified date) has been set + dto.NodeId = nodeDto.NodeId; + Database.Insert(dto); + + //Create the PropertyData for this version - cmsPropertyData + var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); + var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); + var keyDictionary = new Dictionary(); + + //Add Properties + foreach (var propertyDataDto in propertyDataDtos) + { + var primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); + keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); + } + + //Update Properties with its newly set Id + foreach (var property in entity.Properties) + { + property.Id = keyDictionary[property.PropertyTypeId]; + } + + UpdatePropertyTags(entity, _tagRepository); + + entity.ResetDirtyProperties(); + } + + protected override void PersistUpdatedItem(IMedia entity) + { + //Updates Modified date + ((Models.Media)entity).UpdatingEntity(); + + //Ensure unique name on the same level + entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id); + + //Ensure that strings don't contain characters that are invalid in XML + entity.SanitizeEntityPropertiesForXmlStorage(); + + //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 }); + 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; + } + + 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 }); + factory.SetPrimaryKey(contentDto.PrimaryKey); + var dto = factory.BuildDto(entity); + + //Updates the (base) node data - umbracoNode + var nodeDto = dto.ContentDto.NodeDto; + var o = Database.Update(nodeDto); + + //Only update this DTO if the contentType has actually changed + if (contentDto.ContentTypeId != entity.ContentTypeId) + { + //Create the Content specific data - cmsContent + var newContentDto = dto.ContentDto; + Database.Update(newContentDto); + } + + //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 }); + dto.Id = contentVerDto.Id; + //Updates the current version - cmsContentVersion + //Assumes a Version guid exists and Version date (modified date) has been set/updated + Database.Update(dto); + + //Create the PropertyData for this version - cmsPropertyData + var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); + var propertyDataDtos = propertyFactory.BuildDto(entity.Properties); + var keyDictionary = new Dictionary(); + + //Add Properties + foreach (var propertyDataDto in propertyDataDtos) + { + if (propertyDataDto.Id > 0) + { + Database.Update(propertyDataDto); + } + else + { + int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); + keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); + } + } + + //Update Properties with its newly set Id + if (keyDictionary.Any()) + { + foreach (var property in entity.Properties) + { + property.Id = keyDictionary[property.PropertyTypeId]; + } + } + + UpdatePropertyTags(entity, _tagRepository); + + entity.ResetDirtyProperties(); + } + + #endregion + + #region IRecycleBinRepository members + + protected override int RecycleBinId + { + get { return Constants.System.RecycleBinMedia; } + } + + #endregion + + /// + /// Gets paged media results + /// + /// Query to excute + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") + { + var args = new List(); + var sbWhere = new StringBuilder(); + Func> filterCallback = null; + if (filter.IsNullOrWhiteSpace() == false) + { + sbWhere.Append("AND (umbracoNode." + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ")"); + args.Add("%" + filter + "%"); + filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray()); + } + + return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, + new Tuple("cmsContentVersion", "contentId"), + ProcessQuery, orderBy, orderDirection, orderBySystemField, + filterCallback); + + } + + private IEnumerable ProcessQuery(Sql sql) + { + //NOTE: This doesn't allow properties to be part of the query + var dtos = Database.Fetch(sql); + + var ids = dtos.Select(x => x.ContentDto.ContentTypeId).ToArray(); + + //content types + var contentTypes = ids.Length == 0 ? Enumerable.Empty() : _mediaTypeRepository.GetAll(ids).ToArray(); + + var dtosWithContentTypes = dtos + //This select into and null check are required because we don't have a foreign damn key on the contentType column + // http://issues.umbraco.org/issue/U4-5503 + .Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentDto.ContentTypeId) }) + .Where(x => x.contentType != null) + .ToArray(); + + //Go get the property data for each document + var docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition( + d.dto.NodeId, + d.dto.VersionId, + d.dto.VersionDate, + d.dto.ContentDto.NodeDto.CreateDate, + d.contentType)) + .ToArray(); + + var propertyData = GetPropertyCollection(sql, docDefs); + + return dtosWithContentTypes.Select(d => CreateMediaFromDto( + d.dto, + contentTypes.First(ct => ct.Id == d.dto.ContentDto.ContentTypeId), + propertyData[d.dto.NodeId])); + } + + /// + /// Private method to create a media object from a ContentDto + /// + /// + /// + /// + /// + private IMedia CreateMediaFromDto(ContentVersionDto dto, + IMediaType contentType, + PropertyCollection propCollection) + { + var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId); + var media = factory.BuildEntity(dto); + + media.Properties = propCollection; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)media).ResetDirtyProperties(false); + return media; + } + + /// + /// Private method to create a media object from a ContentDto + /// + /// + /// + /// + /// + private IMedia CreateMediaFromDto(ContentVersionDto dto, Guid versionId, Sql docSql) + { + var contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); + + var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId); + var media = factory.BuildEntity(dto); + + var docDef = new DocumentDefinition(dto.NodeId, versionId, media.UpdateDate, media.CreateDate, contentType); + + var properties = GetPropertyCollection(docSql, new[] { docDef }); + + media.Properties = properties[dto.NodeId]; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)media).ResetDirtyProperties(false); + return media; + } + + private string EnsureUniqueNodeName(int parentId, string nodeName, int id = 0) + { + if (EnsureUniqueNaming == false) + return nodeName; + + var sql = new Sql(); + sql.Select("*") + .From() + .Where(x => x.NodeObjectType == NodeObjectTypeId && x.ParentId == parentId && x.Text.StartsWith(nodeName)); + + int uniqueNumber = 1; + var currentName = nodeName; + + var dtos = Database.Fetch(sql); + if (dtos.Any()) + { + var results = dtos.OrderBy(x => x.Text, new SimilarNodeNameComparer()); + foreach (var dto in results) + { + if (id != 0 && id == dto.NodeId) continue; + + if (dto.Text.ToLowerInvariant().Equals(currentName.ToLowerInvariant())) + { + currentName = nodeName + string.Format(" ({0})", uniqueNumber); + uniqueNumber++; + } + } + } + + return currentName; + } + + /// + /// Dispose disposable properties + /// + /// + /// Ensure the unit of work is disposed + /// + protected override void DisposeResources() + { + _mediaTypeRepository.Dispose(); + _tagRepository.Dispose(); + _contentXmlRepository.Dispose(); + _contentPreviewRepository.Dispose(); + } + } } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index c103d24b2d..e0718d0356 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -23,748 +23,748 @@ using Umbraco.Core.Dynamics; namespace Umbraco.Core.Persistence.Repositories { - /// - /// Represents a repository for doing CRUD operations for - /// - internal class MemberRepository : VersionableRepositoryBase, IMemberRepository - { - private readonly IMemberTypeRepository _memberTypeRepository; - private readonly ITagRepository _tagRepository; - private readonly IMemberGroupRepository _memberGroupRepository; - private readonly ContentXmlRepository _contentXmlRepository; - private readonly ContentPreviewRepository _contentPreviewRepository; - - public MemberRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, IContentSection contentSection) - : base(work, cache, logger, sqlSyntax, contentSection) - { - if (memberTypeRepository == null) throw new ArgumentNullException("memberTypeRepository"); - if (tagRepository == null) throw new ArgumentNullException("tagRepository"); - _memberTypeRepository = memberTypeRepository; - _tagRepository = tagRepository; - _memberGroupRepository = memberGroupRepository; - _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); - _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); - } - - #region Overrides of RepositoryBase - - protected override IMember PerformGet(int id) - { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(x => x.VersionDate); - - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - return null; - - var content = CreateMemberFromDto(dto, dto.ContentVersionDto.VersionId, sql); - - return content; - - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - var sql = GetBaseQuery(false); - if (ids.Any()) - { - sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); - } - - return ProcessQuery(sql); - - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - var baseQuery = GetBaseQuery(false); - - //check if the query is based on properties or not - - var wheres = query.GetWhereClauses(); - //this is a pretty rudimentary check but wil work, we just need to know if this query requires property - // level queries - if (wheres.Any(x => x.Item1.Contains("cmsPropertyType"))) - { - var sqlWithProps = GetNodeIdQueryWithPropertyData(); - var translator = new SqlTranslator(sqlWithProps, query); - var sql = translator.Translate(); - - baseQuery.Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)) - .OrderBy(x => x.SortOrder); - - return ProcessQuery(baseQuery); - } - else - { - var translator = new SqlTranslator(baseQuery, query); - var sql = translator.Translate() - .OrderBy(x => x.SortOrder); - - return ProcessQuery(sql); - } - - } - - #endregion - - #region Overrides of PetaPocoRepositoryBase - - protected override Sql GetBaseQuery(bool isCount) - { - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - //We're joining the type so we can do a query against the member type - not sure if this adds much overhead or not? - // the execution plan says it doesn't so we'll go with that and in that case, it might be worth joining the content - // types by default on the document and media repo's so we can query by content type there too. - .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId); - return sql; - - } - - protected override string GetBaseWhereClause() - { - return "umbracoNode.id = @Id"; - } - - protected Sql GetNodeIdQueryWithPropertyData() - { - var sql = new Sql(); - sql.Select("DISTINCT(umbracoNode.id)") - .From() - .InnerJoin().On(left => left.NodeId, right => right.NodeId) - .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) - .InnerJoin().On(left => left.NodeId, right => right.NodeId) - .InnerJoin().On(left => left.NodeId, right => right.NodeId) - .LeftJoin().On(left => left.ContentTypeId, right => right.ContentTypeId) - .LeftJoin().On(left => left.DataTypeId, right => right.DataTypeId) - .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) - .Append("AND cmsPropertyData.versionId = cmsContentVersion.VersionId") - .Where(x => x.NodeObjectType == NodeObjectTypeId); - return sql; - } - - protected override IEnumerable GetDeleteClauses() - { - var list = new List - { - "DELETE FROM cmsTask WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", - "DELETE FROM umbracoRelation WHERE parentId = @Id", - "DELETE FROM umbracoRelation WHERE childId = @Id", - "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", - "DELETE FROM cmsPropertyData WHERE contentNodeId = @Id", - "DELETE FROM cmsMember2MemberGroup WHERE Member = @Id", - "DELETE FROM cmsMember WHERE nodeId = @Id", - "DELETE FROM cmsContentVersion WHERE ContentId = @Id", - "DELETE FROM cmsContentXml WHERE nodeId = @Id", - "DELETE FROM cmsContent WHERE nodeId = @Id", - "DELETE FROM umbracoNode WHERE id = @Id" - }; - return list; - } - - protected override Guid NodeObjectTypeId - { - get { return new Guid(Constants.ObjectTypes.Member); } - } - - #endregion - - #region Unit of Work Implementation - - protected override void PersistNewItem(IMember entity) - { - ((Member)entity).AddingEntity(); - - //Ensure that strings don't contain characters that are invalid in XML - entity.SanitizeEntityPropertiesForXmlStorage(); - - var factory = new MemberFactory(NodeObjectTypeId, entity.Id); - var dto = factory.BuildDto(entity); - - //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 = - Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE parentID = @ParentId AND nodeObjectType = @NodeObjectType", - new { ParentId = ((IUmbracoEntity)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); - - //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; - - //Create the Content specific data - cmsContent - var contentDto = dto.ContentVersionDto.ContentDto; - contentDto.NodeId = nodeDto.NodeId; - Database.Insert(contentDto); - - //Create the first version - cmsContentVersion - //Assumes a new Version guid and Version date (modified date) has been set - dto.ContentVersionDto.NodeId = nodeDto.NodeId; - Database.Insert(dto.ContentVersionDto); - - //Create the first entry in cmsMember - dto.NodeId = nodeDto.NodeId; - Database.Insert(dto); - - //Create the PropertyData for this version - cmsPropertyData - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - //Add Properties - // - don't try to save the property if it doesn't exist (or doesn't have an ID) on the content type - // - this can occur if the member type doesn't contain the built-in properties that the - // - member object contains. - var propsToPersist = entity.Properties.Where(x => x.PropertyType.HasIdentity).ToArray(); - var propertyDataDtos = propertyFactory.BuildDto(propsToPersist); - var keyDictionary = new Dictionary(); - - //Add Properties - foreach (var propertyDataDto in propertyDataDtos) - { - var primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } - - //Update Properties with its newly set Id - foreach (var property in propsToPersist) - { - property.Id = keyDictionary[property.PropertyTypeId]; - } - - UpdatePropertyTags(entity, _tagRepository); - - ((Member)entity).ResetDirtyProperties(); - } - - protected override void PersistUpdatedItem(IMember entity) - { - //Updates Modified date - ((Member)entity).UpdatingEntity(); - - //Ensure that strings don't contain characters that are invalid in XML - entity.SanitizeEntityPropertiesForXmlStorage(); - - var dirtyEntity = (ICanBeDirty)entity; - - //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 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; - } - - 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 }); - factory.SetPrimaryKey(contentDto.PrimaryKey); - var dto = factory.BuildDto(entity); - - //Updates the (base) node data - umbracoNode - var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; - var o = Database.Update(nodeDto); - - //Only update this DTO if the contentType has actually changed - if (contentDto.ContentTypeId != ((Member)entity).ContentTypeId) - { - //Create the Content specific data - cmsContent - var newContentDto = dto.ContentVersionDto.ContentDto; - Database.Update(newContentDto); - } - - //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 }); - dto.ContentVersionDto.Id = contentVerDto.Id; - //Updates the current version - cmsContentVersion - //Assumes a Version guid exists and Version date (modified date) has been set/updated - Database.Update(dto.ContentVersionDto); - - //Updates the cmsMember entry if it has changed - - //NOTE: these cols are the REAL column names in the db - var changedCols = new List(); - - if (dirtyEntity.IsPropertyDirty("Email")) - { - changedCols.Add("Email"); - } - if (dirtyEntity.IsPropertyDirty("Username")) - { - changedCols.Add("LoginName"); - } - // DO NOT update the password if it has not changed or if it is null or empty - if (dirtyEntity.IsPropertyDirty("RawPasswordValue") && entity.RawPasswordValue.IsNullOrWhiteSpace() == false) - { - changedCols.Add("Password"); - } - //only update the changed cols - if (changedCols.Count > 0) - { - Database.Update(dto, changedCols); - } - - //TODO ContentType for the Member entity - - //Create the PropertyData for this version - cmsPropertyData - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); - var keyDictionary = new Dictionary(); - - //Add Properties - // - don't try to save the property if it doesn't exist (or doesn't have an ID) on the content type - // - this can occur if the member type doesn't contain the built-in properties that the - // - member object contains. - var propsToPersist = entity.Properties.Where(x => x.PropertyType.HasIdentity).ToArray(); - - var propertyDataDtos = propertyFactory.BuildDto(propsToPersist); - - foreach (var propertyDataDto in propertyDataDtos) - { - if (propertyDataDto.Id > 0) - { - Database.Update(propertyDataDto); - } - else - { - int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); - } - } - - //Update Properties with its newly set Id - if (keyDictionary.Any()) - { - foreach (var property in ((Member)entity).Properties) - { - property.Id = keyDictionary[property.PropertyTypeId]; - } - } - - UpdatePropertyTags(entity, _tagRepository); - - dirtyEntity.ResetDirtyProperties(); - } - - #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 memberObjectType = Guid.Parse(Constants.ObjectTypes.Member); - var subQuery = new 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 = new 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.Builder; - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); - } - else - { - foreach (var contentTypeId in contentTypeIds) - { - //copy local - var id = contentTypeId; - var query = Query.Builder.Where(x => x.ContentTypeId == id && x.Trashed == false); - RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); - } - } - - tr.Complete(); - } - } - - private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, Transaction 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, orderBySystemField: 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(xmlItems, tr); - - processed += xmlItems.Length; - - pageIndex++; - } while (processed < total); - } - - public override IMember GetByVersion(Guid versionId) - { - var sql = GetBaseQuery(false); - sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(x => x.VersionDate); - - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - return null; - - var memberType = _memberTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); - - var factory = new MemberFactory(memberType, NodeObjectTypeId, dto.NodeId); - var media = factory.BuildEntity(dto); - - var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.ContentVersionDto.VersionId, media.UpdateDate, media.CreateDate, memberType) }); - - media.Properties = properties[dto.NodeId]; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)media).ResetDirtyProperties(false); - return media; - - } - - protected override void PerformDeleteVersion(int id, Guid versionId) - { - Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); - Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); - } - - #endregion - - public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - //get the group id - var grpQry = new Query().Where(group => group.Name.Equals(roleName)); - var memberGroup = _memberGroupRepository.GetByQuery(grpQry).FirstOrDefault(); - if (memberGroup == null) return Enumerable.Empty(); - - // get the members by username - var query = new Query(); - switch (matchType) - { - case StringPropertyMatchType.Exact: - query.Where(member => member.Username.Equals(usernameToMatch)); - break; - case StringPropertyMatchType.Contains: - query.Where(member => member.Username.Contains(usernameToMatch)); - break; - case StringPropertyMatchType.StartsWith: - query.Where(member => member.Username.StartsWith(usernameToMatch)); - break; - case StringPropertyMatchType.EndsWith: - query.Where(member => member.Username.EndsWith(usernameToMatch)); - break; - case StringPropertyMatchType.Wildcard: - query.Where(member => member.Username.SqlWildcard(usernameToMatch, TextColumnType.NVarchar)); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); - } - var matchedMembers = GetByQuery(query).ToArray(); - - var membersInGroup = new List(); - //then we need to filter the matched members that are in the role - //since the max sql params are 2100 on sql server, we'll reduce that to be safe for potentially other servers and run the queries in batches - var inGroups = matchedMembers.InGroupsOf(1000); - foreach (var batch in inGroups) - { - var memberIdBatch = batch.Select(x => x.Id); - var sql = new Sql().Select("*").From() - .Where(dto => dto.MemberGroup == memberGroup.Id) - .Where("Member IN (@memberIds)", new { memberIds = memberIdBatch }); - var memberIdsInGroup = Database.Fetch(sql) - .Select(x => x.Member).ToArray(); - - membersInGroup.AddRange(matchedMembers.Where(x => memberIdsInGroup.Contains(x.Id))); - } - - return membersInGroup; - - } - - /// - /// Get all members in a specific group - /// - /// - /// - public IEnumerable GetByMemberGroup(string groupName) - { - var grpQry = new Query().Where(group => group.Name.Equals(groupName)); - var memberGroup = _memberGroupRepository.GetByQuery(grpQry).FirstOrDefault(); - if (memberGroup == null) return Enumerable.Empty(); - - var subQuery = new Sql().Select("Member").From().Where(dto => dto.MemberGroup == memberGroup.Id); - - var sql = GetBaseQuery(false) - //TODO: An inner join would be better, though I've read that the query optimizer will always turn a - // subquery with an IN clause into an inner join anyways. - .Append(new Sql("WHERE umbracoNode.id IN (" + subQuery.SQL + ")", subQuery.Arguments)) - .OrderByDescending(x => x.VersionDate) - .OrderBy(x => x.SortOrder); - - return ProcessQuery(sql); - - } - - public bool Exists(string username) - { - var sql = new Sql(); - - sql.Select("COUNT(*)") - .From() - .Where(x => x.LoginName == username); - - return Database.ExecuteScalar(sql) > 0; - } - - public int GetCountByQuery(IQuery query) - { - var sqlWithProps = GetNodeIdQueryWithPropertyData(); - var translator = new SqlTranslator(sqlWithProps, query); - var sql = translator.Translate(); - - //get the COUNT base query - var fullSql = GetBaseQuery(true) - .Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)); - - return Database.ExecuteScalar(fullSql); - } - - /// - /// Gets paged member results - /// - /// - /// The where clause, if this is null all records are queried - /// - /// Index of the page. - /// Size of the page. - /// The total records. - /// The order by column - /// The order direction. - /// Flag to indicate when ordering by system field - /// Search query - /// - /// - /// The query supplied will ONLY work with data specifically on the cmsMember table because we are using PetaPoco paging (SQL paging) - /// - public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") - { - var args = new List(); - var sbWhere = new StringBuilder(); - Func> filterCallback = null; - if (filter.IsNullOrWhiteSpace() == false) - { - sbWhere.Append("AND ((umbracoNode. " + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ") " + - "OR (cmsMember.LoginName LIKE @0" + args.Count + "))"); - args.Add("%" + filter + "%"); - filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray()); - } - - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - new Tuple("cmsMember", "nodeId"), - ProcessQuery, orderBy, orderDirection, orderBySystemField, - filterCallback); - } - - 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 - switch (orderBy.ToUpperInvariant()) - { - case "EMAIL": - return "cmsMember.Email"; - case "LOGINNAME": - return "cmsMember.LoginName"; - } - - return base.GetDatabaseFieldNameForOrderBy(orderBy); - } - - protected override string GetEntityPropertyNameForOrderBy(string orderBy) - { - //Some custom ones - switch (orderBy.ToUpperInvariant()) - { - case "LOGINNAME": - return "Username"; - } - - return base.GetEntityPropertyNameForOrderBy(orderBy); - } - - private IEnumerable ProcessQuery(Sql sql) - { - //NOTE: This doesn't allow properties to be part of the query - var dtos = Database.Fetch(sql); - - var ids = dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray(); - - //content types - var contentTypes = ids.Length == 0 ? Enumerable.Empty() : _memberTypeRepository.GetAll(ids).ToArray(); - - var dtosWithContentTypes = dtos - //This select into and null check are required because we don't have a foreign damn key on the contentType column - // http://issues.umbraco.org/issue/U4-5503 - .Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentVersionDto.ContentDto.ContentTypeId) }) - .Where(x => x.contentType != null) - .ToArray(); - - //Go get the property data for each document - IEnumerable docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition( - d.dto.NodeId, - d.dto.ContentVersionDto.VersionId, - d.dto.ContentVersionDto.VersionDate, - d.dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, - d.contentType)); - - var propertyData = GetPropertyCollection(sql, docDefs); - - return dtosWithContentTypes.Select(d => CreateMemberFromDto( - d.dto, - contentTypes.First(ct => ct.Id == d.dto.ContentVersionDto.ContentDto.ContentTypeId), - propertyData[d.dto.NodeId])); - } - - /// - /// Private method to create a member object from a MemberDto - /// - /// - /// - /// - /// - private IMember CreateMemberFromDto(MemberDto dto, - IMemberType contentType, - PropertyCollection propCollection) - { - var factory = new MemberFactory(contentType, NodeObjectTypeId, dto.ContentVersionDto.NodeId); - var member = factory.BuildEntity(dto); - - member.Properties = propCollection; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)member).ResetDirtyProperties(false); - return member; - } - - /// - /// Private method to create a member object from a MemberDto - /// - /// - /// - /// - /// - private IMember CreateMemberFromDto(MemberDto dto, Guid versionId, Sql docSql) - { - var memberType = _memberTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); - - var factory = new MemberFactory(memberType, NodeObjectTypeId, dto.ContentVersionDto.NodeId); - var member = factory.BuildEntity(dto); - - var docDef = new DocumentDefinition(dto.ContentVersionDto.NodeId, versionId, member.UpdateDate, member.CreateDate, memberType); - - var properties = GetPropertyCollection(docSql, new[] { docDef }); - - member.Properties = properties[dto.ContentVersionDto.NodeId]; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)member).ResetDirtyProperties(false); - return member; - } - - /// - /// Dispose disposable properties - /// - /// - /// Ensure the unit of work is disposed - /// - protected override void DisposeResources() - { - _memberTypeRepository.Dispose(); - _tagRepository.Dispose(); - _memberGroupRepository.Dispose(); - _contentXmlRepository.Dispose(); - _contentPreviewRepository.Dispose(); - } - - } + /// + /// Represents a repository for doing CRUD operations for + /// + internal class MemberRepository : VersionableRepositoryBase, IMemberRepository + { + private readonly IMemberTypeRepository _memberTypeRepository; + private readonly ITagRepository _tagRepository; + private readonly IMemberGroupRepository _memberGroupRepository; + private readonly ContentXmlRepository _contentXmlRepository; + private readonly ContentPreviewRepository _contentPreviewRepository; + + public MemberRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, IContentSection contentSection) + : base(work, cache, logger, sqlSyntax, contentSection) + { + if (memberTypeRepository == null) throw new ArgumentNullException("memberTypeRepository"); + if (tagRepository == null) throw new ArgumentNullException("tagRepository"); + _memberTypeRepository = memberTypeRepository; + _tagRepository = tagRepository; + _memberGroupRepository = memberGroupRepository; + _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); + _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax); + } + + #region Overrides of RepositoryBase + + protected override IMember PerformGet(int id) + { + var sql = GetBaseQuery(false); + sql.Where(GetBaseWhereClause(), new { Id = id }); + sql.OrderByDescending(x => x.VersionDate); + + var dto = Database.Fetch(sql).FirstOrDefault(); + + if (dto == null) + return null; + + var content = CreateMemberFromDto(dto, dto.ContentVersionDto.VersionId, sql); + + return content; + + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + var sql = GetBaseQuery(false); + if (ids.Any()) + { + sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); + } + + return ProcessQuery(sql); + + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + var baseQuery = GetBaseQuery(false); + + //check if the query is based on properties or not + + var wheres = query.GetWhereClauses(); + //this is a pretty rudimentary check but wil work, we just need to know if this query requires property + // level queries + if (wheres.Any(x => x.Item1.Contains("cmsPropertyType"))) + { + var sqlWithProps = GetNodeIdQueryWithPropertyData(); + var translator = new SqlTranslator(sqlWithProps, query); + var sql = translator.Translate(); + + baseQuery.Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)) + .OrderBy(x => x.SortOrder); + + return ProcessQuery(baseQuery); + } + else + { + var translator = new SqlTranslator(baseQuery, query); + var sql = translator.Translate() + .OrderBy(x => x.SortOrder); + + return ProcessQuery(sql); + } + + } + + #endregion + + #region Overrides of PetaPocoRepositoryBase + + protected override Sql GetBaseQuery(bool isCount) + { + var sql = new Sql(); + sql.Select(isCount ? "COUNT(*)" : "*") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + //We're joining the type so we can do a query against the member type - not sure if this adds much overhead or not? + // the execution plan says it doesn't so we'll go with that and in that case, it might be worth joining the content + // types by default on the document and media repo's so we can query by content type there too. + .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; + + } + + protected override string GetBaseWhereClause() + { + return "umbracoNode.id = @Id"; + } + + protected Sql GetNodeIdQueryWithPropertyData() + { + var sql = new Sql(); + sql.Select("DISTINCT(umbracoNode.id)") + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .LeftJoin().On(left => left.ContentTypeId, right => right.ContentTypeId) + .LeftJoin().On(left => left.DataTypeId, right => right.DataTypeId) + .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) + .Append("AND cmsPropertyData.versionId = cmsContentVersion.VersionId") + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; + } + + protected override IEnumerable GetDeleteClauses() + { + var list = new List + { + "DELETE FROM cmsTask WHERE nodeId = @Id", + "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", + "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", + "DELETE FROM umbracoRelation WHERE parentId = @Id", + "DELETE FROM umbracoRelation WHERE childId = @Id", + "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", + "DELETE FROM cmsPropertyData WHERE contentNodeId = @Id", + "DELETE FROM cmsMember2MemberGroup WHERE Member = @Id", + "DELETE FROM cmsMember WHERE nodeId = @Id", + "DELETE FROM cmsContentVersion WHERE ContentId = @Id", + "DELETE FROM cmsContentXml WHERE nodeId = @Id", + "DELETE FROM cmsContent WHERE nodeId = @Id", + "DELETE FROM umbracoNode WHERE id = @Id" + }; + return list; + } + + protected override Guid NodeObjectTypeId + { + get { return new Guid(Constants.ObjectTypes.Member); } + } + + #endregion + + #region Unit of Work Implementation + + protected override void PersistNewItem(IMember entity) + { + ((Member)entity).AddingEntity(); + + //Ensure that strings don't contain characters that are invalid in XML + entity.SanitizeEntityPropertiesForXmlStorage(); + + var factory = new MemberFactory(NodeObjectTypeId, entity.Id); + var dto = factory.BuildDto(entity); + + //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 = + Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoNode WHERE parentID = @ParentId AND nodeObjectType = @NodeObjectType", + new { ParentId = ((IUmbracoEntity)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); + + //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; + + //Create the Content specific data - cmsContent + var contentDto = dto.ContentVersionDto.ContentDto; + contentDto.NodeId = nodeDto.NodeId; + Database.Insert(contentDto); + + //Create the first version - cmsContentVersion + //Assumes a new Version guid and Version date (modified date) has been set + dto.ContentVersionDto.NodeId = nodeDto.NodeId; + Database.Insert(dto.ContentVersionDto); + + //Create the first entry in cmsMember + dto.NodeId = nodeDto.NodeId; + Database.Insert(dto); + + //Create the PropertyData for this version - cmsPropertyData + var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); + //Add Properties + // - don't try to save the property if it doesn't exist (or doesn't have an ID) on the content type + // - this can occur if the member type doesn't contain the built-in properties that the + // - member object contains. + var propsToPersist = entity.Properties.Where(x => x.PropertyType.HasIdentity).ToArray(); + var propertyDataDtos = propertyFactory.BuildDto(propsToPersist); + var keyDictionary = new Dictionary(); + + //Add Properties + foreach (var propertyDataDto in propertyDataDtos) + { + var primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); + keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); + } + + //Update Properties with its newly set Id + foreach (var property in propsToPersist) + { + property.Id = keyDictionary[property.PropertyTypeId]; + } + + UpdatePropertyTags(entity, _tagRepository); + + ((Member)entity).ResetDirtyProperties(); + } + + protected override void PersistUpdatedItem(IMember entity) + { + //Updates Modified date + ((Member)entity).UpdatingEntity(); + + //Ensure that strings don't contain characters that are invalid in XML + entity.SanitizeEntityPropertiesForXmlStorage(); + + var dirtyEntity = (ICanBeDirty)entity; + + //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 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; + } + + 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 }); + factory.SetPrimaryKey(contentDto.PrimaryKey); + var dto = factory.BuildDto(entity); + + //Updates the (base) node data - umbracoNode + var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto; + var o = Database.Update(nodeDto); + + //Only update this DTO if the contentType has actually changed + if (contentDto.ContentTypeId != ((Member)entity).ContentTypeId) + { + //Create the Content specific data - cmsContent + var newContentDto = dto.ContentVersionDto.ContentDto; + Database.Update(newContentDto); + } + + //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 }); + dto.ContentVersionDto.Id = contentVerDto.Id; + //Updates the current version - cmsContentVersion + //Assumes a Version guid exists and Version date (modified date) has been set/updated + Database.Update(dto.ContentVersionDto); + + //Updates the cmsMember entry if it has changed + + //NOTE: these cols are the REAL column names in the db + var changedCols = new List(); + + if (dirtyEntity.IsPropertyDirty("Email")) + { + changedCols.Add("Email"); + } + if (dirtyEntity.IsPropertyDirty("Username")) + { + changedCols.Add("LoginName"); + } + // DO NOT update the password if it has not changed or if it is null or empty + if (dirtyEntity.IsPropertyDirty("RawPasswordValue") && entity.RawPasswordValue.IsNullOrWhiteSpace() == false) + { + changedCols.Add("Password"); + } + //only update the changed cols + if (changedCols.Count > 0) + { + Database.Update(dto, changedCols); + } + + //TODO ContentType for the Member entity + + //Create the PropertyData for this version - cmsPropertyData + var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); + var keyDictionary = new Dictionary(); + + //Add Properties + // - don't try to save the property if it doesn't exist (or doesn't have an ID) on the content type + // - this can occur if the member type doesn't contain the built-in properties that the + // - member object contains. + var propsToPersist = entity.Properties.Where(x => x.PropertyType.HasIdentity).ToArray(); + + var propertyDataDtos = propertyFactory.BuildDto(propsToPersist); + + foreach (var propertyDataDto in propertyDataDtos) + { + if (propertyDataDto.Id > 0) + { + Database.Update(propertyDataDto); + } + else + { + int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); + keyDictionary.Add(propertyDataDto.PropertyTypeId, primaryKey); + } + } + + //Update Properties with its newly set Id + if (keyDictionary.Any()) + { + foreach (var property in ((Member)entity).Properties) + { + property.Id = keyDictionary[property.PropertyTypeId]; + } + } + + UpdatePropertyTags(entity, _tagRepository); + + dirtyEntity.ResetDirtyProperties(); + } + + #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 memberObjectType = Guid.Parse(Constants.ObjectTypes.Member); + var subQuery = new 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 = new 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.Builder; + RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); + } + else + { + foreach (var contentTypeId in contentTypeIds) + { + //copy local + var id = contentTypeId; + var query = Query.Builder.Where(x => x.ContentTypeId == id && x.Trashed == false); + RebuildXmlStructuresProcessQuery(serializer, query, tr, groupSize); + } + } + + tr.Complete(); + } + } + + private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, Transaction 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, orderBySystemField: 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(xmlItems, tr); + + processed += xmlItems.Length; + + pageIndex++; + } while (processed < total); + } + + public override IMember GetByVersion(Guid versionId) + { + var sql = GetBaseQuery(false); + sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); + sql.OrderByDescending(x => x.VersionDate); + + var dto = Database.Fetch(sql).FirstOrDefault(); + + if (dto == null) + return null; + + var memberType = _memberTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); + + var factory = new MemberFactory(memberType, NodeObjectTypeId, dto.NodeId); + var media = factory.BuildEntity(dto); + + var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.ContentVersionDto.VersionId, media.UpdateDate, media.CreateDate, memberType) }); + + media.Properties = properties[dto.NodeId]; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)media).ResetDirtyProperties(false); + return media; + + } + + protected override void PerformDeleteVersion(int id, Guid versionId) + { + Database.Delete("WHERE nodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE contentNodeId = @Id AND versionId = @VersionId", new { Id = id, VersionId = versionId }); + Database.Delete("WHERE ContentId = @Id AND VersionId = @VersionId", new { Id = id, VersionId = versionId }); + } + + #endregion + + public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + //get the group id + var grpQry = new Query().Where(group => group.Name.Equals(roleName)); + var memberGroup = _memberGroupRepository.GetByQuery(grpQry).FirstOrDefault(); + if (memberGroup == null) return Enumerable.Empty(); + + // get the members by username + var query = new Query(); + switch (matchType) + { + case StringPropertyMatchType.Exact: + query.Where(member => member.Username.Equals(usernameToMatch)); + break; + case StringPropertyMatchType.Contains: + query.Where(member => member.Username.Contains(usernameToMatch)); + break; + case StringPropertyMatchType.StartsWith: + query.Where(member => member.Username.StartsWith(usernameToMatch)); + break; + case StringPropertyMatchType.EndsWith: + query.Where(member => member.Username.EndsWith(usernameToMatch)); + break; + case StringPropertyMatchType.Wildcard: + query.Where(member => member.Username.SqlWildcard(usernameToMatch, TextColumnType.NVarchar)); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } + var matchedMembers = GetByQuery(query).ToArray(); + + var membersInGroup = new List(); + //then we need to filter the matched members that are in the role + //since the max sql params are 2100 on sql server, we'll reduce that to be safe for potentially other servers and run the queries in batches + var inGroups = matchedMembers.InGroupsOf(1000); + foreach (var batch in inGroups) + { + var memberIdBatch = batch.Select(x => x.Id); + var sql = new Sql().Select("*").From() + .Where(dto => dto.MemberGroup == memberGroup.Id) + .Where("Member IN (@memberIds)", new { memberIds = memberIdBatch }); + var memberIdsInGroup = Database.Fetch(sql) + .Select(x => x.Member).ToArray(); + + membersInGroup.AddRange(matchedMembers.Where(x => memberIdsInGroup.Contains(x.Id))); + } + + return membersInGroup; + + } + + /// + /// Get all members in a specific group + /// + /// + /// + public IEnumerable GetByMemberGroup(string groupName) + { + var grpQry = new Query().Where(group => group.Name.Equals(groupName)); + var memberGroup = _memberGroupRepository.GetByQuery(grpQry).FirstOrDefault(); + if (memberGroup == null) return Enumerable.Empty(); + + var subQuery = new Sql().Select("Member").From().Where(dto => dto.MemberGroup == memberGroup.Id); + + var sql = GetBaseQuery(false) + //TODO: An inner join would be better, though I've read that the query optimizer will always turn a + // subquery with an IN clause into an inner join anyways. + .Append(new Sql("WHERE umbracoNode.id IN (" + subQuery.SQL + ")", subQuery.Arguments)) + .OrderByDescending(x => x.VersionDate) + .OrderBy(x => x.SortOrder); + + return ProcessQuery(sql); + + } + + public bool Exists(string username) + { + var sql = new Sql(); + + sql.Select("COUNT(*)") + .From() + .Where(x => x.LoginName == username); + + return Database.ExecuteScalar(sql) > 0; + } + + public int GetCountByQuery(IQuery query) + { + var sqlWithProps = GetNodeIdQueryWithPropertyData(); + var translator = new SqlTranslator(sqlWithProps, query); + var sql = translator.Translate(); + + //get the COUNT base query + var fullSql = GetBaseQuery(true) + .Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)); + + return Database.ExecuteScalar(fullSql); + } + + /// + /// Gets paged member results + /// + /// + /// The where clause, if this is null all records are queried + /// + /// Index of the page. + /// Size of the page. + /// The total records. + /// The order by column + /// The order direction. + /// Flag to indicate when ordering by system field + /// Search query + /// + /// + /// The query supplied will ONLY work with data specifically on the cmsMember table because we are using PetaPoco paging (SQL paging) + /// + public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") + { + var args = new List(); + var sbWhere = new StringBuilder(); + Func> filterCallback = null; + if (filter.IsNullOrWhiteSpace() == false) + { + sbWhere.Append("AND ((umbracoNode. " + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ") " + + "OR (cmsMember.LoginName LIKE @0" + args.Count + "))"); + args.Add("%" + filter + "%"); + filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray()); + } + + return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, + new Tuple("cmsMember", "nodeId"), + ProcessQuery, orderBy, orderDirection, orderBySystemField, + filterCallback); + } + + 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 + switch (orderBy.ToUpperInvariant()) + { + case "EMAIL": + return "cmsMember.Email"; + case "LOGINNAME": + return "cmsMember.LoginName"; + } + + return base.GetDatabaseFieldNameForOrderBy(orderBy); + } + + protected override string GetEntityPropertyNameForOrderBy(string orderBy) + { + //Some custom ones + switch (orderBy.ToUpperInvariant()) + { + case "LOGINNAME": + return "Username"; + } + + return base.GetEntityPropertyNameForOrderBy(orderBy); + } + + private IEnumerable ProcessQuery(Sql sql) + { + //NOTE: This doesn't allow properties to be part of the query + var dtos = Database.Fetch(sql); + + var ids = dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray(); + + //content types + var contentTypes = ids.Length == 0 ? Enumerable.Empty() : _memberTypeRepository.GetAll(ids).ToArray(); + + var dtosWithContentTypes = dtos + //This select into and null check are required because we don't have a foreign damn key on the contentType column + // http://issues.umbraco.org/issue/U4-5503 + .Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentVersionDto.ContentDto.ContentTypeId) }) + .Where(x => x.contentType != null) + .ToArray(); + + //Go get the property data for each document + IEnumerable docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition( + d.dto.NodeId, + d.dto.ContentVersionDto.VersionId, + d.dto.ContentVersionDto.VersionDate, + d.dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, + d.contentType)); + + var propertyData = GetPropertyCollection(sql, docDefs); + + return dtosWithContentTypes.Select(d => CreateMemberFromDto( + d.dto, + contentTypes.First(ct => ct.Id == d.dto.ContentVersionDto.ContentDto.ContentTypeId), + propertyData[d.dto.NodeId])); + } + + /// + /// Private method to create a member object from a MemberDto + /// + /// + /// + /// + /// + private IMember CreateMemberFromDto(MemberDto dto, + IMemberType contentType, + PropertyCollection propCollection) + { + var factory = new MemberFactory(contentType, NodeObjectTypeId, dto.ContentVersionDto.NodeId); + var member = factory.BuildEntity(dto); + + member.Properties = propCollection; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)member).ResetDirtyProperties(false); + return member; + } + + /// + /// Private method to create a member object from a MemberDto + /// + /// + /// + /// + /// + private IMember CreateMemberFromDto(MemberDto dto, Guid versionId, Sql docSql) + { + var memberType = _memberTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); + + var factory = new MemberFactory(memberType, NodeObjectTypeId, dto.ContentVersionDto.NodeId); + var member = factory.BuildEntity(dto); + + var docDef = new DocumentDefinition(dto.ContentVersionDto.NodeId, versionId, member.UpdateDate, member.CreateDate, memberType); + + var properties = GetPropertyCollection(docSql, new[] { docDef }); + + member.Properties = properties[dto.ContentVersionDto.NodeId]; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)member).ResetDirtyProperties(false); + return member; + } + + /// + /// Dispose disposable properties + /// + /// + /// Ensure the unit of work is disposed + /// + protected override void DisposeResources() + { + _memberTypeRepository.Dispose(); + _tagRepository.Dispose(); + _memberGroupRepository.Dispose(); + _contentXmlRepository.Dispose(); + _contentPreviewRepository.Dispose(); + } + + } } diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 4fd1c83436..acff02c892 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -25,256 +25,256 @@ using Umbraco.Core.IO; namespace Umbraco.Core.Persistence.Repositories { - using SqlSyntax; - internal abstract class VersionableRepositoryBase : PetaPocoRepositoryBase - where TEntity : class, IAggregateRoot - { - private readonly IContentSection _contentSection; + using SqlSyntax; + internal abstract class VersionableRepositoryBase : PetaPocoRepositoryBase + where TEntity : class, IAggregateRoot + { + private readonly IContentSection _contentSection; - protected VersionableRepositoryBase(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IContentSection contentSection) - : base(work, cache, logger, sqlSyntax) - { - _contentSection = contentSection; - } + protected VersionableRepositoryBase(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IContentSection contentSection) + : base(work, cache, logger, sqlSyntax) + { + _contentSection = contentSection; + } - #region IRepositoryVersionable Implementation + #region IRepositoryVersionable Implementation - public virtual IEnumerable GetAllVersions(int id) - { - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId) - .Where(x => x.NodeId == id) - .OrderByDescending(x => x.VersionDate, SqlSyntax); + public virtual IEnumerable GetAllVersions(int id) + { + var sql = new Sql(); + sql.Select("*") + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.NodeId == id) + .OrderByDescending(x => x.VersionDate, SqlSyntax); - var dtos = Database.Fetch(sql); - foreach (var dto in dtos) - { - yield return GetByVersion(dto.VersionId); - } - } + var dtos = Database.Fetch(sql); + foreach (var dto in dtos) + { + yield return GetByVersion(dto.VersionId); + } + } - public virtual void DeleteVersion(Guid versionId) - { - var dto = Database.FirstOrDefault("WHERE versionId = @VersionId", new { VersionId = versionId }); - if (dto == null) return; + public virtual void DeleteVersion(Guid versionId) + { + var dto = Database.FirstOrDefault("WHERE versionId = @VersionId", new { VersionId = versionId }); + if (dto == null) return; - //Ensure that the lastest version is not deleted - var latestVersionDto = Database.FirstOrDefault("WHERE ContentId = @Id ORDER BY VersionDate DESC", new { Id = dto.NodeId }); - if (latestVersionDto.VersionId == dto.VersionId) - return; + //Ensure that the lastest version is not deleted + var latestVersionDto = Database.FirstOrDefault("WHERE ContentId = @Id ORDER BY VersionDate DESC", new { Id = dto.NodeId }); + if (latestVersionDto.VersionId == dto.VersionId) + return; - using (var transaction = Database.GetTransaction()) - { - PerformDeleteVersion(dto.NodeId, versionId); + using (var transaction = Database.GetTransaction()) + { + PerformDeleteVersion(dto.NodeId, versionId); - transaction.Complete(); - } - } + transaction.Complete(); + } + } - public virtual void DeleteVersions(int id, DateTime versionDate) - { - //Ensure that the latest version is not part of the versions being deleted - var latestVersionDto = Database.FirstOrDefault("WHERE ContentId = @Id ORDER BY VersionDate DESC", new { Id = id }); - var list = - Database.Fetch( - "WHERE versionId <> @VersionId AND (ContentId = @Id AND VersionDate < @VersionDate)", - new { VersionId = latestVersionDto.VersionId, Id = id, VersionDate = versionDate }); - if (list.Any() == false) return; + public virtual void DeleteVersions(int id, DateTime versionDate) + { + //Ensure that the latest version is not part of the versions being deleted + var latestVersionDto = Database.FirstOrDefault("WHERE ContentId = @Id ORDER BY VersionDate DESC", new { Id = id }); + var list = + Database.Fetch( + "WHERE versionId <> @VersionId AND (ContentId = @Id AND VersionDate < @VersionDate)", + new { VersionId = latestVersionDto.VersionId, Id = id, VersionDate = versionDate }); + if (list.Any() == false) return; - using (var transaction = Database.GetTransaction()) - { - foreach (var dto in list) - { - PerformDeleteVersion(id, dto.VersionId); - } + using (var transaction = Database.GetTransaction()) + { + foreach (var dto in list) + { + PerformDeleteVersion(id, dto.VersionId); + } - transaction.Complete(); - } - } + transaction.Complete(); + } + } - public abstract TEntity GetByVersion(Guid versionId); + public abstract TEntity GetByVersion(Guid versionId); - /// - /// Protected method to execute the delete statements for removing a single version for a TEntity item. - /// - /// Id of the to delete a version from - /// Guid id of the version to delete - protected abstract void PerformDeleteVersion(int id, Guid versionId); + /// + /// Protected method to execute the delete statements for removing a single version for a TEntity item. + /// + /// Id of the to delete a version from + /// Guid id of the version to delete + protected abstract void PerformDeleteVersion(int id, Guid versionId); - #endregion + #endregion - public int CountDescendants(int parentId, string contentTypeAlias = null) - { - var pathMatch = parentId == -1 - ? "-1," - : "," + parentId + ","; - var sql = new Sql(); - if (contentTypeAlias.IsNullOrWhiteSpace()) - { - sql.Select("COUNT(*)") - .From() - .Where(x => x.NodeObjectType == NodeObjectTypeId) - .Where(x => x.Path.Contains(pathMatch)); - } - else - { - sql.Select("COUNT(*)") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.ContentTypeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId) - .Where(x => x.Path.Contains(pathMatch)) - .Where(x => x.Alias == contentTypeAlias); - } + public int CountDescendants(int parentId, string contentTypeAlias = null) + { + var pathMatch = parentId == -1 + ? "-1," + : "," + parentId + ","; + var sql = new Sql(); + if (contentTypeAlias.IsNullOrWhiteSpace()) + { + sql.Select("COUNT(*)") + .From() + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.Path.Contains(pathMatch)); + } + else + { + sql.Select("COUNT(*)") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.ContentTypeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.Path.Contains(pathMatch)) + .Where(x => x.Alias == contentTypeAlias); + } - return Database.ExecuteScalar(sql); - } + return Database.ExecuteScalar(sql); + } - public int CountChildren(int parentId, string contentTypeAlias = null) - { - var sql = new Sql(); - if (contentTypeAlias.IsNullOrWhiteSpace()) - { - sql.Select("COUNT(*)") - .From() - .Where(x => x.NodeObjectType == NodeObjectTypeId) - .Where(x => x.ParentId == parentId); - } - else - { - sql.Select("COUNT(*)") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.ContentTypeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId) - .Where(x => x.ParentId == parentId) - .Where(x => x.Alias == contentTypeAlias); - } + public int CountChildren(int parentId, string contentTypeAlias = null) + { + var sql = new Sql(); + if (contentTypeAlias.IsNullOrWhiteSpace()) + { + sql.Select("COUNT(*)") + .From() + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.ParentId == parentId); + } + else + { + sql.Select("COUNT(*)") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.ContentTypeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.ParentId == parentId) + .Where(x => x.Alias == contentTypeAlias); + } - return Database.ExecuteScalar(sql); - } + return Database.ExecuteScalar(sql); + } - /// - /// Get the total count of entities - /// - /// - /// - public int Count(string contentTypeAlias = null) - { - var sql = new Sql(); - if (contentTypeAlias.IsNullOrWhiteSpace()) - { - sql.Select("COUNT(*)") - .From() - .Where(x => x.NodeObjectType == NodeObjectTypeId); - } - else - { - sql.Select("COUNT(*)") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.ContentTypeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId) - .Where(x => x.Alias == contentTypeAlias); - } + /// + /// Get the total count of entities + /// + /// + /// + public int Count(string contentTypeAlias = null) + { + var sql = new Sql(); + if (contentTypeAlias.IsNullOrWhiteSpace()) + { + sql.Select("COUNT(*)") + .From() + .Where(x => x.NodeObjectType == NodeObjectTypeId); + } + else + { + sql.Select("COUNT(*)") + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.ContentTypeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.Alias == contentTypeAlias); + } - return Database.ExecuteScalar(sql); - } + return Database.ExecuteScalar(sql); + } - /// - /// This removes associated tags from the entity - used generally when an entity is recycled - /// - /// - /// - protected void ClearEntityTags(IContentBase entity, ITagRepository tagRepo) - { - tagRepo.ClearTagsFromEntity(entity.Id); - } + /// + /// This removes associated tags from the entity - used generally when an entity is recycled + /// + /// + /// + protected void ClearEntityTags(IContentBase entity, ITagRepository tagRepo) + { + tagRepo.ClearTagsFromEntity(entity.Id); + } - /// - /// Updates the tag repository with any tag enabled properties and their values - /// - /// - /// - protected void UpdatePropertyTags(IContentBase entity, ITagRepository tagRepo) - { - foreach (var tagProp in entity.Properties.Where(x => x.TagSupport.Enable)) - { - if (tagProp.TagSupport.Behavior == PropertyTagBehavior.Remove) - { - //remove the specific tags - tagRepo.RemoveTagsFromProperty( - entity.Id, - tagProp.PropertyTypeId, - tagProp.TagSupport.Tags.Select(x => new Tag { Text = x.Item1, Group = x.Item2 })); - } - else - { - //assign the tags - tagRepo.AssignTagsToProperty( - entity.Id, - tagProp.PropertyTypeId, - tagProp.TagSupport.Tags.Select(x => new Tag { Text = x.Item1, Group = x.Item2 }), - tagProp.TagSupport.Behavior == PropertyTagBehavior.Replace); - } - } - } + /// + /// Updates the tag repository with any tag enabled properties and their values + /// + /// + /// + protected void UpdatePropertyTags(IContentBase entity, ITagRepository tagRepo) + { + foreach (var tagProp in entity.Properties.Where(x => x.TagSupport.Enable)) + { + if (tagProp.TagSupport.Behavior == PropertyTagBehavior.Remove) + { + //remove the specific tags + tagRepo.RemoveTagsFromProperty( + entity.Id, + tagProp.PropertyTypeId, + tagProp.TagSupport.Tags.Select(x => new Tag { Text = x.Item1, Group = x.Item2 })); + } + else + { + //assign the tags + tagRepo.AssignTagsToProperty( + entity.Id, + tagProp.PropertyTypeId, + tagProp.TagSupport.Tags.Select(x => new Tag { Text = x.Item1, Group = x.Item2 }), + tagProp.TagSupport.Behavior == PropertyTagBehavior.Replace); + } + } + } - private Sql GetFilteredSqlForPagedResults(Sql sql, Func> defaultFilter = null) - { - //copy to var so that the original isn't changed - var filteredSql = new Sql(sql.SQL, sql.Arguments); - // Apply filter - if (defaultFilter != null) - { - var filterResult = defaultFilter(); - filteredSql.Append(filterResult.Item1, filterResult.Item2); - } - return filteredSql; - } + private Sql GetFilteredSqlForPagedResults(Sql sql, Func> defaultFilter = null) + { + //copy to var so that the original isn't changed + var filteredSql = new Sql(sql.SQL, sql.Arguments); + // Apply filter + if (defaultFilter != null) + { + var filterResult = defaultFilter(); + filteredSql.Append(filterResult.Item1, filterResult.Item2); + } + return filteredSql; + } - private Sql GetSortedSqlForPagedResults(Sql sql, Direction orderDirection, string orderBy, bool orderBySystemField) - { - //copy to var so that the original isn't changed - var sortedSql = new Sql(sql.SQL, sql.Arguments); + private Sql GetSortedSqlForPagedResults(Sql sql, Direction orderDirection, string orderBy, bool orderBySystemField) + { + //copy to var so that the original isn't changed + var sortedSql = new Sql(sql.SQL, sql.Arguments); - if (orderBySystemField) - { - // Apply order according to parameters - if (string.IsNullOrEmpty(orderBy) == false) - { - var orderByParams = new[] { GetDatabaseFieldNameForOrderBy(orderBy) }; - if (orderDirection == Direction.Ascending) - { - sortedSql.OrderBy(orderByParams); - } - else - { - sortedSql.OrderByDescending(orderByParams); - } - } - } - else - { - // Sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through valie - // from most recent content version for the given order by field - var sortedInt = string.Format(SqlSyntaxContext.SqlSyntaxProvider.ConvertIntegerToOrderableString, "dataInt"); - var sortedDate = string.Format(SqlSyntaxContext.SqlSyntaxProvider.ConvertDateToOrderableString, "dataDate"); - var sortedString = string.Format(SqlSyntaxContext.SqlSyntaxProvider.IsNull, "dataNvarchar", "''"); + if (orderBySystemField) + { + // Apply order according to parameters + if (string.IsNullOrEmpty(orderBy) == false) + { + var orderByParams = new[] { GetDatabaseFieldNameForOrderBy(orderBy) }; + if (orderDirection == Direction.Ascending) + { + sortedSql.OrderBy(orderByParams); + } + else + { + sortedSql.OrderByDescending(orderByParams); + } + } + } + else + { + // Sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through valie + // from most recent content version for the given order by field + var sortedInt = string.Format(SqlSyntaxContext.SqlSyntaxProvider.ConvertIntegerToOrderableString, "dataInt"); + var sortedDate = string.Format(SqlSyntaxContext.SqlSyntaxProvider.ConvertDateToOrderableString, "dataDate"); + var sortedString = string.Format(SqlSyntaxContext.SqlSyntaxProvider.IsNull, "dataNvarchar", "''"); - var orderBySql = string.Format(@"ORDER BY ( + var orderBySql = string.Format(@"ORDER BY ( SELECT CASE WHEN dataInt Is Not Null THEN {0} WHEN dataDate Is Not Null THEN {1} @@ -291,125 +291,125 @@ namespace Umbraco.Core.Persistence.Repositories INNER JOIN cmsPropertyType cpt ON cpt.Id = cpd.propertytypeId WHERE c.nodeId = umbracoNode.Id and cpt.Alias = @0)", sortedInt, sortedDate, sortedString); - sortedSql.Append(orderBySql, orderBy); - if (orderDirection == Direction.Descending) - { - sortedSql.Append(" DESC"); - } - } - return sortedSql; - } + sortedSql.Append(orderBySql, orderBy); + if (orderDirection == Direction.Descending) + { + sortedSql.Append(" DESC"); + } + } + return sortedSql; + } - /// - /// A helper method for inheritors to get the paged results by query in a way that minimizes queries - /// - /// The type of the d. - /// The 'true' entity type (i.e. Content, Member, etc...) - /// The query. - /// Index of the page. - /// Size of the page. - /// The total records. - /// The tablename + column name for the SELECT statement fragment to return the node id from the query - /// A callback to create the default filter to be applied if there is one - /// A callback to process the query result - /// The order by column - /// The order direction. - /// Flag to indicate when ordering by system field - /// - /// orderBy - protected IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - Tuple nodeIdSelect, - Func> processQuery, - string orderBy, - Direction orderDirection, - bool orderBySystemField, - Func> defaultFilter = null) - where TContentBase : class, IAggregateRoot, TEntity - { - if (orderBy == null) throw new ArgumentNullException("orderBy"); + /// + /// A helper method for inheritors to get the paged results by query in a way that minimizes queries + /// + /// The type of the d. + /// The 'true' entity type (i.e. Content, Member, etc...) + /// The query. + /// Index of the page. + /// Size of the page. + /// The total records. + /// The tablename + column name for the SELECT statement fragment to return the node id from the query + /// A callback to create the default filter to be applied if there is one + /// A callback to process the query result + /// The order by column + /// The order direction. + /// Flag to indicate when ordering by system field + /// + /// orderBy + protected IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, + Tuple nodeIdSelect, + Func> processQuery, + string orderBy, + Direction orderDirection, + bool orderBySystemField, + Func> defaultFilter = null) + where TContentBase : class, IAggregateRoot, TEntity + { + if (orderBy == null) throw new ArgumentNullException("orderBy"); - // Get base query - var sqlBase = GetBaseQuery(false); + // Get base query + var sqlBase = GetBaseQuery(false); - if (query == null) query = new Query(); - var translator = new SqlTranslator(sqlBase, query); - var sqlQuery = translator.Translate(); + if (query == null) query = new Query(); + var translator = new SqlTranslator(sqlBase, query); + var sqlQuery = translator.Translate(); - // Note we can't do multi-page for several DTOs like we can multi-fetch and are doing in PerformGetByQuery, - // but actually given we are doing a Get on each one (again as in PerformGetByQuery), we only need the node Id. - // So we'll modify the SQL. - var sqlNodeIds = new Sql( - sqlQuery.SQL.Replace("SELECT *", string.Format("SELECT {0}.{1}", nodeIdSelect.Item1, nodeIdSelect.Item2)), - sqlQuery.Arguments); + // Note we can't do multi-page for several DTOs like we can multi-fetch and are doing in PerformGetByQuery, + // but actually given we are doing a Get on each one (again as in PerformGetByQuery), we only need the node Id. + // So we'll modify the SQL. + var sqlNodeIds = new Sql( + sqlQuery.SQL.Replace("SELECT *", string.Format("SELECT {0}.{1}", nodeIdSelect.Item1, nodeIdSelect.Item2)), + sqlQuery.Arguments); - //get sorted and filtered sql - var sqlNodeIdsWithSort = GetSortedSqlForPagedResults( - GetFilteredSqlForPagedResults(sqlNodeIds, defaultFilter), - orderDirection, orderBy, orderBySystemField); + //get sorted and filtered sql + var sqlNodeIdsWithSort = GetSortedSqlForPagedResults( + GetFilteredSqlForPagedResults(sqlNodeIds, defaultFilter), + orderDirection, orderBy, orderBySystemField); - // Get page of results and total count - IEnumerable result; - var pagedResult = Database.Page(pageIndex + 1, pageSize, sqlNodeIdsWithSort); - totalRecords = Convert.ToInt32(pagedResult.TotalItems); + // Get page of results and total count + IEnumerable result; + var pagedResult = Database.Page(pageIndex + 1, pageSize, sqlNodeIdsWithSort); + totalRecords = Convert.ToInt32(pagedResult.TotalItems); - //NOTE: We need to check the actual items returned, not the 'totalRecords', that is because if you request a page number - // that doesn't actually have any data on it, the totalRecords will still indicate there are records but there are none in - // the pageResult, then the GetAll will actually return ALL records in the db. - if (pagedResult.Items.Any()) - { - //Crete the inner paged query that was used above to get the paged result, we'll use that as the inner sub query - var args = sqlNodeIdsWithSort.Arguments; - string sqlStringCount, sqlStringPage; - Database.BuildPageQueries(pageIndex * pageSize, pageSize, sqlNodeIdsWithSort.SQL, ref args, out sqlStringCount, out sqlStringPage); + //NOTE: We need to check the actual items returned, not the 'totalRecords', that is because if you request a page number + // that doesn't actually have any data on it, the totalRecords will still indicate there are records but there are none in + // the pageResult, then the GetAll will actually return ALL records in the db. + if (pagedResult.Items.Any()) + { + //Crete the inner paged query that was used above to get the paged result, we'll use that as the inner sub query + var args = sqlNodeIdsWithSort.Arguments; + string sqlStringCount, sqlStringPage; + Database.BuildPageQueries(pageIndex * pageSize, pageSize, sqlNodeIdsWithSort.SQL, ref args, out sqlStringCount, out sqlStringPage); - //if this is for sql server, the sqlPage will start with a SELECT * but we don't want that, we only want to return the nodeId - sqlStringPage = sqlStringPage - .Replace("SELECT *", - //This ensures we only take the field name of the node id select and not the table name - since the resulting select - // will ony work with the field name. - "SELECT " + nodeIdSelect.Item2); + //if this is for sql server, the sqlPage will start with a SELECT * but we don't want that, we only want to return the nodeId + sqlStringPage = sqlStringPage + .Replace("SELECT *", + //This ensures we only take the field name of the node id select and not the table name - since the resulting select + // will ony work with the field name. + "SELECT " + nodeIdSelect.Item2); - //We need to make this an inner join on the paged query - var splitQuery = sqlQuery.SQL.Split(new[] { "WHERE " }, StringSplitOptions.None); - var withInnerJoinSql = new Sql(splitQuery[0]) - .Append("INNER JOIN (") - //join the paged query with the paged query arguments - .Append(sqlStringPage, args) - .Append(") temp ") - .Append(string.Format("ON {0}.{1} = temp.{1}", nodeIdSelect.Item1, nodeIdSelect.Item2)) - //add the original where clause back with the original arguments - .Where(splitQuery[1], sqlQuery.Arguments); + //We need to make this an inner join on the paged query + var splitQuery = sqlQuery.SQL.Split(new[] { "WHERE " }, StringSplitOptions.None); + var withInnerJoinSql = new Sql(splitQuery[0]) + .Append("INNER JOIN (") + //join the paged query with the paged query arguments + .Append(sqlStringPage, args) + .Append(") temp ") + .Append(string.Format("ON {0}.{1} = temp.{1}", nodeIdSelect.Item1, nodeIdSelect.Item2)) + //add the original where clause back with the original arguments + .Where(splitQuery[1], sqlQuery.Arguments); - //get sorted and filtered sql - var fullQuery = GetSortedSqlForPagedResults( - GetFilteredSqlForPagedResults(withInnerJoinSql, defaultFilter), - orderDirection, orderBy, orderBySystemField); - return processQuery(fullQuery); - } - else - { - result = Enumerable.Empty(); - } + //get sorted and filtered sql + var fullQuery = GetSortedSqlForPagedResults( + GetFilteredSqlForPagedResults(withInnerJoinSql, defaultFilter), + orderDirection, orderBy, orderBySystemField); + return processQuery(fullQuery); + } + else + { + result = Enumerable.Empty(); + } - return result; - } + return result; + } - protected IDictionary GetPropertyCollection( - Sql docSql, - IEnumerable documentDefs) - { - if (documentDefs.Any() == false) return new Dictionary(); + protected IDictionary GetPropertyCollection( + Sql docSql, + IEnumerable documentDefs) + { + if (documentDefs.Any() == false) return new Dictionary(); - //we need to parse the original SQL statement and reduce the columns to just cmsContent.nodeId, cmsContentVersion.VersionId so that we can use - // the statement to go get the property data for all of the items by using an inner join - var parsedOriginalSql = "SELECT {0} " + docSql.SQL.Substring(docSql.SQL.IndexOf("FROM", StringComparison.Ordinal)); - //now remove everything from an Orderby clause and beyond - if (parsedOriginalSql.InvariantContains("ORDER BY ")) - { - parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal)); - } + //we need to parse the original SQL statement and reduce the columns to just cmsContent.nodeId, cmsContentVersion.VersionId so that we can use + // the statement to go get the property data for all of the items by using an inner join + var parsedOriginalSql = "SELECT {0} " + docSql.SQL.Substring(docSql.SQL.IndexOf("FROM", StringComparison.Ordinal)); + //now remove everything from an Orderby clause and beyond + if (parsedOriginalSql.InvariantContains("ORDER BY ")) + { + parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal)); + } - var propSql = new Sql(@"SELECT cmsPropertyData.* + var propSql = new Sql(@"SELECT cmsPropertyData.* FROM cmsPropertyData INNER JOIN cmsPropertyType ON cmsPropertyData.propertytypeid = cmsPropertyType.id @@ -419,13 +419,13 @@ ON cmsPropertyData.versionId = docData.VersionId AND cmsPropertyData.contentNode LEFT OUTER JOIN cmsDataTypePreValues ON cmsPropertyType.dataTypeId = cmsDataTypePreValues.datatypeNodeId", docSql.Arguments); - var allPropertyData = Database.Fetch(propSql); + var allPropertyData = Database.Fetch(propSql); - //This is a lazy access call to get all prevalue data for the data types that make up all of these properties which we use - // below if any property requires tag support - var allPreValues = new Lazy>(() => - { - var preValsSql = new Sql(@"SELECT a.id, a.value, a.sortorder, a.alias, a.datatypeNodeId + //This is a lazy access call to get all prevalue data for the data types that make up all of these properties which we use + // below if any property requires tag support + var allPreValues = new Lazy>(() => + { + var preValsSql = new Sql(@"SELECT a.id, a.value, a.sortorder, a.alias, a.datatypeNodeId FROM cmsDataTypePreValues a WHERE EXISTS( SELECT DISTINCT b.id as preValIdInner @@ -437,185 +437,185 @@ WHERE EXISTS( ON cmsPropertyType.contentTypeId = docData.contentType WHERE a.id = b.id)", docSql.Arguments); - return Database.Fetch(preValsSql); - }); + return Database.Fetch(preValsSql); + }); - var result = new Dictionary(); + var result = new Dictionary(); - var propertiesWithTagSupport = new Dictionary(); + var propertiesWithTagSupport = new Dictionary(); - //iterate each definition grouped by it's content type - this will mean less property type iterations while building - // up the property collections - foreach (var compositionGroup in documentDefs.GroupBy(x => x.Composition)) - { - var compositionProperties = compositionGroup.Key.CompositionPropertyTypes.ToArray(); + //iterate each definition grouped by it's content type - this will mean less property type iterations while building + // up the property collections + foreach (var compositionGroup in documentDefs.GroupBy(x => x.Composition)) + { + var compositionProperties = compositionGroup.Key.CompositionPropertyTypes.ToArray(); - foreach (var def in compositionGroup) - { - var propertyDataDtos = allPropertyData.Where(x => x.NodeId == def.Id).Distinct(); + foreach (var def in compositionGroup) + { + var propertyDataDtos = allPropertyData.Where(x => x.NodeId == def.Id).Distinct(); - var propertyFactory = new PropertyFactory(compositionProperties, def.Version, def.Id, def.CreateDate, def.VersionDate); - var properties = propertyFactory.BuildEntity(propertyDataDtos.ToArray()).ToArray(); + var propertyFactory = new PropertyFactory(compositionProperties, def.Version, def.Id, def.CreateDate, def.VersionDate); + var properties = propertyFactory.BuildEntity(propertyDataDtos.ToArray()).ToArray(); - var newProperties = properties.Where(x => x.HasIdentity == false && x.PropertyType.HasIdentity); + var newProperties = properties.Where(x => x.HasIdentity == false && x.PropertyType.HasIdentity); - foreach (var property in newProperties) - { - var propertyDataDto = new PropertyDataDto { NodeId = def.Id, PropertyTypeId = property.PropertyTypeId, VersionId = def.Version }; - int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); + foreach (var property in newProperties) + { + var propertyDataDto = new PropertyDataDto { NodeId = def.Id, PropertyTypeId = property.PropertyTypeId, VersionId = def.Version }; + int primaryKey = Convert.ToInt32(Database.Insert(propertyDataDto)); - property.Version = def.Version; - property.Id = primaryKey; - } + property.Version = def.Version; + property.Id = primaryKey; + } - foreach (var property in properties) - { - //NOTE: The benchmarks run with and without the following code show very little change so this is not a perf bottleneck - var editor = PropertyEditorResolver.Current.GetByAlias(property.PropertyType.PropertyEditorAlias); + foreach (var property in properties) + { + //NOTE: The benchmarks run with and without the following code show very little change so this is not a perf bottleneck + var editor = PropertyEditorResolver.Current.GetByAlias(property.PropertyType.PropertyEditorAlias); - var tagSupport = propertiesWithTagSupport.ContainsKey(property.PropertyType.PropertyEditorAlias) - ? propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] - : TagExtractor.GetAttribute(editor); + var tagSupport = propertiesWithTagSupport.ContainsKey(property.PropertyType.PropertyEditorAlias) + ? propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] + : TagExtractor.GetAttribute(editor); - if (tagSupport != null) - { - //add to local cache so we don't need to reflect next time for this property editor alias - propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] = tagSupport; + if (tagSupport != null) + { + //add to local cache so we don't need to reflect next time for this property editor alias + propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] = tagSupport; - //this property has tags, so we need to extract them and for that we need the prevals which we've already looked up - var preValData = allPreValues.Value.Where(x => x.DataTypeNodeId == property.PropertyType.DataTypeDefinitionId) - .Distinct() - .ToArray(); + //this property has tags, so we need to extract them and for that we need the prevals which we've already looked up + var preValData = allPreValues.Value.Where(x => x.DataTypeNodeId == property.PropertyType.DataTypeDefinitionId) + .Distinct() + .ToArray(); - var asDictionary = preValData.ToDictionary(x => x.Alias, x => new PreValue(x.Id, x.Value, x.SortOrder)); + var asDictionary = preValData.ToDictionary(x => x.Alias, x => new PreValue(x.Id, x.Value, x.SortOrder)); - var preVals = new PreValueCollection(asDictionary); + var preVals = new PreValueCollection(asDictionary); - var contentPropData = new ContentPropertyData(property.Value, - preVals, - new Dictionary()); + var contentPropData = new ContentPropertyData(property.Value, + preVals, + new Dictionary()); - TagExtractor.SetPropertyTags(property, contentPropData, property.Value, tagSupport); - } - } + TagExtractor.SetPropertyTags(property, contentPropData, property.Value, tagSupport); + } + } - if (result.ContainsKey(def.Id)) - { - Logger.Warn>("The query returned multiple property sets for document definition " + def.Id + ", " + def.Composition.Name); - } - result[def.Id] = new PropertyCollection(properties); - } - } + if (result.ContainsKey(def.Id)) + { + Logger.Warn>("The query returned multiple property sets for document definition " + def.Id + ", " + def.Composition.Name); + } + result[def.Id] = new PropertyCollection(properties); + } + } - return result; - } + return result; + } - public class DocumentDefinition - { - /// - /// Initializes a new instance of the class. - /// - public DocumentDefinition(int id, Guid version, DateTime versionDate, DateTime createDate, IContentTypeComposition composition) - { - Id = id; - Version = version; - VersionDate = versionDate; - CreateDate = createDate; - Composition = composition; - } + public class DocumentDefinition + { + /// + /// Initializes a new instance of the class. + /// + public DocumentDefinition(int id, Guid version, DateTime versionDate, DateTime createDate, IContentTypeComposition composition) + { + Id = id; + Version = version; + VersionDate = versionDate; + CreateDate = createDate; + Composition = composition; + } - public int Id { get; set; } - public Guid Version { get; set; } - public DateTime VersionDate { get; set; } - public DateTime CreateDate { get; set; } - public IContentTypeComposition Composition { get; set; } - } + public int Id { get; set; } + public Guid Version { get; set; } + public DateTime VersionDate { get; set; } + public DateTime CreateDate { get; set; } + public IContentTypeComposition Composition { get; set; } + } - protected virtual string GetDatabaseFieldNameForOrderBy(string orderBy) - { - // Translate the passed order by field (which were originally defined for in-memory object sorting - // of ContentItemBasic instances) to the database field names. - switch (orderBy.ToUpperInvariant()) - { - case "UPDATEDATE": - return "cmsContentVersion.VersionDate"; - case "NAME": - return "umbracoNode.text"; - case "OWNER": - //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter - return "umbracoNode.nodeUser"; - // Members only - case "USERNAME": - return "cmsMember.LoginName"; - default: - //ensure invalid SQL cannot be submitted - return Regex.Replace(orderBy, @"[^\w\.,`\[\]@-]", ""); - } - } + protected virtual string GetDatabaseFieldNameForOrderBy(string orderBy) + { + // Translate the passed order by field (which were originally defined for in-memory object sorting + // of ContentItemBasic instances) to the database field names. + switch (orderBy.ToUpperInvariant()) + { + case "UPDATEDATE": + return "cmsContentVersion.VersionDate"; + case "NAME": + return "umbracoNode.text"; + case "OWNER": + //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter + return "umbracoNode.nodeUser"; + // Members only + case "USERNAME": + return "cmsMember.LoginName"; + default: + //ensure invalid SQL cannot be submitted + return Regex.Replace(orderBy, @"[^\w\.,`\[\]@-]", ""); + } + } - protected virtual string GetEntityPropertyNameForOrderBy(string orderBy) - { - // Translate the passed order by field (which were originally defined for in-memory object sorting - // of ContentItemBasic instances) to the IMedia property names. - switch (orderBy.ToUpperInvariant()) - { - case "OWNER": - //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter - return "CreatorId"; - case "UPDATER": - //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter - return "WriterId"; - case "VERSIONDATE": - return "UpdateDate"; - default: - //ensure invalid SQL cannot be submitted - return Regex.Replace(orderBy, @"[^\w\.,`\[\]@-]", ""); - } - } + protected virtual string GetEntityPropertyNameForOrderBy(string orderBy) + { + // Translate the passed order by field (which were originally defined for in-memory object sorting + // of ContentItemBasic instances) to the IMedia property names. + switch (orderBy.ToUpperInvariant()) + { + case "OWNER": + //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter + return "CreatorId"; + case "UPDATER": + //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter + return "WriterId"; + case "VERSIONDATE": + return "UpdateDate"; + default: + //ensure invalid SQL cannot be submitted + return Regex.Replace(orderBy, @"[^\w\.,`\[\]@-]", ""); + } + } - /// - /// Deletes all media files passed in. - /// - /// - /// - public virtual bool DeleteMediaFiles(IEnumerable files) - { - //ensure duplicates are removed - files = files.Distinct(); + /// + /// Deletes all media files passed in. + /// + /// + /// + public virtual bool DeleteMediaFiles(IEnumerable files) + { + //ensure duplicates are removed + files = files.Distinct(); - var allsuccess = true; + var allsuccess = true; - var fs = FileSystemProviderManager.Current.GetFileSystemProvider(); - Parallel.ForEach(files, file => - { - try - { - if (file.IsNullOrWhiteSpace()) return; + 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 relativeFilePath = fs.GetRelativePath(file); + if (fs.FileExists(relativeFilePath) == false) return; - var parentDirectory = System.IO.Path.GetDirectoryName(relativeFilePath); + var parentDirectory = System.IO.Path.GetDirectoryName(relativeFilePath); - // don't want to delete the media folder if not using directories. - if (_contentSection.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) - { - Logger.Error>("An error occurred while deleting file attached to nodes: " + file, e); - allsuccess = false; - } - }); + // don't want to delete the media folder if not using directories. + if (_contentSection.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) + { + Logger.Error>("An error occurred while deleting file attached to nodes: " + file, e); + allsuccess = false; + } + }); - return allsuccess; - } - } + return allsuccess; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index ef4601f1e5..e663c8e64b 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -6,79 +6,79 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.SqlSyntax { - /// - /// Defines an SqlSyntaxProvider - /// - public interface ISqlSyntaxProvider - { - string EscapeString(string val); + /// + /// Defines an SqlSyntaxProvider + /// + public interface ISqlSyntaxProvider + { + string EscapeString(string val); - string GetWildcardPlaceholder(); - string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType); - string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType); + string GetWildcardPlaceholder(); + string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType); + string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType); - [Obsolete("Use the overload with the parameter index instead")] - string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType); - [Obsolete("Use the overload with the parameter index instead")] - string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType); - [Obsolete("Use the overload with the parameter index instead")] - string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType); - [Obsolete("Use the overload with the parameter index instead")] - string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType); - [Obsolete("Use the overload with the parameter index instead")] - string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType); + [Obsolete("Use the overload with the parameter index instead")] + string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType); + [Obsolete("Use the overload with the parameter index instead")] + string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType); + [Obsolete("Use the overload with the parameter index instead")] + string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType); + [Obsolete("Use the overload with the parameter index instead")] + string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType); + [Obsolete("Use the overload with the parameter index instead")] + string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType); - string GetQuotedTableName(string tableName); - string GetQuotedColumnName(string columnName); - string GetQuotedName(string name); - bool DoesTableExist(Database db, string tableName); - string GetIndexType(IndexTypes indexTypes); - string GetSpecialDbType(SpecialDbTypes dbTypes); - string CreateTable { get; } - string DropTable { get; } - string AddColumn { get; } - string DropColumn { get; } - string AlterColumn { get; } - string RenameColumn { get; } - string RenameTable { get; } - string CreateSchema { get; } - string AlterSchema { get; } - string DropSchema { get; } - string CreateIndex { get; } - string DropIndex { get; } - string InsertData { get; } - string UpdateData { get; } - string DeleteData { get; } - string TruncateTable { get; } - string CreateConstraint { get; } - string DeleteConstraint { get; } - string CreateForeignKeyConstraint { get; } - string DeleteDefaultConstraint { get; } - string FormatDateTime(DateTime date, bool includeTime = true); - string Format(TableDefinition table); - string Format(IEnumerable columns); - List Format(IEnumerable indexes); - List Format(IEnumerable foreignKeys); - string FormatPrimaryKey(TableDefinition table); - string GetQuotedValue(string value); - string Format(ColumnDefinition column); - string Format(IndexDefinition index); - string Format(ForeignKeyDefinition foreignKey); - string FormatColumnRename(string tableName, string oldName, string newName); - string FormatTableRename(string oldName, string newName); - bool SupportsClustered(); - bool SupportsIdentityInsert(); - bool? SupportsCaseInsensitiveQueries(Database db); + string GetQuotedTableName(string tableName); + string GetQuotedColumnName(string columnName); + string GetQuotedName(string name); + bool DoesTableExist(Database db, string tableName); + string GetIndexType(IndexTypes indexTypes); + string GetSpecialDbType(SpecialDbTypes dbTypes); + string CreateTable { get; } + string DropTable { get; } + string AddColumn { get; } + string DropColumn { get; } + string AlterColumn { get; } + string RenameColumn { get; } + string RenameTable { get; } + string CreateSchema { get; } + string AlterSchema { get; } + string DropSchema { get; } + string CreateIndex { get; } + string DropIndex { get; } + string InsertData { get; } + string UpdateData { get; } + string DeleteData { get; } + string TruncateTable { get; } + string CreateConstraint { get; } + string DeleteConstraint { get; } + string CreateForeignKeyConstraint { get; } + string DeleteDefaultConstraint { get; } + string FormatDateTime(DateTime date, bool includeTime = true); + string Format(TableDefinition table); + string Format(IEnumerable columns); + List Format(IEnumerable indexes); + List Format(IEnumerable foreignKeys); + string FormatPrimaryKey(TableDefinition table); + string GetQuotedValue(string value); + string Format(ColumnDefinition column); + string Format(IndexDefinition index); + string Format(ForeignKeyDefinition foreignKey); + string FormatColumnRename(string tableName, string oldName, string newName); + string FormatTableRename(string oldName, string newName); + bool SupportsClustered(); + bool SupportsIdentityInsert(); + bool? SupportsCaseInsensitiveQueries(Database db); - string IsNull { get; } - string ConvertIntegerToOrderableString { get; } - string ConvertDateToOrderableString { get; } + string IsNull { get; } + string ConvertIntegerToOrderableString { get; } + string ConvertDateToOrderableString { get; } - IEnumerable GetTablesInSchema(Database db); - IEnumerable GetColumnsInSchema(Database db); - IEnumerable> GetConstraintsPerTable(Database db); - IEnumerable> GetConstraintsPerColumn(Database db); + IEnumerable GetTablesInSchema(Database db); + IEnumerable GetColumnsInSchema(Database db); + IEnumerable> GetConstraintsPerTable(Database db); + IEnumerable> GetConstraintsPerColumn(Database db); - IEnumerable> GetDefinedIndexes(Database db); - } + IEnumerable> GetDefinedIndexes(Database db); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index c29afa7d32..c45aade707 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -7,393 +7,393 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Persistence.SqlSyntax { - /// - /// Represents an SqlSyntaxProvider for MySql - /// - [SqlSyntaxProviderAttribute("MySql.Data.MySqlClient")] - public class MySqlSyntaxProvider : SqlSyntaxProviderBase - { - private readonly ILogger _logger; + /// + /// Represents an SqlSyntaxProvider for MySql + /// + [SqlSyntaxProviderAttribute("MySql.Data.MySqlClient")] + public class MySqlSyntaxProvider : SqlSyntaxProviderBase + { + private readonly ILogger _logger; - public MySqlSyntaxProvider(ILogger logger) - { - _logger = logger; + public MySqlSyntaxProvider(ILogger logger) + { + _logger = logger; - AutoIncrementDefinition = "AUTO_INCREMENT"; - IntColumnDefinition = "int(11)"; - BoolColumnDefinition = "tinyint(1)"; - DateTimeColumnDefinition = "TIMESTAMP"; - TimeColumnDefinition = "time"; - DecimalColumnDefinition = "decimal(38,6)"; - GuidColumnDefinition = "char(36)"; + AutoIncrementDefinition = "AUTO_INCREMENT"; + IntColumnDefinition = "int(11)"; + BoolColumnDefinition = "tinyint(1)"; + DateTimeColumnDefinition = "TIMESTAMP"; + TimeColumnDefinition = "time"; + DecimalColumnDefinition = "decimal(38,6)"; + GuidColumnDefinition = "char(36)"; - DefaultValueFormat = "DEFAULT {0}"; + DefaultValueFormat = "DEFAULT {0}"; InitColumnTypeMap(); - } + } - public override IEnumerable GetTablesInSchema(Database db) - { - List list; - try - { - //needs to be open to read the schema name - db.OpenSharedConnection(); + public override IEnumerable GetTablesInSchema(Database db) + { + List list; + try + { + //needs to be open to read the schema name + db.OpenSharedConnection(); - var items = - db.Fetch( - "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = @TableSchema", - new { TableSchema = db.Connection.Database }); - list = items.Select(x => x.TABLE_NAME).Cast().ToList(); - } - finally - { - db.CloseSharedConnection(); - } - return list; - } + var items = + db.Fetch( + "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = @TableSchema", + new { TableSchema = db.Connection.Database }); + list = items.Select(x => x.TABLE_NAME).Cast().ToList(); + } + finally + { + db.CloseSharedConnection(); + } + return list; + } - public override IEnumerable GetColumnsInSchema(Database db) - { - List list; - try - { - //needs to be open to read the schema name - db.OpenSharedConnection(); + public override IEnumerable GetColumnsInSchema(Database db) + { + List list; + try + { + //needs to be open to read the schema name + db.OpenSharedConnection(); - var items = - db.Fetch( - "SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @TableSchema", - new { TableSchema = db.Connection.Database }); - list = - items.Select( - item => - new ColumnInfo(item.TABLE_NAME, item.COLUMN_NAME, int.Parse(item.ORDINAL_POSITION.ToString()), item.COLUMN_DEFAULT ?? "", - item.IS_NULLABLE, item.DATA_TYPE)).ToList(); - } - finally - { - db.CloseSharedConnection(); - } - return list; - } + var items = + db.Fetch( + "SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @TableSchema", + new { TableSchema = db.Connection.Database }); + list = + items.Select( + item => + new ColumnInfo(item.TABLE_NAME, item.COLUMN_NAME, int.Parse(item.ORDINAL_POSITION.ToString()), item.COLUMN_DEFAULT ?? "", + item.IS_NULLABLE, item.DATA_TYPE)).ToList(); + } + finally + { + db.CloseSharedConnection(); + } + return list; + } - public override IEnumerable> GetConstraintsPerTable(Database db) - { - List> list; - try - { - //needs to be open to read the schema name - db.OpenSharedConnection(); + public override IEnumerable> GetConstraintsPerTable(Database db) + { + List> list; + try + { + //needs to be open to read the schema name + db.OpenSharedConnection(); - //Does not include indexes and constraints are named differently - var items = - db.Fetch( - "SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = @TableSchema", - new { TableSchema = db.Connection.Database }); - list = items.Select(item => new Tuple(item.TABLE_NAME, item.CONSTRAINT_NAME)).ToList(); - } - finally - { - db.CloseSharedConnection(); - } - return list; - } + //Does not include indexes and constraints are named differently + var items = + db.Fetch( + "SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = @TableSchema", + new { TableSchema = db.Connection.Database }); + list = items.Select(item => new Tuple(item.TABLE_NAME, item.CONSTRAINT_NAME)).ToList(); + } + finally + { + db.CloseSharedConnection(); + } + return list; + } - public override IEnumerable> GetConstraintsPerColumn(Database db) - { - List> list; - try - { - //needs to be open to read the schema name - db.OpenSharedConnection(); + public override IEnumerable> GetConstraintsPerColumn(Database db) + { + List> list; + try + { + //needs to be open to read the schema name + db.OpenSharedConnection(); - //Does not include indexes and constraints are named differently - var items = - db.Fetch( - "SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = @TableSchema", - new { TableSchema = db.Connection.Database }); - list = - items.Select( - item => - new Tuple(item.TABLE_NAME, item.COLUMN_NAME, item.CONSTRAINT_NAME)) - .ToList(); - } - finally - { - db.CloseSharedConnection(); - } - return list; - } + //Does not include indexes and constraints are named differently + var items = + db.Fetch( + "SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = @TableSchema", + new { TableSchema = db.Connection.Database }); + list = + items.Select( + item => + new Tuple(item.TABLE_NAME, item.COLUMN_NAME, item.CONSTRAINT_NAME)) + .ToList(); + } + finally + { + db.CloseSharedConnection(); + } + return list; + } - public override IEnumerable> GetDefinedIndexes(Database db) - { - List> list; - try - { - //needs to be open to read the schema name - db.OpenSharedConnection(); + public override IEnumerable> GetDefinedIndexes(Database db) + { + List> list; + try + { + //needs to be open to read the schema name + db.OpenSharedConnection(); - var indexes = - db.Fetch(@"SELECT DISTINCT + var indexes = + db.Fetch(@"SELECT DISTINCT TABLE_NAME, INDEX_NAME, COLUMN_NAME, CASE NON_UNIQUE WHEN 1 THEN 0 ELSE 1 END AS `UNIQUE` FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = @TableSchema AND INDEX_NAME <> COLUMN_NAME AND INDEX_NAME <> 'PRIMARY' ORDER BY TABLE_NAME, INDEX_NAME", - new { TableSchema = db.Connection.Database }); - list = - indexes.Select( - item => - new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, item.UNIQUE == 1)) - .ToList(); - } - finally - { - db.CloseSharedConnection(); - } - return list; - } + new { TableSchema = db.Connection.Database }); + list = + indexes.Select( + item => + new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, item.UNIQUE == 1)) + .ToList(); + } + finally + { + db.CloseSharedConnection(); + } + return list; + } - public override bool DoesTableExist(Database db, string tableName) - { - long result; - try - { - //needs to be open to read the schema name - db.OpenSharedConnection(); + public override bool DoesTableExist(Database db, string tableName) + { + long result; + try + { + //needs to be open to read the schema name + db.OpenSharedConnection(); - result = - db.ExecuteScalar("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES " + - "WHERE TABLE_NAME = @TableName AND " + - "TABLE_SCHEMA = @TableSchema", - new { TableName = tableName, TableSchema = db.Connection.Database }); + result = + db.ExecuteScalar("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES " + + "WHERE TABLE_NAME = @TableName AND " + + "TABLE_SCHEMA = @TableSchema", + new { TableName = tableName, TableSchema = db.Connection.Database }); - } - finally - { - db.CloseSharedConnection(); - } + } + finally + { + db.CloseSharedConnection(); + } - return result > 0; - } + return result > 0; + } - public override bool SupportsClustered() - { - return true; - } + public override bool SupportsClustered() + { + return true; + } - public override bool SupportsIdentityInsert() - { - return false; - } + public override bool SupportsIdentityInsert() + { + return false; + } - /// - /// This is used ONLY if we need to format datetime without using SQL parameters (i.e. during migrations) - /// - /// - /// - /// - /// - /// MySQL has a DateTime standard that is unambiguous and works on all servers: - /// YYYYMMDDHHMMSS - /// - public override string FormatDateTime(DateTime date, bool includeTime = true) - { - return includeTime ? date.ToString("yyyyMMddHHmmss") : date.ToString("yyyyMMdd"); - } + /// + /// This is used ONLY if we need to format datetime without using SQL parameters (i.e. during migrations) + /// + /// + /// + /// + /// + /// MySQL has a DateTime standard that is unambiguous and works on all servers: + /// YYYYMMDDHHMMSS + /// + public override string FormatDateTime(DateTime date, bool includeTime = true) + { + return includeTime ? date.ToString("yyyyMMddHHmmss") : date.ToString("yyyyMMdd"); + } - public override string GetQuotedTableName(string tableName) - { - return string.Format("`{0}`", tableName); - } + public override string GetQuotedTableName(string tableName) + { + return string.Format("`{0}`", tableName); + } - public override string GetQuotedColumnName(string columnName) - { - return string.Format("`{0}`", columnName); - } + public override string GetQuotedColumnName(string columnName) + { + return string.Format("`{0}`", columnName); + } - public override string GetQuotedName(string name) - { - return string.Format("`{0}`", name); - } + public override string GetQuotedName(string name) + { + return string.Format("`{0}`", name); + } - public override string GetSpecialDbType(SpecialDbTypes dbTypes) - { - if (dbTypes == SpecialDbTypes.NCHAR) - { - return "CHAR"; - } - else if (dbTypes == SpecialDbTypes.NTEXT) - return "LONGTEXT"; + public override string GetSpecialDbType(SpecialDbTypes dbTypes) + { + if (dbTypes == SpecialDbTypes.NCHAR) + { + return "CHAR"; + } + else if (dbTypes == SpecialDbTypes.NTEXT) + return "LONGTEXT"; - return "NVARCHAR"; - } + return "NVARCHAR"; + } - public override string Format(TableDefinition table) - { - string primaryKey = string.Empty; - var columnDefinition = table.Columns.FirstOrDefault(x => x.IsPrimaryKey); - if (columnDefinition != null) - { - string columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns) - ? GetQuotedColumnName(columnDefinition.Name) - : string.Join(", ", columnDefinition.PrimaryKeyColumns - .Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(GetQuotedColumnName)); + public override string Format(TableDefinition table) + { + string primaryKey = string.Empty; + var columnDefinition = table.Columns.FirstOrDefault(x => x.IsPrimaryKey); + if (columnDefinition != null) + { + string columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns) + ? GetQuotedColumnName(columnDefinition.Name) + : string.Join(", ", columnDefinition.PrimaryKeyColumns + .Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + .Select(GetQuotedColumnName)); - primaryKey = string.Format(", \nPRIMARY KEY {0} ({1})", columnDefinition.IsIndexed ? "CLUSTERED" : "NONCLUSTERED", columns); - } + primaryKey = string.Format(", \nPRIMARY KEY {0} ({1})", columnDefinition.IsIndexed ? "CLUSTERED" : "NONCLUSTERED", columns); + } - var statement = string.Format(CreateTable, GetQuotedTableName(table.Name), Format(table.Columns), primaryKey); + var statement = string.Format(CreateTable, GetQuotedTableName(table.Name), Format(table.Columns), primaryKey); - return statement; - } + return statement; + } - public override string Format(IndexDefinition index) - { - string name = string.IsNullOrEmpty(index.Name) - ? string.Format("IX_{0}_{1}", index.TableName, index.ColumnName) - : index.Name; + public override string Format(IndexDefinition index) + { + string name = string.IsNullOrEmpty(index.Name) + ? string.Format("IX_{0}_{1}", index.TableName, index.ColumnName) + : index.Name; - string columns = index.Columns.Any() - ? string.Join(",", index.Columns.Select(x => GetQuotedColumnName(x.Name))) - : GetQuotedColumnName(index.ColumnName); + string columns = index.Columns.Any() + ? string.Join(",", index.Columns.Select(x => GetQuotedColumnName(x.Name))) + : GetQuotedColumnName(index.ColumnName); - return string.Format(CreateIndex, - GetQuotedName(name), - GetQuotedTableName(index.TableName), - columns); - } + return string.Format(CreateIndex, + GetQuotedName(name), + GetQuotedTableName(index.TableName), + columns); + } - public override string Format(ForeignKeyDefinition foreignKey) - { - return string.Format(CreateForeignKeyConstraint, - GetQuotedTableName(foreignKey.ForeignTable), - GetQuotedColumnName(foreignKey.ForeignColumns.First()), - GetQuotedTableName(foreignKey.PrimaryTable), - GetQuotedColumnName(foreignKey.PrimaryColumns.First()), - FormatCascade("DELETE", foreignKey.OnDelete), - FormatCascade("UPDATE", foreignKey.OnUpdate)); - } + public override string Format(ForeignKeyDefinition foreignKey) + { + return string.Format(CreateForeignKeyConstraint, + GetQuotedTableName(foreignKey.ForeignTable), + GetQuotedColumnName(foreignKey.ForeignColumns.First()), + GetQuotedTableName(foreignKey.PrimaryTable), + GetQuotedColumnName(foreignKey.PrimaryColumns.First()), + FormatCascade("DELETE", foreignKey.OnDelete), + FormatCascade("UPDATE", foreignKey.OnUpdate)); + } - public override string FormatPrimaryKey(TableDefinition table) - { - return string.Empty; - } + public override string FormatPrimaryKey(TableDefinition table) + { + return string.Empty; + } - protected override string FormatConstraint(ColumnDefinition column) - { - return string.Empty; - } + protected override string FormatConstraint(ColumnDefinition column) + { + return string.Empty; + } - protected override string FormatIdentity(ColumnDefinition column) - { - return column.IsIdentity ? AutoIncrementDefinition : string.Empty; - } + protected override string FormatIdentity(ColumnDefinition column) + { + return column.IsIdentity ? AutoIncrementDefinition : string.Empty; + } - protected override string FormatDefaultValue(ColumnDefinition column) - { - if (column.DefaultValue == null) - return string.Empty; + protected override string FormatDefaultValue(ColumnDefinition column) + { + if (column.DefaultValue == null) + return string.Empty; - //hack - probably not needed with latest changes - if (column.DefaultValue.ToString().ToLower().Equals("getdate()".ToLower())) - column.DefaultValue = SystemMethods.CurrentDateTime; + //hack - probably not needed with latest changes + if (column.DefaultValue.ToString().ToLower().Equals("getdate()".ToLower())) + column.DefaultValue = SystemMethods.CurrentDateTime; - // see if this is for a system method - if (column.DefaultValue is SystemMethods) - { - string method = FormatSystemMethods((SystemMethods)column.DefaultValue); - if (string.IsNullOrEmpty(method)) - return string.Empty; + // see if this is for a system method + if (column.DefaultValue is SystemMethods) + { + string method = FormatSystemMethods((SystemMethods)column.DefaultValue); + if (string.IsNullOrEmpty(method)) + return string.Empty; - return string.Format(DefaultValueFormat, method); - } + return string.Format(DefaultValueFormat, method); + } - //needs quote - return string.Format(DefaultValueFormat, string.Format("'{0}'", column.DefaultValue)); - } + //needs quote + return string.Format(DefaultValueFormat, string.Format("'{0}'", column.DefaultValue)); + } - protected override string FormatPrimaryKey(ColumnDefinition column) - { - return string.Empty; - } + protected override string FormatPrimaryKey(ColumnDefinition column) + { + return string.Empty; + } - protected override string FormatSystemMethods(SystemMethods systemMethod) - { - switch (systemMethod) - { - case SystemMethods.NewGuid: - return null; // NOT SUPPORTED! - //return "NEWID()"; - case SystemMethods.CurrentDateTime: - return "CURRENT_TIMESTAMP"; - //case SystemMethods.NewSequentialId: - // return "NEWSEQUENTIALID()"; - //case SystemMethods.CurrentUTCDateTime: - // return "GETUTCDATE()"; - } + protected override string FormatSystemMethods(SystemMethods systemMethod) + { + switch (systemMethod) + { + case SystemMethods.NewGuid: + return null; // NOT SUPPORTED! + //return "NEWID()"; + case SystemMethods.CurrentDateTime: + return "CURRENT_TIMESTAMP"; + //case SystemMethods.NewSequentialId: + // return "NEWSEQUENTIALID()"; + //case SystemMethods.CurrentUTCDateTime: + // return "GETUTCDATE()"; + } - return null; - } + return null; + } - public override string DeleteDefaultConstraint - { - get - { - return "ALTER TABLE {0} ALTER COLUMN {1} DROP DEFAULT"; - } - } + public override string DeleteDefaultConstraint + { + get + { + return "ALTER TABLE {0} ALTER COLUMN {1} DROP DEFAULT"; + } + } - public override string AlterColumn { get { return "ALTER TABLE {0} MODIFY COLUMN {1}"; } } + public override string AlterColumn { get { return "ALTER TABLE {0} MODIFY COLUMN {1}"; } } - //CREATE TABLE {0} ({1}) ENGINE = INNODB versus CREATE TABLE {0} ({1}) ENGINE = MYISAM ? - public override string CreateTable { get { return "CREATE TABLE {0} ({1}{2})"; } } + //CREATE TABLE {0} ({1}) ENGINE = INNODB versus CREATE TABLE {0} ({1}) ENGINE = MYISAM ? + public override string CreateTable { get { return "CREATE TABLE {0} ({1}{2})"; } } - public override string CreateIndex { get { return "CREATE INDEX {0} ON {1} ({2})"; } } + public override string CreateIndex { get { return "CREATE INDEX {0} ON {1} ({2})"; } } - public override string CreateForeignKeyConstraint { get { return "ALTER TABLE {0} ADD FOREIGN KEY ({1}) REFERENCES {2} ({3}){4}{5}"; } } + public override string CreateForeignKeyConstraint { get { return "ALTER TABLE {0} ADD FOREIGN KEY ({1}) REFERENCES {2} ({3}){4}{5}"; } } - public override string DeleteConstraint { get { return "ALTER TABLE {0} DROP {1} {2}"; } } + public override string DeleteConstraint { get { return "ALTER TABLE {0} DROP {1} {2}"; } } - public override string DropIndex { get { return "DROP INDEX {0} ON {1}"; } } + public override string DropIndex { get { return "DROP INDEX {0} ON {1}"; } } - public override string RenameColumn { get { return "ALTER TABLE {0} CHANGE {1} {2}"; } } - public override string IsNull { get { return "IFNULL({0},{1})"; } } - public override string ConvertIntegerToOrderableString { get { return "LPAD({0}, 8, '0')"; } } - public override string ConvertDateToOrderableString { get { return "DATE_FORMAT({0}, '%Y%m%d')"; } } + public override string RenameColumn { get { return "ALTER TABLE {0} CHANGE {1} {2}"; } } + public override string IsNull { get { return "IFNULL({0},{1})"; } } + public override string ConvertIntegerToOrderableString { get { return "LPAD({0}, 8, '0')"; } } + public override string ConvertDateToOrderableString { get { return "DATE_FORMAT({0}, '%Y%m%d')"; } } - public override bool? SupportsCaseInsensitiveQueries(Database db) - { - bool? supportsCaseInsensitiveQueries = null; + public override bool? SupportsCaseInsensitiveQueries(Database db) + { + bool? supportsCaseInsensitiveQueries = null; - try - { - db.OpenSharedConnection(); - // Need 4 @ signs as it is regarded as a parameter, @@ escapes it once, @@@@ escapes it twice - var lowerCaseTableNames = db.Fetch("SELECT @@@@Global.lower_case_table_names"); + try + { + db.OpenSharedConnection(); + // Need 4 @ signs as it is regarded as a parameter, @@ escapes it once, @@@@ escapes it twice + var lowerCaseTableNames = db.Fetch("SELECT @@@@Global.lower_case_table_names"); - if (lowerCaseTableNames.Any()) - supportsCaseInsensitiveQueries = lowerCaseTableNames.First() == 1; - } - catch (Exception ex) - { - _logger.Error("Error querying for lower_case support", ex); - } - finally - { - db.CloseSharedConnection(); - } + if (lowerCaseTableNames.Any()) + supportsCaseInsensitiveQueries = lowerCaseTableNames.First() == 1; + } + catch (Exception ex) + { + _logger.Error("Error querying for lower_case support", ex); + } + finally + { + db.CloseSharedConnection(); + } - // Could return null, which means testing failed, - // add message to check with their hosting provider - return supportsCaseInsensitiveQueries; - } + // Could return null, which means testing failed, + // add message to check with their hosting provider + return supportsCaseInsensitiveQueries; + } - public override string EscapeString(string val) - { - return PetaPocoExtensions.EscapeAtSymbols(MySql.Data.MySqlClient.MySqlHelper.EscapeString(val)); - } - } + public override string EscapeString(string val) + { + return PetaPocoExtensions.EscapeAtSymbols(MySql.Data.MySqlClient.MySqlHelper.EscapeString(val)); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index 3b3fe103d0..35c133ce6e 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -10,535 +10,535 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.SqlSyntax { - /// - /// Represents the Base Sql Syntax provider implementation. - /// - /// - /// All Sql Syntax provider implementations should derive from this abstract class. - /// - /// - public abstract class SqlSyntaxProviderBase : ISqlSyntaxProvider - where TSyntax : ISqlSyntaxProvider - { - protected SqlSyntaxProviderBase() - { - ClauseOrder = new List> - { - FormatString, - FormatType, - FormatNullable, - FormatConstraint, - FormatDefaultValue, - FormatPrimaryKey, - FormatIdentity - }; - - //defaults for all providers - StringLengthColumnDefinitionFormat = StringLengthUnicodeColumnDefinitionFormat; - StringColumnDefinition = string.Format(StringLengthColumnDefinitionFormat, DefaultStringLength); - DecimalColumnDefinition = string.Format(DecimalColumnDefinitionFormat, DefaultDecimalPrecision, DefaultDecimalScale); - - InitColumnTypeMap(); - } - - public string GetWildcardPlaceholder() - { - return "%"; - } - - public string StringLengthNonUnicodeColumnDefinitionFormat = "VARCHAR({0})"; - public string StringLengthUnicodeColumnDefinitionFormat = "NVARCHAR({0})"; - public string DecimalColumnDefinitionFormat = "DECIMAL({0},{1})"; - - public string DefaultValueFormat = "DEFAULT ({0})"; - public int DefaultStringLength = 255; - public int DefaultDecimalPrecision = 20; - public int DefaultDecimalScale = 9; - - //Set by Constructor - public string StringColumnDefinition; - public string StringLengthColumnDefinitionFormat; - - public string AutoIncrementDefinition = "AUTOINCREMENT"; - public string IntColumnDefinition = "INTEGER"; - public string LongColumnDefinition = "BIGINT"; - public string GuidColumnDefinition = "GUID"; - public string BoolColumnDefinition = "BOOL"; - public string RealColumnDefinition = "DOUBLE"; - public string DecimalColumnDefinition; - public string BlobColumnDefinition = "BLOB"; - public string DateTimeColumnDefinition = "DATETIME"; - public string TimeColumnDefinition = "DATETIME"; - - protected IList> ClauseOrder { get; set; } - - protected DbTypes DbTypeMap = new DbTypes(); - protected void InitColumnTypeMap() - { - DbTypeMap.Set(DbType.String, StringColumnDefinition); - DbTypeMap.Set(DbType.StringFixedLength, StringColumnDefinition); - DbTypeMap.Set(DbType.StringFixedLength, StringColumnDefinition); - DbTypeMap.Set(DbType.String, StringColumnDefinition); - DbTypeMap.Set(DbType.Boolean, BoolColumnDefinition); - DbTypeMap.Set(DbType.Boolean, BoolColumnDefinition); - DbTypeMap.Set(DbType.Guid, GuidColumnDefinition); - DbTypeMap.Set(DbType.Guid, GuidColumnDefinition); - DbTypeMap.Set(DbType.DateTime, DateTimeColumnDefinition); - DbTypeMap.Set(DbType.DateTime, DateTimeColumnDefinition); - DbTypeMap.Set(DbType.Time, TimeColumnDefinition); - DbTypeMap.Set(DbType.Time, TimeColumnDefinition); - DbTypeMap.Set(DbType.Time, TimeColumnDefinition); - DbTypeMap.Set(DbType.Time, TimeColumnDefinition); - - DbTypeMap.Set(DbType.Byte, IntColumnDefinition); - DbTypeMap.Set(DbType.Byte, IntColumnDefinition); - DbTypeMap.Set(DbType.SByte, IntColumnDefinition); - DbTypeMap.Set(DbType.SByte, IntColumnDefinition); - DbTypeMap.Set(DbType.Int16, IntColumnDefinition); - DbTypeMap.Set(DbType.Int16, IntColumnDefinition); - DbTypeMap.Set(DbType.UInt16, IntColumnDefinition); - DbTypeMap.Set(DbType.UInt16, IntColumnDefinition); - DbTypeMap.Set(DbType.Int32, IntColumnDefinition); - DbTypeMap.Set(DbType.Int32, IntColumnDefinition); - DbTypeMap.Set(DbType.UInt32, IntColumnDefinition); - DbTypeMap.Set(DbType.UInt32, IntColumnDefinition); - - DbTypeMap.Set(DbType.Int64, LongColumnDefinition); - DbTypeMap.Set(DbType.Int64, LongColumnDefinition); - DbTypeMap.Set(DbType.UInt64, LongColumnDefinition); - DbTypeMap.Set(DbType.UInt64, LongColumnDefinition); - - DbTypeMap.Set(DbType.Single, RealColumnDefinition); - DbTypeMap.Set(DbType.Single, RealColumnDefinition); - DbTypeMap.Set(DbType.Double, RealColumnDefinition); - DbTypeMap.Set(DbType.Double, RealColumnDefinition); - - DbTypeMap.Set(DbType.Decimal, DecimalColumnDefinition); - DbTypeMap.Set(DbType.Decimal, DecimalColumnDefinition); - - DbTypeMap.Set(DbType.Binary, BlobColumnDefinition); - } - - public virtual string EscapeString(string val) - { - return PetaPocoExtensions.EscapeAtSymbols(val.Replace("'", "''")); - } - - public virtual string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return string.Format("upper({0}) = upper(@{1})", column, paramIndex); - } - - public virtual string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return string.Format("upper({0}) LIKE upper(@{1})", column, paramIndex); - } - - [Obsolete("Use the overload with the parameter index instead")] - public virtual string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return string.Format("upper({0}) = '{1}'", column, value.ToUpper()); - } - - [Obsolete("Use the overload with the parameter index instead")] - public virtual string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return string.Format("upper({0}) LIKE '{1}%'", column, value.ToUpper()); - } - - [Obsolete("Use the overload with the parameter index instead")] - public virtual string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return string.Format("upper({0}) LIKE '%{1}'", column, value.ToUpper()); - } - - [Obsolete("Use the overload with the parameter index instead")] - public virtual string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return string.Format("upper({0}) LIKE '%{1}%'", column, value.ToUpper()); - } - - [Obsolete("Use the overload with the parameter index instead")] - public virtual string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType) - { - //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. - return string.Format("upper({0}) LIKE '{1}'", column, value.ToUpper()); - } - - public virtual string GetQuotedTableName(string tableName) - { - return string.Format("\"{0}\"", tableName); - } - - public virtual string GetQuotedColumnName(string columnName) - { - return string.Format("\"{0}\"", columnName); - } - - public virtual string GetQuotedName(string name) - { - return string.Format("\"{0}\"", name); - } - - public virtual string GetQuotedValue(string value) - { - return string.Format("'{0}'", value); - } - - public virtual string GetIndexType(IndexTypes indexTypes) - { - string indexType; - - if (indexTypes == IndexTypes.Clustered) - { - indexType = "CLUSTERED"; - } - else - { - indexType = indexTypes == IndexTypes.NonClustered - ? "NONCLUSTERED" - : "UNIQUE NONCLUSTERED"; - } - return indexType; - } - - public virtual string GetSpecialDbType(SpecialDbTypes dbTypes) - { - if (dbTypes == SpecialDbTypes.NCHAR) - { - return "NCHAR"; - } - else if (dbTypes == SpecialDbTypes.NTEXT) - return "NTEXT"; - - return "NVARCHAR"; - } - - public virtual bool? SupportsCaseInsensitiveQueries(Database db) - { - return true; - } - - public virtual IEnumerable GetTablesInSchema(Database db) - { - return new List(); - } - - public virtual IEnumerable GetColumnsInSchema(Database db) - { - return new List(); - } - - public virtual IEnumerable> GetConstraintsPerTable(Database db) - { - return new List>(); - } - - public virtual IEnumerable> GetConstraintsPerColumn(Database db) - { - return new List>(); - } - - public abstract IEnumerable> GetDefinedIndexes(Database db); - - public virtual bool DoesTableExist(Database db, string tableName) - { - return false; - } - - public virtual bool SupportsClustered() - { - return true; - } - - public virtual bool SupportsIdentityInsert() - { - return true; - } - - /// - /// This is used ONLY if we need to format datetime without using SQL parameters (i.e. during migrations) - /// - /// - /// - /// - /// - /// MSSQL has a DateTime standard that is unambiguous and works on all servers: - /// YYYYMMDD HH:mm:ss - /// - public virtual string FormatDateTime(DateTime date, bool includeTime = true) - { - // need CultureInfo.InvariantCulture because ":" here is the "time separator" and - // may be converted to something else in different cultures (eg "." in DK). - return date.ToString(includeTime ? "yyyyMMdd HH:mm:ss" : "yyyyMMdd", CultureInfo.InvariantCulture); - } - - public virtual string Format(TableDefinition table) - { - var statement = string.Format(CreateTable, GetQuotedTableName(table.Name), Format(table.Columns)); - - return statement; - } - - public virtual List Format(IEnumerable indexes) - { - return indexes.Select(Format).ToList(); - } - - public virtual string Format(IndexDefinition index) - { - string name = string.IsNullOrEmpty(index.Name) - ? string.Format("IX_{0}_{1}", index.TableName, index.ColumnName) - : index.Name; - - string columns = index.Columns.Any() - ? string.Join(",", index.Columns.Select(x => GetQuotedColumnName(x.Name))) - : GetQuotedColumnName(index.ColumnName); - - return string.Format(CreateIndex, GetIndexType(index.IndexType), " ", GetQuotedName(name), - GetQuotedTableName(index.TableName), columns); - } - - public virtual List Format(IEnumerable foreignKeys) - { - return foreignKeys.Select(Format).ToList(); - } - - public virtual string Format(ForeignKeyDefinition foreignKey) - { - string constraintName = string.IsNullOrEmpty(foreignKey.Name) - ? string.Format("FK_{0}_{1}_{2}", foreignKey.ForeignTable, foreignKey.PrimaryTable, foreignKey.PrimaryColumns.First()) - : foreignKey.Name; - - return string.Format(CreateForeignKeyConstraint, - GetQuotedTableName(foreignKey.ForeignTable), - GetQuotedName(constraintName), - GetQuotedColumnName(foreignKey.ForeignColumns.First()), - GetQuotedTableName(foreignKey.PrimaryTable), - GetQuotedColumnName(foreignKey.PrimaryColumns.First()), - FormatCascade("DELETE", foreignKey.OnDelete), - FormatCascade("UPDATE", foreignKey.OnUpdate)); - } - - public virtual string Format(IEnumerable columns) - { - var sb = new StringBuilder(); - foreach (var column in columns) - { - sb.Append(Format(column) + ",\n"); - } - return sb.ToString().TrimEnd(",\n"); - } - - public virtual string Format(ColumnDefinition column) - { - var clauses = new List(); - - foreach (var action in ClauseOrder) - { - string clause = action(column); - if (!string.IsNullOrEmpty(clause)) - clauses.Add(clause); - } - - return string.Join(" ", clauses.ToArray()); - } - - public virtual string FormatPrimaryKey(TableDefinition table) - { - var columnDefinition = table.Columns.FirstOrDefault(x => x.IsPrimaryKey); - if (columnDefinition == null) - return string.Empty; - - string constraintName = string.IsNullOrEmpty(columnDefinition.PrimaryKeyName) - ? string.Format("PK_{0}", table.Name) - : columnDefinition.PrimaryKeyName; - - string columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns) - ? GetQuotedColumnName(columnDefinition.Name) - : string.Join(", ", columnDefinition.PrimaryKeyColumns - .Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(GetQuotedColumnName)); - - string primaryKeyPart = string.Concat("PRIMARY KEY", columnDefinition.IsIndexed ? " CLUSTERED" : " NONCLUSTERED"); - - return string.Format(CreateConstraint, - GetQuotedTableName(table.Name), - GetQuotedName(constraintName), - primaryKeyPart, - columns); - } - - public virtual string FormatColumnRename(string tableName, string oldName, string newName) - { - return string.Format(RenameColumn, - GetQuotedTableName(tableName), - GetQuotedColumnName(oldName), - GetQuotedColumnName(newName)); - } - - public virtual string FormatTableRename(string oldName, string newName) - { - return string.Format(RenameTable, GetQuotedTableName(oldName), GetQuotedTableName(newName)); - } - - protected virtual string FormatCascade(string onWhat, Rule rule) - { - string action = "NO ACTION"; - switch (rule) - { - case Rule.None: - return ""; - case Rule.Cascade: - action = "CASCADE"; - break; - case Rule.SetNull: - action = "SET NULL"; - break; - case Rule.SetDefault: - action = "SET DEFAULT"; - break; - } - - return string.Format(" ON {0} {1}", onWhat, action); - } - - protected virtual string FormatString(ColumnDefinition column) - { - return GetQuotedColumnName(column.Name); - } - - protected virtual string FormatType(ColumnDefinition column) - { - if (column.Type.HasValue == false && string.IsNullOrEmpty(column.CustomType) == false) - return column.CustomType; - - if (column.HasSpecialDbType) - { - if (column.Size != default(int)) - { - return string.Format("{0}({1})", - GetSpecialDbType(column.DbType), - column.Size); - } - - return GetSpecialDbType(column.DbType); - } - - Type type = column.Type.HasValue - ? DbTypeMap.ColumnDbTypeMap.First(x => x.Value == column.Type.Value).Key - : column.PropertyType; - - if (type == typeof(string)) - { - var valueOrDefault = column.Size != default(int) ? column.Size : DefaultStringLength; - return string.Format(StringLengthColumnDefinitionFormat, valueOrDefault); - } - - if (type == typeof(decimal)) - { - var precision = column.Size != default(int) ? column.Size : DefaultDecimalPrecision; - var scale = column.Precision != default(int) ? column.Precision : DefaultDecimalScale; - return string.Format(DecimalColumnDefinitionFormat, precision, scale); - } - - string definition = DbTypeMap.ColumnTypeMap.First(x => x.Key == type).Value; - string dbTypeDefinition = column.Size != default(int) - ? string.Format("{0}({1})", definition, column.Size) - : definition; - //NOTE Percision is left out - return dbTypeDefinition; - } - - protected virtual string FormatNullable(ColumnDefinition column) - { - return column.IsNullable ? "NULL" : "NOT NULL"; - } - - protected virtual string FormatConstraint(ColumnDefinition column) - { - if (string.IsNullOrEmpty(column.ConstraintName) && column.DefaultValue == null) - return string.Empty; - - return string.Format("CONSTRAINT {0}", - string.IsNullOrEmpty(column.ConstraintName) - ? GetQuotedName(string.Format("DF_{0}_{1}", column.TableName, column.Name)) - : column.ConstraintName); - } - - protected virtual string FormatDefaultValue(ColumnDefinition column) - { - if (column.DefaultValue == null) - return string.Empty; - - //hack - probably not needed with latest changes - if (column.DefaultValue.ToString().ToLower().Equals("getdate()".ToLower())) - column.DefaultValue = SystemMethods.CurrentDateTime; - - // see if this is for a system method - if (column.DefaultValue is SystemMethods) - { - string method = FormatSystemMethods((SystemMethods)column.DefaultValue); - if (string.IsNullOrEmpty(method)) - return string.Empty; - - return string.Format(DefaultValueFormat, method); - } - - return string.Format(DefaultValueFormat, GetQuotedValue(column.DefaultValue.ToString())); - } - - protected virtual string FormatPrimaryKey(ColumnDefinition column) - { - return string.Empty; - } - - protected abstract string FormatSystemMethods(SystemMethods systemMethod); - - protected abstract string FormatIdentity(ColumnDefinition column); - - public virtual string DeleteDefaultConstraint - { - get - { - throw new NotSupportedException("Default constraints are not supported"); - } - } - - public virtual string CreateTable { get { return "CREATE TABLE {0} ({1})"; } } - public virtual string DropTable { get { return "DROP TABLE {0}"; } } - - public virtual string AddColumn { get { return "ALTER TABLE {0} ADD COLUMN {1}"; } } - public virtual string DropColumn { get { return "ALTER TABLE {0} DROP COLUMN {1}"; } } - public virtual string AlterColumn { get { return "ALTER TABLE {0} ALTER COLUMN {1}"; } } - public virtual string RenameColumn { get { return "ALTER TABLE {0} RENAME COLUMN {1} TO {2}"; } } - - public virtual string RenameTable { get { return "RENAME TABLE {0} TO {1}"; } } - - public virtual string CreateSchema { get { return "CREATE SCHEMA {0}"; } } - public virtual string AlterSchema { get { return "ALTER SCHEMA {0} TRANSFER {1}.{2}"; } } - public virtual string DropSchema { get { return "DROP SCHEMA {0}"; } } - - public virtual string CreateIndex { get { return "CREATE {0}{1}INDEX {2} ON {3} ({4})"; } } - public virtual string DropIndex { get { return "DROP INDEX {0}"; } } - - public virtual string InsertData { get { return "INSERT INTO {0} ({1}) VALUES ({2})"; } } - public virtual string UpdateData { get { return "UPDATE {0} SET {1} WHERE {2}"; } } - public virtual string DeleteData { get { return "DELETE FROM {0} WHERE {1}"; } } - public virtual string TruncateTable { get { return "TRUNCATE TABLE {0}"; } } - - public virtual string CreateConstraint { get { return "ALTER TABLE {0} ADD CONSTRAINT {1} {2} ({3})"; } } - public virtual string DeleteConstraint { get { return "ALTER TABLE {0} DROP CONSTRAINT {1}"; } } - public virtual string CreateForeignKeyConstraint { get { return "ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4}){5}{6}"; } } - - public virtual string IsNull { get { return "ISNULL({0},{1})"; } } - public virtual string ConvertIntegerToOrderableString { get { return "RIGHT('00000000' + CAST({0} AS varchar(8)),8)"; } } - public virtual string ConvertDateToOrderableString { get { return "CONVERT(varchar, {0}, 102)"; } } - } + /// + /// Represents the Base Sql Syntax provider implementation. + /// + /// + /// All Sql Syntax provider implementations should derive from this abstract class. + /// + /// + public abstract class SqlSyntaxProviderBase : ISqlSyntaxProvider + where TSyntax : ISqlSyntaxProvider + { + protected SqlSyntaxProviderBase() + { + ClauseOrder = new List> + { + FormatString, + FormatType, + FormatNullable, + FormatConstraint, + FormatDefaultValue, + FormatPrimaryKey, + FormatIdentity + }; + + //defaults for all providers + StringLengthColumnDefinitionFormat = StringLengthUnicodeColumnDefinitionFormat; + StringColumnDefinition = string.Format(StringLengthColumnDefinitionFormat, DefaultStringLength); + DecimalColumnDefinition = string.Format(DecimalColumnDefinitionFormat, DefaultDecimalPrecision, DefaultDecimalScale); + + InitColumnTypeMap(); + } + + public string GetWildcardPlaceholder() + { + return "%"; + } + + public string StringLengthNonUnicodeColumnDefinitionFormat = "VARCHAR({0})"; + public string StringLengthUnicodeColumnDefinitionFormat = "NVARCHAR({0})"; + public string DecimalColumnDefinitionFormat = "DECIMAL({0},{1})"; + + public string DefaultValueFormat = "DEFAULT ({0})"; + public int DefaultStringLength = 255; + public int DefaultDecimalPrecision = 20; + public int DefaultDecimalScale = 9; + + //Set by Constructor + public string StringColumnDefinition; + public string StringLengthColumnDefinitionFormat; + + public string AutoIncrementDefinition = "AUTOINCREMENT"; + public string IntColumnDefinition = "INTEGER"; + public string LongColumnDefinition = "BIGINT"; + public string GuidColumnDefinition = "GUID"; + public string BoolColumnDefinition = "BOOL"; + public string RealColumnDefinition = "DOUBLE"; + public string DecimalColumnDefinition; + public string BlobColumnDefinition = "BLOB"; + public string DateTimeColumnDefinition = "DATETIME"; + public string TimeColumnDefinition = "DATETIME"; + + protected IList> ClauseOrder { get; set; } + + protected DbTypes DbTypeMap = new DbTypes(); + protected void InitColumnTypeMap() + { + DbTypeMap.Set(DbType.String, StringColumnDefinition); + DbTypeMap.Set(DbType.StringFixedLength, StringColumnDefinition); + DbTypeMap.Set(DbType.StringFixedLength, StringColumnDefinition); + DbTypeMap.Set(DbType.String, StringColumnDefinition); + DbTypeMap.Set(DbType.Boolean, BoolColumnDefinition); + DbTypeMap.Set(DbType.Boolean, BoolColumnDefinition); + DbTypeMap.Set(DbType.Guid, GuidColumnDefinition); + DbTypeMap.Set(DbType.Guid, GuidColumnDefinition); + DbTypeMap.Set(DbType.DateTime, DateTimeColumnDefinition); + DbTypeMap.Set(DbType.DateTime, DateTimeColumnDefinition); + DbTypeMap.Set(DbType.Time, TimeColumnDefinition); + DbTypeMap.Set(DbType.Time, TimeColumnDefinition); + DbTypeMap.Set(DbType.Time, TimeColumnDefinition); + DbTypeMap.Set(DbType.Time, TimeColumnDefinition); + + DbTypeMap.Set(DbType.Byte, IntColumnDefinition); + DbTypeMap.Set(DbType.Byte, IntColumnDefinition); + DbTypeMap.Set(DbType.SByte, IntColumnDefinition); + DbTypeMap.Set(DbType.SByte, IntColumnDefinition); + DbTypeMap.Set(DbType.Int16, IntColumnDefinition); + DbTypeMap.Set(DbType.Int16, IntColumnDefinition); + DbTypeMap.Set(DbType.UInt16, IntColumnDefinition); + DbTypeMap.Set(DbType.UInt16, IntColumnDefinition); + DbTypeMap.Set(DbType.Int32, IntColumnDefinition); + DbTypeMap.Set(DbType.Int32, IntColumnDefinition); + DbTypeMap.Set(DbType.UInt32, IntColumnDefinition); + DbTypeMap.Set(DbType.UInt32, IntColumnDefinition); + + DbTypeMap.Set(DbType.Int64, LongColumnDefinition); + DbTypeMap.Set(DbType.Int64, LongColumnDefinition); + DbTypeMap.Set(DbType.UInt64, LongColumnDefinition); + DbTypeMap.Set(DbType.UInt64, LongColumnDefinition); + + DbTypeMap.Set(DbType.Single, RealColumnDefinition); + DbTypeMap.Set(DbType.Single, RealColumnDefinition); + DbTypeMap.Set(DbType.Double, RealColumnDefinition); + DbTypeMap.Set(DbType.Double, RealColumnDefinition); + + DbTypeMap.Set(DbType.Decimal, DecimalColumnDefinition); + DbTypeMap.Set(DbType.Decimal, DecimalColumnDefinition); + + DbTypeMap.Set(DbType.Binary, BlobColumnDefinition); + } + + public virtual string EscapeString(string val) + { + return PetaPocoExtensions.EscapeAtSymbols(val.Replace("'", "''")); + } + + public virtual string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) = upper(@{1})", column, paramIndex); + } + + public virtual string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) LIKE upper(@{1})", column, paramIndex); + } + + [Obsolete("Use the overload with the parameter index instead")] + public virtual string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) = '{1}'", column, value.ToUpper()); + } + + [Obsolete("Use the overload with the parameter index instead")] + public virtual string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) LIKE '{1}%'", column, value.ToUpper()); + } + + [Obsolete("Use the overload with the parameter index instead")] + public virtual string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) LIKE '%{1}'", column, value.ToUpper()); + } + + [Obsolete("Use the overload with the parameter index instead")] + public virtual string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) LIKE '%{1}%'", column, value.ToUpper()); + } + + [Obsolete("Use the overload with the parameter index instead")] + public virtual string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType) + { + //use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting. + return string.Format("upper({0}) LIKE '{1}'", column, value.ToUpper()); + } + + public virtual string GetQuotedTableName(string tableName) + { + return string.Format("\"{0}\"", tableName); + } + + public virtual string GetQuotedColumnName(string columnName) + { + return string.Format("\"{0}\"", columnName); + } + + public virtual string GetQuotedName(string name) + { + return string.Format("\"{0}\"", name); + } + + public virtual string GetQuotedValue(string value) + { + return string.Format("'{0}'", value); + } + + public virtual string GetIndexType(IndexTypes indexTypes) + { + string indexType; + + if (indexTypes == IndexTypes.Clustered) + { + indexType = "CLUSTERED"; + } + else + { + indexType = indexTypes == IndexTypes.NonClustered + ? "NONCLUSTERED" + : "UNIQUE NONCLUSTERED"; + } + return indexType; + } + + public virtual string GetSpecialDbType(SpecialDbTypes dbTypes) + { + if (dbTypes == SpecialDbTypes.NCHAR) + { + return "NCHAR"; + } + else if (dbTypes == SpecialDbTypes.NTEXT) + return "NTEXT"; + + return "NVARCHAR"; + } + + public virtual bool? SupportsCaseInsensitiveQueries(Database db) + { + return true; + } + + public virtual IEnumerable GetTablesInSchema(Database db) + { + return new List(); + } + + public virtual IEnumerable GetColumnsInSchema(Database db) + { + return new List(); + } + + public virtual IEnumerable> GetConstraintsPerTable(Database db) + { + return new List>(); + } + + public virtual IEnumerable> GetConstraintsPerColumn(Database db) + { + return new List>(); + } + + public abstract IEnumerable> GetDefinedIndexes(Database db); + + public virtual bool DoesTableExist(Database db, string tableName) + { + return false; + } + + public virtual bool SupportsClustered() + { + return true; + } + + public virtual bool SupportsIdentityInsert() + { + return true; + } + + /// + /// This is used ONLY if we need to format datetime without using SQL parameters (i.e. during migrations) + /// + /// + /// + /// + /// + /// MSSQL has a DateTime standard that is unambiguous and works on all servers: + /// YYYYMMDD HH:mm:ss + /// + public virtual string FormatDateTime(DateTime date, bool includeTime = true) + { + // need CultureInfo.InvariantCulture because ":" here is the "time separator" and + // may be converted to something else in different cultures (eg "." in DK). + return date.ToString(includeTime ? "yyyyMMdd HH:mm:ss" : "yyyyMMdd", CultureInfo.InvariantCulture); + } + + public virtual string Format(TableDefinition table) + { + var statement = string.Format(CreateTable, GetQuotedTableName(table.Name), Format(table.Columns)); + + return statement; + } + + public virtual List Format(IEnumerable indexes) + { + return indexes.Select(Format).ToList(); + } + + public virtual string Format(IndexDefinition index) + { + string name = string.IsNullOrEmpty(index.Name) + ? string.Format("IX_{0}_{1}", index.TableName, index.ColumnName) + : index.Name; + + string columns = index.Columns.Any() + ? string.Join(",", index.Columns.Select(x => GetQuotedColumnName(x.Name))) + : GetQuotedColumnName(index.ColumnName); + + return string.Format(CreateIndex, GetIndexType(index.IndexType), " ", GetQuotedName(name), + GetQuotedTableName(index.TableName), columns); + } + + public virtual List Format(IEnumerable foreignKeys) + { + return foreignKeys.Select(Format).ToList(); + } + + public virtual string Format(ForeignKeyDefinition foreignKey) + { + string constraintName = string.IsNullOrEmpty(foreignKey.Name) + ? string.Format("FK_{0}_{1}_{2}", foreignKey.ForeignTable, foreignKey.PrimaryTable, foreignKey.PrimaryColumns.First()) + : foreignKey.Name; + + return string.Format(CreateForeignKeyConstraint, + GetQuotedTableName(foreignKey.ForeignTable), + GetQuotedName(constraintName), + GetQuotedColumnName(foreignKey.ForeignColumns.First()), + GetQuotedTableName(foreignKey.PrimaryTable), + GetQuotedColumnName(foreignKey.PrimaryColumns.First()), + FormatCascade("DELETE", foreignKey.OnDelete), + FormatCascade("UPDATE", foreignKey.OnUpdate)); + } + + public virtual string Format(IEnumerable columns) + { + var sb = new StringBuilder(); + foreach (var column in columns) + { + sb.Append(Format(column) + ",\n"); + } + return sb.ToString().TrimEnd(",\n"); + } + + public virtual string Format(ColumnDefinition column) + { + var clauses = new List(); + + foreach (var action in ClauseOrder) + { + string clause = action(column); + if (!string.IsNullOrEmpty(clause)) + clauses.Add(clause); + } + + return string.Join(" ", clauses.ToArray()); + } + + public virtual string FormatPrimaryKey(TableDefinition table) + { + var columnDefinition = table.Columns.FirstOrDefault(x => x.IsPrimaryKey); + if (columnDefinition == null) + return string.Empty; + + string constraintName = string.IsNullOrEmpty(columnDefinition.PrimaryKeyName) + ? string.Format("PK_{0}", table.Name) + : columnDefinition.PrimaryKeyName; + + string columns = string.IsNullOrEmpty(columnDefinition.PrimaryKeyColumns) + ? GetQuotedColumnName(columnDefinition.Name) + : string.Join(", ", columnDefinition.PrimaryKeyColumns + .Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) + .Select(GetQuotedColumnName)); + + string primaryKeyPart = string.Concat("PRIMARY KEY", columnDefinition.IsIndexed ? " CLUSTERED" : " NONCLUSTERED"); + + return string.Format(CreateConstraint, + GetQuotedTableName(table.Name), + GetQuotedName(constraintName), + primaryKeyPart, + columns); + } + + public virtual string FormatColumnRename(string tableName, string oldName, string newName) + { + return string.Format(RenameColumn, + GetQuotedTableName(tableName), + GetQuotedColumnName(oldName), + GetQuotedColumnName(newName)); + } + + public virtual string FormatTableRename(string oldName, string newName) + { + return string.Format(RenameTable, GetQuotedTableName(oldName), GetQuotedTableName(newName)); + } + + protected virtual string FormatCascade(string onWhat, Rule rule) + { + string action = "NO ACTION"; + switch (rule) + { + case Rule.None: + return ""; + case Rule.Cascade: + action = "CASCADE"; + break; + case Rule.SetNull: + action = "SET NULL"; + break; + case Rule.SetDefault: + action = "SET DEFAULT"; + break; + } + + return string.Format(" ON {0} {1}", onWhat, action); + } + + protected virtual string FormatString(ColumnDefinition column) + { + return GetQuotedColumnName(column.Name); + } + + protected virtual string FormatType(ColumnDefinition column) + { + if (column.Type.HasValue == false && string.IsNullOrEmpty(column.CustomType) == false) + return column.CustomType; + + if (column.HasSpecialDbType) + { + if (column.Size != default(int)) + { + return string.Format("{0}({1})", + GetSpecialDbType(column.DbType), + column.Size); + } + + return GetSpecialDbType(column.DbType); + } + + Type type = column.Type.HasValue + ? DbTypeMap.ColumnDbTypeMap.First(x => x.Value == column.Type.Value).Key + : column.PropertyType; + + if (type == typeof(string)) + { + var valueOrDefault = column.Size != default(int) ? column.Size : DefaultStringLength; + return string.Format(StringLengthColumnDefinitionFormat, valueOrDefault); + } + + if (type == typeof(decimal)) + { + var precision = column.Size != default(int) ? column.Size : DefaultDecimalPrecision; + var scale = column.Precision != default(int) ? column.Precision : DefaultDecimalScale; + return string.Format(DecimalColumnDefinitionFormat, precision, scale); + } + + string definition = DbTypeMap.ColumnTypeMap.First(x => x.Key == type).Value; + string dbTypeDefinition = column.Size != default(int) + ? string.Format("{0}({1})", definition, column.Size) + : definition; + //NOTE Percision is left out + return dbTypeDefinition; + } + + protected virtual string FormatNullable(ColumnDefinition column) + { + return column.IsNullable ? "NULL" : "NOT NULL"; + } + + protected virtual string FormatConstraint(ColumnDefinition column) + { + if (string.IsNullOrEmpty(column.ConstraintName) && column.DefaultValue == null) + return string.Empty; + + return string.Format("CONSTRAINT {0}", + string.IsNullOrEmpty(column.ConstraintName) + ? GetQuotedName(string.Format("DF_{0}_{1}", column.TableName, column.Name)) + : column.ConstraintName); + } + + protected virtual string FormatDefaultValue(ColumnDefinition column) + { + if (column.DefaultValue == null) + return string.Empty; + + //hack - probably not needed with latest changes + if (column.DefaultValue.ToString().ToLower().Equals("getdate()".ToLower())) + column.DefaultValue = SystemMethods.CurrentDateTime; + + // see if this is for a system method + if (column.DefaultValue is SystemMethods) + { + string method = FormatSystemMethods((SystemMethods)column.DefaultValue); + if (string.IsNullOrEmpty(method)) + return string.Empty; + + return string.Format(DefaultValueFormat, method); + } + + return string.Format(DefaultValueFormat, GetQuotedValue(column.DefaultValue.ToString())); + } + + protected virtual string FormatPrimaryKey(ColumnDefinition column) + { + return string.Empty; + } + + protected abstract string FormatSystemMethods(SystemMethods systemMethod); + + protected abstract string FormatIdentity(ColumnDefinition column); + + public virtual string DeleteDefaultConstraint + { + get + { + throw new NotSupportedException("Default constraints are not supported"); + } + } + + public virtual string CreateTable { get { return "CREATE TABLE {0} ({1})"; } } + public virtual string DropTable { get { return "DROP TABLE {0}"; } } + + public virtual string AddColumn { get { return "ALTER TABLE {0} ADD COLUMN {1}"; } } + public virtual string DropColumn { get { return "ALTER TABLE {0} DROP COLUMN {1}"; } } + public virtual string AlterColumn { get { return "ALTER TABLE {0} ALTER COLUMN {1}"; } } + public virtual string RenameColumn { get { return "ALTER TABLE {0} RENAME COLUMN {1} TO {2}"; } } + + public virtual string RenameTable { get { return "RENAME TABLE {0} TO {1}"; } } + + public virtual string CreateSchema { get { return "CREATE SCHEMA {0}"; } } + public virtual string AlterSchema { get { return "ALTER SCHEMA {0} TRANSFER {1}.{2}"; } } + public virtual string DropSchema { get { return "DROP SCHEMA {0}"; } } + + public virtual string CreateIndex { get { return "CREATE {0}{1}INDEX {2} ON {3} ({4})"; } } + public virtual string DropIndex { get { return "DROP INDEX {0}"; } } + + public virtual string InsertData { get { return "INSERT INTO {0} ({1}) VALUES ({2})"; } } + public virtual string UpdateData { get { return "UPDATE {0} SET {1} WHERE {2}"; } } + public virtual string DeleteData { get { return "DELETE FROM {0} WHERE {1}"; } } + public virtual string TruncateTable { get { return "TRUNCATE TABLE {0}"; } } + + public virtual string CreateConstraint { get { return "ALTER TABLE {0} ADD CONSTRAINT {1} {2} ({3})"; } } + public virtual string DeleteConstraint { get { return "ALTER TABLE {0} DROP CONSTRAINT {1}"; } } + public virtual string CreateForeignKeyConstraint { get { return "ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4}){5}{6}"; } } + + public virtual string IsNull { get { return "ISNULL({0},{1})"; } } + public virtual string ConvertIntegerToOrderableString { get { return "RIGHT('00000000' + CAST({0} AS varchar(8)),8)"; } } + public virtual string ConvertDateToOrderableString { get { return "CONVERT(varchar, {0}, 102)"; } } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 0e5b55866f..773d26a455 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -23,2321 +23,2321 @@ using Umbraco.Core.Publishing; namespace Umbraco.Core.Services { - /// - /// Represents the Content Service, which is an easy access to operations involving - /// - public class ContentService : RepositoryService, IContentService, IContentServiceOperations - { - private readonly IPublishingStrategy _publishingStrategy; - private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); - private readonly IDataTypeService _dataTypeService; - private readonly IUserService _userService; - - //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 ContentService( - IDatabaseUnitOfWorkProvider provider, - RepositoryFactory repositoryFactory, - ILogger logger, - IEventMessagesFactory eventMessagesFactory, - IPublishingStrategy publishingStrategy, - IDataTypeService dataTypeService, - IUserService userService) - : base(provider, repositoryFactory, logger, eventMessagesFactory) - { - if (publishingStrategy == null) throw new ArgumentNullException("publishingStrategy"); - if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); - if (userService == null) throw new ArgumentNullException("userService"); - _publishingStrategy = publishingStrategy; - _dataTypeService = dataTypeService; - _userService = userService; - } - - public int CountPublished(string contentTypeAlias = null) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - return repository.CountPublished(); - } - } - - public int Count(string contentTypeAlias = null) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - return repository.Count(contentTypeAlias); - } - } - - public int CountChildren(int parentId, string contentTypeAlias = null) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - return repository.CountChildren(parentId, contentTypeAlias); - } - } - - public int CountDescendants(int parentId, string contentTypeAlias = null) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - return repository.CountDescendants(parentId, contentTypeAlias); - } - } - - /// - /// 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. - /// - /// - public void ReplaceContentPermissions(EntityPermissionSet permissionSet) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - repository.ReplaceContentPermissions(permissionSet); - } - } - - /// - /// Assigns a single permission to the current content item for the specified user ids - /// - /// - /// - /// - public void AssignContentPermission(IContent entity, char permission, IEnumerable userIds) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - repository.AssignEntityPermission(entity, permission, userIds); - } - } - - /// - /// Gets the list of permissions for the content item - /// - /// - /// - public IEnumerable GetPermissionsForEntity(IContent content) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - return repository.GetPermissionsForEntity(content.Id); - } - } - - /// - /// Creates an object using the alias of the - /// that this Content should based on. - /// - /// - /// 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. - /// - /// Name of the Content object - /// Id of Parent for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// - public IContent CreateContent(string name, int parentId, string contentTypeAlias, int userId = 0) - { - var contentType = FindContentTypeByAlias(contentTypeAlias); - 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); - - var uow = UowProvider.GetUnitOfWork(); - using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) - { - auditRepo.AddOrUpdate(new AuditItem(content.Id, string.Format("Content '{0}' was created", name), AuditType.New, content.CreatorId)); - uow.Commit(); - } - - return content; - } - - /// - /// Creates an object using the alias of the - /// that this Content should based on. - /// - /// - /// 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. - /// - /// Name of the Content object - /// Parent object for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// - public IContent CreateContent(string name, IContent parent, string contentTypeAlias, int userId = 0) - { - if (parent == null) throw new ArgumentNullException("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)) - { - content.WasCancelled = true; - 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. - /// - /// - /// 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 - /// - 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; - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - content.CreatorId = userId; - content.WriterId = userId; - repository.AddOrUpdate(content); - //Generate a new preview - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - uow.Commit(); - } - - Saved.RaiseEvent(new SaveEventArgs(content, false), this); - - Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parentId), this); - - Audit(AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id); - - return content; - } - - /// - /// Creates and saves an object using the alias of the - /// that this Content should based on. - /// - /// - /// 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 - /// - public IContent CreateContentWithIdentity(string name, IContent parent, string contentTypeAlias, int userId = 0) - { - if (parent == null) throw new ArgumentNullException("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)) - { - content.WasCancelled = true; - return content; - } - - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(content), this)) - { - content.WasCancelled = true; - return content; - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - content.CreatorId = userId; - content.WriterId = userId; - repository.AddOrUpdate(content); - //Generate a new preview - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - uow.Commit(); - } - - Saved.RaiseEvent(new SaveEventArgs(content, false), this); - - Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parent), this); - - Audit(AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id); - - return content; - } - - /// - /// Gets an object by Id - /// - /// Id of the Content to retrieve - /// - public IContent GetById(int id) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - return repository.Get(id); - } - } - - /// - /// Gets an object by Id - /// - /// Ids of the Content to retrieve - /// - public IEnumerable GetByIds(IEnumerable ids) - { - if (ids.Any() == false) return Enumerable.Empty(); - - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - return repository.GetAll(ids.ToArray()); - } - } - - /// - /// Gets an object by its 'UniqueId' - /// - /// Guid key of the Content to retrieve - /// - public IContent GetById(Guid key) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Key == key); - var contents = repository.GetByQuery(query); - return contents.SingleOrDefault(); - } - } - - /// - /// Gets a collection of objects by the Id of the - /// - /// Id of the - /// An Enumerable list of objects - public IEnumerable GetContentOfContentType(int id) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.ContentTypeId == id); - var contents = repository.GetByQuery(query); - - return contents; - } - } - - internal IEnumerable GetPublishedContentOfContentType(int id) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.ContentTypeId == id); - var contents = repository.GetByPublishedVersion(query); - - return contents; - } - } - - /// - /// Gets a collection of objects by Level - /// - /// The level to retrieve Content from - /// An Enumerable list of objects - public IEnumerable GetByLevel(int level) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Level == level && !x.Path.StartsWith(Constants.System.RecycleBinContent.ToInvariantString())); - var contents = repository.GetByQuery(query); - - return contents; - } - } - - /// - /// Gets a specific version of an item. - /// - /// Id of the version to retrieve - /// An item - public IContent GetByVersion(Guid versionId) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - return repository.GetByVersion(versionId); - } - } - - - /// - /// Gets a collection of an objects versions by Id - /// - /// - /// An Enumerable list of objects - public IEnumerable GetVersions(int id) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var versions = repository.GetAllVersions(id); - return versions; - } - } - - /// - /// Gets a collection of objects, which are ancestors of the current content. - /// - /// Id of the to retrieve ancestors for - /// An Enumerable list of objects - public IEnumerable GetAncestors(int id) - { - var content = GetById(id); - return GetAncestors(content); - } - - /// - /// Gets a collection of objects, which are ancestors of the current content. - /// - /// to retrieve ancestors for - /// An Enumerable list of objects - public IEnumerable GetAncestors(IContent content) - { - //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(); - if (ids.Any() == false) - return new List(); - - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - return repository.GetAll(ids); - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// An Enumerable list of objects - public IEnumerable GetChildren(int id) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.ParentId == id); - var contents = repository.GetByQuery(query).OrderBy(x => x.SortOrder); - - return contents; - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, - string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - - var query = Query.Builder; - //if the id is System Root, then just get all - if (id != Constants.System.Root) - { - query.Where(x => x.ParentId == id); - } - long total; - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, orderBySystemField, filter); - totalChildren = Convert.ToInt32(total); - return contents; - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page index (zero based) - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// An Enumerable list of objects - public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - - var query = Query.Builder; - //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; - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - - var query = Query.Builder; - //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)); - } - long total; - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, orderBySystemField, filter); - totalChildren = Convert.ToInt32(total); - return contents; - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - - var query = Query.Builder; - //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)); - } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); - - return contents; - } - } - - /// - /// Gets a collection of objects by its name or partial name - /// - /// Id of the Parent to retrieve Children from - /// Full or partial name of the children - /// An Enumerable list of objects - public IEnumerable GetChildrenByName(int parentId, string name) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.ParentId == parentId && x.Name.Contains(name)); - var contents = repository.GetByQuery(query); - - return contents; - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// An Enumerable list of objects - public IEnumerable GetDescendants(int id) - { - var content = GetById(id); - if (content == null) - { - return Enumerable.Empty(); - } - return GetDescendants(content); - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// item to retrieve Descendants from - /// An Enumerable list of objects - public IEnumerable GetDescendants(IContent content) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var pathMatch = content.Path + ","; - var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != content.Id); - var contents = repository.GetByQuery(query); - - return contents; - } - } - - /// - /// Gets the parent of the current content as an item. - /// - /// Id of the to retrieve the parent from - /// Parent object - public IContent GetParent(int id) - { - var content = GetById(id); - return GetParent(content); - } - - /// - /// Gets the parent of the current content as an item. - /// - /// to retrieve the parent from - /// Parent object - public IContent GetParent(IContent content) - { - if (content.ParentId == Constants.System.Root || content.ParentId == Constants.System.RecycleBinContent) - return null; - - return GetById(content.ParentId); - } - - /// - /// Gets the published version of an item - /// - /// Id of the to retrieve version from - /// An item - public IContent GetPublishedVersion(int id) - { - var version = GetVersions(id); - return version.FirstOrDefault(x => x.Published == true); - } - - /// - /// Gets the published version of a item. - /// - /// The content item. - /// The published version, if any; otherwise, null. - public IContent GetPublishedVersion(IContent content) - { - if (content.Published) return content; - return content.HasPublishedVersion - ? GetByVersion(content.PublishedVersionGuid) - : null; - } - - /// - /// Gets a collection of objects, which reside at the first level / root - /// - /// An Enumerable list of objects - public IEnumerable GetRootContent() - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.ParentId == Constants.System.Root); - var contents = repository.GetByQuery(query); - - return contents; - } - } - - /// - /// Gets all published content items - /// - /// - internal IEnumerable GetAllPublished() - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Trashed == false); - return repository.GetByPublishedVersion(query); - } - } - - /// - /// Gets a collection of objects, which has an expiration date less than or equal to today. - /// - /// An Enumerable list of objects - public IEnumerable GetContentForExpiration() - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Published == true && x.ExpireDate <= DateTime.Now); - var contents = repository.GetByQuery(query); - - return contents; - } - } - - /// - /// Gets a collection of objects, which has a release date less than or equal to today. - /// - /// An Enumerable list of objects - public IEnumerable GetContentForRelease() - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Published == false && x.ReleaseDate <= DateTime.Now); - var contents = repository.GetByQuery(query); - - return contents; - } - } - - /// - /// Gets a collection of an objects, which resides in the Recycle Bin - /// - /// An Enumerable list of objects - public IEnumerable GetContentInRecycleBin() - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Path.Contains(Constants.System.RecycleBinContent.ToInvariantString())); - var contents = repository.GetByQuery(query); - - return contents; - } - } - - - - /// - /// Checks whether an item has any children - /// - /// Id of the - /// True if the content has any children otherwise False - public bool HasChildren(int id) - { - return CountChildren(id) > 0; - } - - internal int CountChildren(int id) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.ParentId == id); - var count = repository.Count(query); - return count; - } - } - - /// - /// Checks whether an item has any published versions - /// - /// Id of the - /// True if the content has any published version otherwise False - public bool HasPublishedVersion(int id) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Published == true && x.Id == id && x.Trashed == false); - int count = repository.Count(query); - return count > 0; - } - } - - /// - /// Checks if the passed in can be published based on the anscestors publish state. - /// - /// to check if anscestors are published - /// 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); - } - - return IsPublishable(content, false); - } - - /// - /// This will rebuild the xml structures for content in the database. - /// - /// This is not used for anything - /// True if publishing succeeded, otherwise False - /// - /// This is used for when a document type alias or a document type property is changed, the xml will need to - /// be regenerated. - /// - public bool RePublishAll(int userId = 0) - { - try - { - RebuildXmlStructures(); - return true; - } - catch (Exception ex) - { - Logger.Error("An error occurred executing RePublishAll", ex); - return false; - } - } - - /// - /// This will rebuild the xml structures for content in the database. - /// - /// - /// If specified will only rebuild the xml for the content type's specified, otherwise will update the structure - /// for all published content. - /// - internal void RePublishAll(params int[] contentTypeIds) - { - try - { - RebuildXmlStructures(contentTypeIds); - } - catch (Exception ex) - { - Logger.Error("An error occurred executing RePublishAll", ex); - } - } - - /// - /// Publishes 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) - { - 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; - } - - /// - /// 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); - } - - /// - /// 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) - { - var evtMsgs = EventMessagesFactory.Get(); - - using (new WriteLock(Locker)) - { - 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); - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - content.WriterId = userId; - content.ChangeTrashedState(true); - 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.Commit(); - } - - 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); - } - } - - /// - /// 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); - } - - /// - /// 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; - } - - /// - /// 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 - [Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")] - public bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true) - { - var result = SaveAndPublishDo(content, userId, raiseEvents); - return result.Success; - } - - /// - /// 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 - public Attempt SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true) - { - return ((IContentServiceOperations)this).SaveAndPublish(content, userId, raiseEvents); - } - - /// - /// Saves 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) - { - ((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); - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - 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, 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, c)); - } - } - - uow.Commit(); - } - - 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); - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - repository.Delete(content); - uow.Commit(); - - 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); - } - } - - /// - /// Publishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// The published status attempt - Attempt IContentServiceOperations.Publish(IContent content, int userId) - { - return SaveAndPublishDo(content, userId); - } - - /// - /// Saves 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) - { - return Save(content, true, userId, raiseEvents); - } - - /// - /// 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); - } - - /// - /// 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. - - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - //NOTE What about content that has the contenttype as part of its composition? - var query = Query.Builder.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 = Query.Builder.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); - } - } - - /// - /// Permanently deletes an object as well as all of its Children. - /// - /// - /// 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 - public void Delete(IContent content, int userId = 0) - { - ((IContentServiceOperations)this).Delete(content, userId); - } - - /// - /// Permanently deletes versions from an object prior to a specific date. - /// This method will never delete the latest version of a content item. - /// - /// Id of the object to delete versions from - /// Latest version date - /// Optional Id of the User deleting versions of a Content object - public void DeleteVersions(int id, DateTime versionDate, int userId = 0) - { - if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, dateToRetain: versionDate), this)) - return; - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - repository.DeleteVersions(id, versionDate); - uow.Commit(); - } - - DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate), this); - - Audit(AuditType.Delete, "Delete Content by version date performed by user", userId, Constants.System.Root); - } - - /// - /// Permanently deletes specific version(s) from an object. - /// This method will never delete the latest version of a content item. - /// - /// Id of the object to delete a version from - /// Id of the version to delete - /// Boolean indicating whether to delete versions prior to the versionId - /// 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) - { - var content = GetByVersion(versionId); - DeleteVersions(id, content.UpdateDate, userId); - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - repository.DeleteVersion(versionId); - uow.Commit(); - } - - DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), this); - - Audit(AuditType.Delete, "Delete Content by version performed by user", userId, Constants.System.Root); - } - } - - /// - /// 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 - public void MoveToRecycleBin(IContent content, int userId = 0) - { - ((IContentServiceOperations)this).MoveToRecycleBin(content, userId); - } - - /// - /// Moves an object to a new location by changing its parent id. - /// - /// - /// If the object is already published it will be - /// published after being moved to its new location. Otherwise it'll just - /// be saved with a new parent id. - /// - /// The to move - /// Id of the Content's new Parent - /// Optional Id of the User moving the Content - public void Move(IContent content, int parentId, int userId = 0) - { - using (new WriteLock(Locker)) - { - //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); - } - } - - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - public void EmptyRecycleBin() - { - using (new WriteLock(Locker)) - { - Dictionary> entities; - List files; - bool success; - var nodeObjectType = new Guid(Constants.ObjectTypes.Document); - - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - //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); - } - } - Audit(AuditType.Delete, "Empty Content Recycle Bin performed by user", 0, Constants.System.RecycleBinContent); - } - - /// - /// 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. - /// - /// The to copy - /// Id of the Content's new Parent - /// Boolean indicating whether the copy should be related to the original - /// Optional Id of the User copying the Content - /// The newly created object - public IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0) - { - return Copy(content, parentId, relateToOriginal, true, userId); - } - - /// - /// 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. - /// - /// The to copy - /// Id of the Content's new Parent - /// Boolean indicating whether the copy should be related to the original - /// A value indicating whether to recursively copy children. - /// Optional Id of the User copying the Content - /// 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 - - using (new WriteLock(Locker)) - { - var copy = content.DeepCloneWithResetIdentities(); - copy.ParentId = parentId; - - // A copy should never be set to published automatically even if the original was. - copy.ChangePublishedState(PublishedState.Unpublished); - - if (Copying.IsRaisedEventCancelled(new CopyEventArgs(content, copy, parentId), this)) - return null; - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - // Update the create author and last edit author - copy.CreatorId = userId; - copy.WriterId = userId; - - repository.AddOrUpdate(copy); - //add or update a preview - repository.AddOrUpdatePreviewXml(copy, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - uow.Commit(); - - - //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 }); - } - } - } - - if (recursive) - { - //Look for children and copy those as well - var children = GetChildren(content.Id); - foreach (var child in children) - { - //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); - } - } - - 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. - /// - /// The to send to publication - /// Optional Id of the User issueing the send to publication - /// True if sending publication was succesfull otherwise false - public bool SendToPublication(IContent content, int userId = 0) - { - if (SendingToPublish.IsRaisedEventCancelled(new SendToPublishEventArgs(content), this)) - return false; - - //Save before raising event - Save(content, userId); - - SentToPublish.RaiseEvent(new SendToPublishEventArgs(content, false), this); - - Audit(AuditType.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id); - - return true; - } - - /// - /// Rollback an object to a previous version. - /// This will create a new version, which is a copy of all the old data. - /// - /// - /// The way data is stored actually only allows us to rollback on properties - /// and not data like Name and Alias of the Content. - /// - /// Id of the being rolled back - /// Id of the version to rollback to - /// Optional Id of the User issueing the rollback of the Content - /// The newly created object - public IContent Rollback(int id, Guid versionId, int userId = 0) - { - var content = GetByVersion(versionId); - - if (RollingBack.IsRaisedEventCancelled(new RollbackEventArgs(content), this)) - return content; - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - content.WriterId = userId; - content.CreatorId = userId; - content.ChangePublishedState(PublishedState.Unpublished); - - repository.AddOrUpdate(content); - //add or update a preview - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - uow.Commit(); - } - - RolledBack.RaiseEvent(new RollbackEventArgs(content, false), this); - - Audit(AuditType.RollBack, "Content rollback performed by user", content.WriterId, content.Id); - - return content; - } - - /// - /// Sorts a collection of objects by updating the SortOrder according - /// to the ordering of items in the passed in . - /// - /// - /// Using this method will ensure that the Published-state is maintained upon sorting - /// so the cache is updated accordingly - as needed. - /// - /// - /// - /// - /// 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)) - return false; - } - - var shouldBePublished = new List(); - var shouldBeSaved = new List(); - - var asArray = items.ToArray(); - using (new WriteLock(Locker)) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - 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 == 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, c)); - } - - foreach (var content in shouldBePublished) - { - //Create and Save ContentXml DTO - repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - } - - uow.Commit(); - } - } - - 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); - } - - - Audit(AuditType.Sort, "Sorting content performed by user", userId, 0); - - return true; - } - - /// - /// 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) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - repository.RebuildXmlStructures( - content => _entitySerializer.Serialize(this, _dataTypeService, _userService, content), - contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds); - - uow.Commit(); - } - - Audit(AuditType.Publish, "ContentService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, Constants.System.Root); - - } - - #region Internal Methods - - /// - /// Gets a collection of descendants by the first Parent. - /// - /// item to retrieve Descendants from - /// An Enumerable list of objects - internal IEnumerable GetPublishedDescendants(IContent content) - { - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Id != content.Id && x.Path.StartsWith(content.Path) && x.Trashed == false); - var contents = repository.GetByPublishedVersion(query); - - return contents; - } - } - - #endregion - - #region Private Methods - - private void Audit(AuditType type, string message, int userId, int objectId) - { - var uow = UowProvider.GetUnitOfWork(); - using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) - { - auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); - uow.Commit(); - } - } - - //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.GetUnitOfWork()) - { - var xml = _entitySerializer.Serialize(this, _dataTypeService, _userService, 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 - /// - /// The to publish along with its children - /// Optional Id of the User issueing the publishing - /// If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published - /// - /// A list of publish statues. If the parent document is not valid or cannot be published because it's parent(s) is not published - /// 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) - { - if (content == null) throw new ArgumentNullException("content"); - - var evtMsgs = EventMessagesFactory.Get(); - - using (new WriteLock(Locker)) - { - var result = new List>(); - - //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 or one of its ancestors is not published.", - content.Name, content.Id)); - result.Add(Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedPathNotPublished, evtMsgs))); - return result; - } - - //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 = Query.Builder.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); - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - //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, c)); - //add or update the published xml - repository.AddOrUpdateContentXml(item.Result.ContentItem, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - updated.Add(item.Result.ContentItem); - } - - uow.Commit(); - - } - //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; - } - } - - /// - /// UnPublishes a single object - /// - /// The to publish - /// Optional boolean to avoid having the cache refreshed when calling this Unpublish method. By default this method will update the cache. - /// Optional Id of the User issueing the publishing - /// 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; - - 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)); - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - 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); - repository.DeleteContentXml(content); - - uow.Commit(); - } - //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)); - } - - /// - /// 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 - private Attempt SaveAndPublishDo(IContent content, int userId = 0, bool raiseEvents = true) - { - var evtMsgs = EventMessagesFactory.Get(); - - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled( - new SaveEventArgs(content, evtMsgs), this)) - { - return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent, evtMsgs)); - } - } - - 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 - - //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; - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - 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, c)); - - if (published) - { - //Content Xml - repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - } - - uow.Commit(); - } - - 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)) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateContentRepository(uow)) - { - 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, c)); - - uow.Commit(); - } - - 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 repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Alias == contentTypeAlias); - var types = repository.GetByQuery(query); - - if (types.Any() == false) - throw new Exception( - string.Format("No ContentType matching the passed in Alias: '{0}' was found", - contentTypeAlias)); - - var contentType = types.First(); - - if (contentType == null) - throw new Exception(string.Format("ContentType matching the passed in Alias: '{0}' was null", - contentTypeAlias)); - - return contentType; - } - } - - #endregion - - #region Proxy Event Handlers - /// - /// Occurs before publish. - /// - /// Proxy to the real event on the - public static event TypedEventHandler> Publishing - { - add { PublishingStrategy.Publishing += value; } - remove { PublishingStrategy.Publishing -= value; } - } - - /// - /// 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 - /// - public static event TypedEventHandler> Deleting; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> Deleted; - - /// - /// Occurs before Delete Versions - /// - public static event TypedEventHandler DeletingVersions; - - /// - /// Occurs after Delete Versions - /// - public static event TypedEventHandler DeletedVersions; - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> Saving; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> Saved; - - /// - /// Occurs before Create - /// - [Obsolete("Use the Created event instead, the Creating and Created events both offer the same functionality, Creating event has been deprecated.")] - public static event TypedEventHandler> Creating; - - /// - /// Occurs after Create - /// - /// - /// Please note that the Content object has been created, but might not have been saved - /// so it does not have an identity yet (meaning no Id has been set). - /// - public static event TypedEventHandler> Created; - - /// - /// Occurs before Copy - /// - public static event TypedEventHandler> Copying; - - /// - /// Occurs after Copy - /// - public static event TypedEventHandler> Copied; - - /// - /// Occurs before Content is moved to Recycle Bin - /// - public static event TypedEventHandler> Trashing; - - /// - /// Occurs after Content is moved to Recycle Bin - /// - public static event TypedEventHandler> Trashed; - - /// - /// Occurs before Move - /// - public static event TypedEventHandler> Moving; - - /// - /// Occurs after Move - /// - public static event TypedEventHandler> Moved; - - /// - /// Occurs before Rollback - /// - public static event TypedEventHandler> RollingBack; - - /// - /// Occurs after Rollback - /// - public static event TypedEventHandler> RolledBack; - - /// - /// Occurs before Send to Publish - /// - public static event TypedEventHandler> SendingToPublish; - - /// - /// Occurs after Send to Publish - /// - public static event TypedEventHandler> SentToPublish; - - /// - /// Occurs before the Recycle Bin is emptied - /// - public static event TypedEventHandler EmptyingRecycleBin; - - /// - /// Occurs after the Recycle Bin has been Emptied - /// - public static event TypedEventHandler EmptiedRecycleBin; - #endregion - } + /// + /// Represents the Content Service, which is an easy access to operations involving + /// + public class ContentService : RepositoryService, IContentService, IContentServiceOperations + { + private readonly IPublishingStrategy _publishingStrategy; + private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); + private readonly IDataTypeService _dataTypeService; + private readonly IUserService _userService; + + //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 ContentService( + IDatabaseUnitOfWorkProvider provider, + RepositoryFactory repositoryFactory, + ILogger logger, + IEventMessagesFactory eventMessagesFactory, + IPublishingStrategy publishingStrategy, + IDataTypeService dataTypeService, + IUserService userService) + : base(provider, repositoryFactory, logger, eventMessagesFactory) + { + if (publishingStrategy == null) throw new ArgumentNullException("publishingStrategy"); + if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); + if (userService == null) throw new ArgumentNullException("userService"); + _publishingStrategy = publishingStrategy; + _dataTypeService = dataTypeService; + _userService = userService; + } + + public int CountPublished(string contentTypeAlias = null) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + return repository.CountPublished(); + } + } + + public int Count(string contentTypeAlias = null) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + return repository.Count(contentTypeAlias); + } + } + + public int CountChildren(int parentId, string contentTypeAlias = null) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + return repository.CountChildren(parentId, contentTypeAlias); + } + } + + public int CountDescendants(int parentId, string contentTypeAlias = null) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + return repository.CountDescendants(parentId, contentTypeAlias); + } + } + + /// + /// 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. + /// + /// + public void ReplaceContentPermissions(EntityPermissionSet permissionSet) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + repository.ReplaceContentPermissions(permissionSet); + } + } + + /// + /// Assigns a single permission to the current content item for the specified user ids + /// + /// + /// + /// + public void AssignContentPermission(IContent entity, char permission, IEnumerable userIds) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + repository.AssignEntityPermission(entity, permission, userIds); + } + } + + /// + /// Gets the list of permissions for the content item + /// + /// + /// + public IEnumerable GetPermissionsForEntity(IContent content) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + return repository.GetPermissionsForEntity(content.Id); + } + } + + /// + /// Creates an object using the alias of the + /// that this Content should based on. + /// + /// + /// 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. + /// + /// Name of the Content object + /// Id of Parent for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + public IContent CreateContent(string name, int parentId, string contentTypeAlias, int userId = 0) + { + var contentType = FindContentTypeByAlias(contentTypeAlias); + 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); + + var uow = UowProvider.GetUnitOfWork(); + using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) + { + auditRepo.AddOrUpdate(new AuditItem(content.Id, string.Format("Content '{0}' was created", name), AuditType.New, content.CreatorId)); + uow.Commit(); + } + + return content; + } + + /// + /// Creates an object using the alias of the + /// that this Content should based on. + /// + /// + /// 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. + /// + /// Name of the Content object + /// Parent object for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + public IContent CreateContent(string name, IContent parent, string contentTypeAlias, int userId = 0) + { + if (parent == null) throw new ArgumentNullException("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)) + { + content.WasCancelled = true; + 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. + /// + /// + /// 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 + /// + 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; + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + content.CreatorId = userId; + content.WriterId = userId; + repository.AddOrUpdate(content); + //Generate a new preview + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + uow.Commit(); + } + + Saved.RaiseEvent(new SaveEventArgs(content, false), this); + + Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parentId), this); + + Audit(AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id); + + return content; + } + + /// + /// Creates and saves an object using the alias of the + /// that this Content should based on. + /// + /// + /// 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 + /// + public IContent CreateContentWithIdentity(string name, IContent parent, string contentTypeAlias, int userId = 0) + { + if (parent == null) throw new ArgumentNullException("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)) + { + content.WasCancelled = true; + return content; + } + + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(content), this)) + { + content.WasCancelled = true; + return content; + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + content.CreatorId = userId; + content.WriterId = userId; + repository.AddOrUpdate(content); + //Generate a new preview + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + uow.Commit(); + } + + Saved.RaiseEvent(new SaveEventArgs(content, false), this); + + Created.RaiseEvent(new NewEventArgs(content, false, contentTypeAlias, parent), this); + + Audit(AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id); + + return content; + } + + /// + /// Gets an object by Id + /// + /// Id of the Content to retrieve + /// + public IContent GetById(int id) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + return repository.Get(id); + } + } + + /// + /// Gets an object by Id + /// + /// Ids of the Content to retrieve + /// + public IEnumerable GetByIds(IEnumerable ids) + { + if (ids.Any() == false) return Enumerable.Empty(); + + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetAll(ids.ToArray()); + } + } + + /// + /// Gets an object by its 'UniqueId' + /// + /// Guid key of the Content to retrieve + /// + public IContent GetById(Guid key) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Key == key); + var contents = repository.GetByQuery(query); + return contents.SingleOrDefault(); + } + } + + /// + /// Gets a collection of objects by the Id of the + /// + /// Id of the + /// An Enumerable list of objects + public IEnumerable GetContentOfContentType(int id) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ContentTypeId == id); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + internal IEnumerable GetPublishedContentOfContentType(int id) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ContentTypeId == id); + var contents = repository.GetByPublishedVersion(query); + + return contents; + } + } + + /// + /// Gets a collection of objects by Level + /// + /// The level to retrieve Content from + /// An Enumerable list of objects + public IEnumerable GetByLevel(int level) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Level == level && !x.Path.StartsWith(Constants.System.RecycleBinContent.ToInvariantString())); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + /// + /// Gets a specific version of an item. + /// + /// Id of the version to retrieve + /// An item + public IContent GetByVersion(Guid versionId) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetByVersion(versionId); + } + } + + + /// + /// Gets a collection of an objects versions by Id + /// + /// + /// An Enumerable list of objects + public IEnumerable GetVersions(int id) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var versions = repository.GetAllVersions(id); + return versions; + } + } + + /// + /// Gets a collection of objects, which are ancestors of the current content. + /// + /// Id of the to retrieve ancestors for + /// An Enumerable list of objects + public IEnumerable GetAncestors(int id) + { + var content = GetById(id); + return GetAncestors(content); + } + + /// + /// Gets a collection of objects, which are ancestors of the current content. + /// + /// to retrieve ancestors for + /// An Enumerable list of objects + public IEnumerable GetAncestors(IContent content) + { + //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(); + if (ids.Any() == false) + return new List(); + + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetAll(ids); + } + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// An Enumerable list of objects + public IEnumerable GetChildren(int id) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ParentId == id); + var contents = repository.GetByQuery(query).OrderBy(x => x.SortOrder); + + return contents; + } + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, + string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + + var query = Query.Builder; + //if the id is System Root, then just get all + if (id != Constants.System.Root) + { + query.Where(x => x.ParentId == id); + } + long total; + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, orderBySystemField, filter); + totalChildren = Convert.ToInt32(total); + return contents; + } + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page index (zero based) + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, + string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + + var query = Query.Builder; + //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; + } + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + + var query = Query.Builder; + //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)); + } + long total; + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, orderBySystemField, filter); + totalChildren = Convert.ToInt32(total); + return contents; + } + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + + var query = Query.Builder; + //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)); + } + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + + return contents; + } + } + + /// + /// Gets a collection of objects by its name or partial name + /// + /// Id of the Parent to retrieve Children from + /// Full or partial name of the children + /// An Enumerable list of objects + public IEnumerable GetChildrenByName(int parentId, string name) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ParentId == parentId && x.Name.Contains(name)); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// An Enumerable list of objects + public IEnumerable GetDescendants(int id) + { + var content = GetById(id); + if (content == null) + { + return Enumerable.Empty(); + } + return GetDescendants(content); + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// item to retrieve Descendants from + /// An Enumerable list of objects + public IEnumerable GetDescendants(IContent content) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var pathMatch = content.Path + ","; + var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != content.Id); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + /// + /// Gets the parent of the current content as an item. + /// + /// Id of the to retrieve the parent from + /// Parent object + public IContent GetParent(int id) + { + var content = GetById(id); + return GetParent(content); + } + + /// + /// Gets the parent of the current content as an item. + /// + /// to retrieve the parent from + /// Parent object + public IContent GetParent(IContent content) + { + if (content.ParentId == Constants.System.Root || content.ParentId == Constants.System.RecycleBinContent) + return null; + + return GetById(content.ParentId); + } + + /// + /// Gets the published version of an item + /// + /// Id of the to retrieve version from + /// An item + public IContent GetPublishedVersion(int id) + { + var version = GetVersions(id); + return version.FirstOrDefault(x => x.Published == true); + } + + /// + /// Gets the published version of a item. + /// + /// The content item. + /// The published version, if any; otherwise, null. + public IContent GetPublishedVersion(IContent content) + { + if (content.Published) return content; + return content.HasPublishedVersion + ? GetByVersion(content.PublishedVersionGuid) + : null; + } + + /// + /// Gets a collection of objects, which reside at the first level / root + /// + /// An Enumerable list of objects + public IEnumerable GetRootContent() + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ParentId == Constants.System.Root); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + /// + /// Gets all published content items + /// + /// + internal IEnumerable GetAllPublished() + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Trashed == false); + return repository.GetByPublishedVersion(query); + } + } + + /// + /// Gets a collection of objects, which has an expiration date less than or equal to today. + /// + /// An Enumerable list of objects + public IEnumerable GetContentForExpiration() + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Published == true && x.ExpireDate <= DateTime.Now); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + /// + /// Gets a collection of objects, which has a release date less than or equal to today. + /// + /// An Enumerable list of objects + public IEnumerable GetContentForRelease() + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Published == false && x.ReleaseDate <= DateTime.Now); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + /// + /// Gets a collection of an objects, which resides in the Recycle Bin + /// + /// An Enumerable list of objects + public IEnumerable GetContentInRecycleBin() + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Path.Contains(Constants.System.RecycleBinContent.ToInvariantString())); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + + + /// + /// Checks whether an item has any children + /// + /// Id of the + /// True if the content has any children otherwise False + public bool HasChildren(int id) + { + return CountChildren(id) > 0; + } + + internal int CountChildren(int id) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ParentId == id); + var count = repository.Count(query); + return count; + } + } + + /// + /// Checks whether an item has any published versions + /// + /// Id of the + /// True if the content has any published version otherwise False + public bool HasPublishedVersion(int id) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Published == true && x.Id == id && x.Trashed == false); + int count = repository.Count(query); + return count > 0; + } + } + + /// + /// Checks if the passed in can be published based on the anscestors publish state. + /// + /// to check if anscestors are published + /// 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); + } + + return IsPublishable(content, false); + } + + /// + /// This will rebuild the xml structures for content in the database. + /// + /// This is not used for anything + /// True if publishing succeeded, otherwise False + /// + /// This is used for when a document type alias or a document type property is changed, the xml will need to + /// be regenerated. + /// + public bool RePublishAll(int userId = 0) + { + try + { + RebuildXmlStructures(); + return true; + } + catch (Exception ex) + { + Logger.Error("An error occurred executing RePublishAll", ex); + return false; + } + } + + /// + /// This will rebuild the xml structures for content in the database. + /// + /// + /// If specified will only rebuild the xml for the content type's specified, otherwise will update the structure + /// for all published content. + /// + internal void RePublishAll(params int[] contentTypeIds) + { + try + { + RebuildXmlStructures(contentTypeIds); + } + catch (Exception ex) + { + Logger.Error("An error occurred executing RePublishAll", ex); + } + } + + /// + /// Publishes 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) + { + 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; + } + + /// + /// 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); + } + + /// + /// 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) + { + var evtMsgs = EventMessagesFactory.Get(); + + using (new WriteLock(Locker)) + { + 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); + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + content.WriterId = userId; + content.ChangeTrashedState(true); + 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.Commit(); + } + + 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); + } + } + + /// + /// 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); + } + + /// + /// 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; + } + + /// + /// 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 + [Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")] + public bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true) + { + var result = SaveAndPublishDo(content, userId, raiseEvents); + return result.Success; + } + + /// + /// 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 + public Attempt SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true) + { + return ((IContentServiceOperations)this).SaveAndPublish(content, userId, raiseEvents); + } + + /// + /// Saves 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) + { + ((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); + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + 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, 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, c)); + } + } + + uow.Commit(); + } + + 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); + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + repository.Delete(content); + uow.Commit(); + + 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); + } + } + + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// The published status attempt + Attempt IContentServiceOperations.Publish(IContent content, int userId) + { + return SaveAndPublishDo(content, userId); + } + + /// + /// Saves 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) + { + return Save(content, true, userId, raiseEvents); + } + + /// + /// 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); + } + + /// + /// 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. + + using (new WriteLock(Locker)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + //NOTE What about content that has the contenttype as part of its composition? + var query = Query.Builder.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 = Query.Builder.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); + } + } + + /// + /// Permanently deletes an object as well as all of its Children. + /// + /// + /// 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 + public void Delete(IContent content, int userId = 0) + { + ((IContentServiceOperations)this).Delete(content, userId); + } + + /// + /// Permanently deletes versions from an object prior to a specific date. + /// This method will never delete the latest version of a content item. + /// + /// Id of the object to delete versions from + /// Latest version date + /// Optional Id of the User deleting versions of a Content object + public void DeleteVersions(int id, DateTime versionDate, int userId = 0) + { + if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, dateToRetain: versionDate), this)) + return; + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + repository.DeleteVersions(id, versionDate); + uow.Commit(); + } + + DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate), this); + + Audit(AuditType.Delete, "Delete Content by version date performed by user", userId, Constants.System.Root); + } + + /// + /// Permanently deletes specific version(s) from an object. + /// This method will never delete the latest version of a content item. + /// + /// Id of the object to delete a version from + /// Id of the version to delete + /// Boolean indicating whether to delete versions prior to the versionId + /// 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) + { + var content = GetByVersion(versionId); + DeleteVersions(id, content.UpdateDate, userId); + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + repository.DeleteVersion(versionId); + uow.Commit(); + } + + DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), this); + + Audit(AuditType.Delete, "Delete Content by version performed by user", userId, Constants.System.Root); + } + } + + /// + /// 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 + public void MoveToRecycleBin(IContent content, int userId = 0) + { + ((IContentServiceOperations)this).MoveToRecycleBin(content, userId); + } + + /// + /// Moves an object to a new location by changing its parent id. + /// + /// + /// If the object is already published it will be + /// published after being moved to its new location. Otherwise it'll just + /// be saved with a new parent id. + /// + /// The to move + /// Id of the Content's new Parent + /// Optional Id of the User moving the Content + public void Move(IContent content, int parentId, int userId = 0) + { + using (new WriteLock(Locker)) + { + //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); + } + } + + /// + /// Empties the Recycle Bin by deleting all that resides in the bin + /// + public void EmptyRecycleBin() + { + using (new WriteLock(Locker)) + { + Dictionary> entities; + List files; + bool success; + var nodeObjectType = new Guid(Constants.ObjectTypes.Document); + + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + //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); + } + } + Audit(AuditType.Delete, "Empty Content Recycle Bin performed by user", 0, Constants.System.RecycleBinContent); + } + + /// + /// 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. + /// + /// The to copy + /// Id of the Content's new Parent + /// Boolean indicating whether the copy should be related to the original + /// Optional Id of the User copying the Content + /// The newly created object + public IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0) + { + return Copy(content, parentId, relateToOriginal, true, userId); + } + + /// + /// 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. + /// + /// The to copy + /// Id of the Content's new Parent + /// Boolean indicating whether the copy should be related to the original + /// A value indicating whether to recursively copy children. + /// Optional Id of the User copying the Content + /// 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 + + using (new WriteLock(Locker)) + { + var copy = content.DeepCloneWithResetIdentities(); + copy.ParentId = parentId; + + // A copy should never be set to published automatically even if the original was. + copy.ChangePublishedState(PublishedState.Unpublished); + + if (Copying.IsRaisedEventCancelled(new CopyEventArgs(content, copy, parentId), this)) + return null; + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + // Update the create author and last edit author + copy.CreatorId = userId; + copy.WriterId = userId; + + repository.AddOrUpdate(copy); + //add or update a preview + repository.AddOrUpdatePreviewXml(copy, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + uow.Commit(); + + + //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 }); + } + } + } + + if (recursive) + { + //Look for children and copy those as well + var children = GetChildren(content.Id); + foreach (var child in children) + { + //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); + } + } + + 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. + /// + /// The to send to publication + /// Optional Id of the User issueing the send to publication + /// True if sending publication was succesfull otherwise false + public bool SendToPublication(IContent content, int userId = 0) + { + if (SendingToPublish.IsRaisedEventCancelled(new SendToPublishEventArgs(content), this)) + return false; + + //Save before raising event + Save(content, userId); + + SentToPublish.RaiseEvent(new SendToPublishEventArgs(content, false), this); + + Audit(AuditType.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id); + + return true; + } + + /// + /// Rollback an object to a previous version. + /// This will create a new version, which is a copy of all the old data. + /// + /// + /// The way data is stored actually only allows us to rollback on properties + /// and not data like Name and Alias of the Content. + /// + /// Id of the being rolled back + /// Id of the version to rollback to + /// Optional Id of the User issueing the rollback of the Content + /// The newly created object + public IContent Rollback(int id, Guid versionId, int userId = 0) + { + var content = GetByVersion(versionId); + + if (RollingBack.IsRaisedEventCancelled(new RollbackEventArgs(content), this)) + return content; + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + content.WriterId = userId; + content.CreatorId = userId; + content.ChangePublishedState(PublishedState.Unpublished); + + repository.AddOrUpdate(content); + //add or update a preview + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + uow.Commit(); + } + + RolledBack.RaiseEvent(new RollbackEventArgs(content, false), this); + + Audit(AuditType.RollBack, "Content rollback performed by user", content.WriterId, content.Id); + + return content; + } + + /// + /// Sorts a collection of objects by updating the SortOrder according + /// to the ordering of items in the passed in . + /// + /// + /// Using this method will ensure that the Published-state is maintained upon sorting + /// so the cache is updated accordingly - as needed. + /// + /// + /// + /// + /// 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)) + return false; + } + + var shouldBePublished = new List(); + var shouldBeSaved = new List(); + + var asArray = items.ToArray(); + using (new WriteLock(Locker)) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + 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 == 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, c)); + } + + foreach (var content in shouldBePublished) + { + //Create and Save ContentXml DTO + repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + } + + uow.Commit(); + } + } + + 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); + } + + + Audit(AuditType.Sort, "Sorting content performed by user", userId, 0); + + return true; + } + + /// + /// 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) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + repository.RebuildXmlStructures( + content => _entitySerializer.Serialize(this, _dataTypeService, _userService, content), + contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds); + + uow.Commit(); + } + + Audit(AuditType.Publish, "ContentService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, Constants.System.Root); + + } + + #region Internal Methods + + /// + /// Gets a collection of descendants by the first Parent. + /// + /// item to retrieve Descendants from + /// An Enumerable list of objects + internal IEnumerable GetPublishedDescendants(IContent content) + { + using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Id != content.Id && x.Path.StartsWith(content.Path) && x.Trashed == false); + var contents = repository.GetByPublishedVersion(query); + + return contents; + } + } + + #endregion + + #region Private Methods + + private void Audit(AuditType type, string message, int userId, int objectId) + { + var uow = UowProvider.GetUnitOfWork(); + using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) + { + auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); + uow.Commit(); + } + } + + //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.GetUnitOfWork()) + { + var xml = _entitySerializer.Serialize(this, _dataTypeService, _userService, 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 + /// + /// The to publish along with its children + /// Optional Id of the User issueing the publishing + /// If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published + /// + /// A list of publish statues. If the parent document is not valid or cannot be published because it's parent(s) is not published + /// 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) + { + if (content == null) throw new ArgumentNullException("content"); + + var evtMsgs = EventMessagesFactory.Get(); + + using (new WriteLock(Locker)) + { + var result = new List>(); + + //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 or one of its ancestors is not published.", + content.Name, content.Id)); + result.Add(Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedPathNotPublished, evtMsgs))); + return result; + } + + //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 = Query.Builder.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); + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + //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, c)); + //add or update the published xml + repository.AddOrUpdateContentXml(item.Result.ContentItem, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + updated.Add(item.Result.ContentItem); + } + + uow.Commit(); + + } + //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; + } + } + + /// + /// UnPublishes a single object + /// + /// The to publish + /// Optional boolean to avoid having the cache refreshed when calling this Unpublish method. By default this method will update the cache. + /// Optional Id of the User issueing the publishing + /// 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; + + 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)); + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + 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); + repository.DeleteContentXml(content); + + uow.Commit(); + } + //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)); + } + + /// + /// 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 + private Attempt SaveAndPublishDo(IContent content, int userId = 0, bool raiseEvents = true) + { + var evtMsgs = EventMessagesFactory.Get(); + + if (raiseEvents) + { + if (Saving.IsRaisedEventCancelled( + new SaveEventArgs(content, evtMsgs), this)) + { + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent, evtMsgs)); + } + } + + 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 + + //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; + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + 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, c)); + + if (published) + { + //Content Xml + repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + } + + uow.Commit(); + } + + 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)) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateContentRepository(uow)) + { + 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, c)); + + uow.Commit(); + } + + 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 repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Alias == contentTypeAlias); + var types = repository.GetByQuery(query); + + if (types.Any() == false) + throw new Exception( + string.Format("No ContentType matching the passed in Alias: '{0}' was found", + contentTypeAlias)); + + var contentType = types.First(); + + if (contentType == null) + throw new Exception(string.Format("ContentType matching the passed in Alias: '{0}' was null", + contentTypeAlias)); + + return contentType; + } + } + + #endregion + + #region Proxy Event Handlers + /// + /// Occurs before publish. + /// + /// Proxy to the real event on the + public static event TypedEventHandler> Publishing + { + add { PublishingStrategy.Publishing += value; } + remove { PublishingStrategy.Publishing -= value; } + } + + /// + /// 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 + /// + public static event TypedEventHandler> Deleting; + + /// + /// Occurs after Delete + /// + public static event TypedEventHandler> Deleted; + + /// + /// Occurs before Delete Versions + /// + public static event TypedEventHandler DeletingVersions; + + /// + /// Occurs after Delete Versions + /// + public static event TypedEventHandler DeletedVersions; + + /// + /// Occurs before Save + /// + public static event TypedEventHandler> Saving; + + /// + /// Occurs after Save + /// + public static event TypedEventHandler> Saved; + + /// + /// Occurs before Create + /// + [Obsolete("Use the Created event instead, the Creating and Created events both offer the same functionality, Creating event has been deprecated.")] + public static event TypedEventHandler> Creating; + + /// + /// Occurs after Create + /// + /// + /// Please note that the Content object has been created, but might not have been saved + /// so it does not have an identity yet (meaning no Id has been set). + /// + public static event TypedEventHandler> Created; + + /// + /// Occurs before Copy + /// + public static event TypedEventHandler> Copying; + + /// + /// Occurs after Copy + /// + public static event TypedEventHandler> Copied; + + /// + /// Occurs before Content is moved to Recycle Bin + /// + public static event TypedEventHandler> Trashing; + + /// + /// Occurs after Content is moved to Recycle Bin + /// + public static event TypedEventHandler> Trashed; + + /// + /// Occurs before Move + /// + public static event TypedEventHandler> Moving; + + /// + /// Occurs after Move + /// + public static event TypedEventHandler> Moved; + + /// + /// Occurs before Rollback + /// + public static event TypedEventHandler> RollingBack; + + /// + /// Occurs after Rollback + /// + public static event TypedEventHandler> RolledBack; + + /// + /// Occurs before Send to Publish + /// + public static event TypedEventHandler> SendingToPublish; + + /// + /// Occurs after Send to Publish + /// + public static event TypedEventHandler> SentToPublish; + + /// + /// Occurs before the Recycle Bin is emptied + /// + public static event TypedEventHandler EmptyingRecycleBin; + + /// + /// Occurs after the Recycle Bin has been Emptied + /// + public static event TypedEventHandler EmptiedRecycleBin; + #endregion + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index fa688eae39..95333bee85 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -8,579 +8,579 @@ using Umbraco.Core.Publishing; namespace Umbraco.Core.Services { - /// - /// A temporary interface until we are in v8, this is used to return a different result for the same method and this interface gets implemented - /// explicitly. These methods will replace the normal ones in IContentService in v8 and this will be removed. - /// - public interface IContentServiceOperations - { - //TODO: Remove this class in v8 - - //TODO: There's probably more that needs to be added like the EmptyRecycleBin, etc... - - /// - /// Saves a single object - /// - /// The to save - /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - Attempt Save(IContent content, int userId = 0, bool raiseEvents = true); - - /// - /// 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 Save(IEnumerable contents, int userId = 0, bool raiseEvents = true); - - /// - /// 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 Delete(IContent content, int userId = 0); - - /// - /// Publishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// The published status attempt - Attempt Publish(IContent content, int userId = 0); - - /// - /// 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> PublishWithChildren(IContent content, int userId = 0, bool includeUnpublished = false); - - /// - /// 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 SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); - - /// - /// 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 MoveToRecycleBin(IContent content, int userId = 0); - - /// - /// UnPublishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if unpublishing succeeded, otherwise False - Attempt UnPublish(IContent content, int userId = 0); - } - - /// - /// Defines the ContentService, which is an easy access to operations involving - /// - public interface IContentService : IService - { - /// - /// 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 - /// - void RebuildXmlStructures(params int[] contentTypeIds); - - int CountPublished(string contentTypeAlias = null); - int Count(string contentTypeAlias = null); - int CountChildren(int parentId, string contentTypeAlias = null); - int CountDescendants(int parentId, string contentTypeAlias = null); - - /// - /// 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. - /// - /// - void ReplaceContentPermissions(EntityPermissionSet permissionSet); - - /// - /// Assigns a single permission to the current content item for the specified user ids - /// - /// - /// - /// - void AssignContentPermission(IContent entity, char permission, IEnumerable userIds); - - /// - /// Gets the list of permissions for the content item - /// - /// - /// - IEnumerable GetPermissionsForEntity(IContent content); - - bool SendToPublication(IContent content, int userId = 0); - - IEnumerable GetByIds(IEnumerable ids); - - /// - /// Creates an object using the alias of the - /// that this Content should based on. - /// - /// - /// 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. - /// - /// Name of the Content object - /// Id of Parent for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// - IContent CreateContent(string name, int parentId, string contentTypeAlias, int userId = 0); - - /// - /// Creates an object using the alias of the - /// that this Content should based on. - /// - /// - /// 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. - /// - /// Name of the Content object - /// Parent object for the new Content - /// Alias of the - /// Optional id of the user creating the content - /// - IContent CreateContent(string name, IContent parent, string contentTypeAlias, int userId = 0); - - /// - /// Gets an object by Id - /// - /// Id of the Content to retrieve - /// - IContent GetById(int id); - - /// - /// Gets an object by its 'UniqueId' - /// - /// Guid key of the Content to retrieve - /// - IContent GetById(Guid key); - - /// - /// Gets a collection of objects by the Id of the - /// - /// Id of the - /// An Enumerable list of objects - IEnumerable GetContentOfContentType(int id); - - /// - /// Gets a collection of objects by Level - /// - /// The level to retrieve Content from - /// An Enumerable list of objects - IEnumerable GetByLevel(int level); - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// An Enumerable list of objects - IEnumerable GetChildren(int id); - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// An Enumerable list of objects - IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// An Enumerable list of objects - IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); - - /// - /// Gets a collection of an objects versions by its Id - /// - /// - /// An Enumerable list of objects - IEnumerable GetVersions(int id); - - /// - /// Gets a collection of objects, which reside at the first level / root - /// - /// An Enumerable list of objects - IEnumerable GetRootContent(); - - /// - /// Gets a collection of objects, which has an expiration date greater then today - /// - /// An Enumerable list of objects - IEnumerable GetContentForExpiration(); - - /// - /// Gets a collection of objects, which has a release date greater then today - /// - /// An Enumerable list of objects - IEnumerable GetContentForRelease(); - - /// - /// Gets a collection of an objects, which resides in the Recycle Bin - /// - /// An Enumerable list of objects - IEnumerable GetContentInRecycleBin(); - - /// - /// Saves a single object - /// - /// The to save - /// Optional Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - void Save(IContent content, int userId = 0, bool raiseEvents = true); - - /// - /// 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. - void Save(IEnumerable contents, int userId = 0, bool raiseEvents = true); - - /// - /// 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 - void DeleteContentOfType(int contentTypeId, int userId = 0); - - /// - /// Permanently deletes versions from an object prior to a specific date. - /// - /// Id of the object to delete versions from - /// Latest version date - /// Optional Id of the User deleting versions of a Content object - void DeleteVersions(int id, DateTime versionDate, int userId = 0); - - /// - /// Permanently deletes a specific version from an object. - /// - /// Id of the object to delete a version from - /// Id of the version to delete - /// Boolean indicating whether to delete versions prior to the versionId - /// Optional Id of the User deleting versions of a Content object - void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0); - - /// - /// 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 - void MoveToRecycleBin(IContent content, int userId = 0); - - /// - /// Moves an object to a new location - /// - /// The to move - /// Id of the Content's new Parent - /// Optional Id of the User moving the Content - void Move(IContent content, int parentId, int userId = 0); - - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - void EmptyRecycleBin(); - - /// - /// Rollback an object to a previous version. - /// This will create a new version, which is a copy of all the old data. - /// - /// Id of the being rolled back - /// Id of the version to rollback to - /// Optional Id of the User issueing the rollback of the Content - /// The newly created object - IContent Rollback(int id, Guid versionId, int userId = 0); - - /// - /// Gets a collection of objects by its name or partial name - /// - /// Id of the Parent to retrieve Children from - /// Full or partial name of the children - /// An Enumerable list of objects - IEnumerable GetChildrenByName(int parentId, string name); - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// An Enumerable list of objects - IEnumerable GetDescendants(int id); - - /// - /// Gets a collection of objects by Parent Id - /// - /// item to retrieve Descendants from - /// An Enumerable list of objects - IEnumerable GetDescendants(IContent content); - - /// - /// Gets a specific version of an item. - /// - /// Id of the version to retrieve - /// An item - IContent GetByVersion(Guid versionId); - - /// - /// Gets the published version of an item - /// - /// Id of the to retrieve version from - /// An item - IContent GetPublishedVersion(int id); - - /// - /// Gets the published version of a item. - /// - /// The content item. - /// The published version, if any; otherwise, null. - IContent GetPublishedVersion(IContent content); - - /// - /// Checks whether an item has any children - /// - /// Id of the - /// True if the content has any children otherwise False - bool HasChildren(int id); - - /// - /// Checks whether an item has any published versions - /// - /// Id of the - /// True if the content has any published version otherwise False - bool HasPublishedVersion(int id); - - /// - /// Re-Publishes all Content - /// - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - bool RePublishAll(int userId = 0); - - /// - /// Publishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if publishing succeeded, otherwise False - bool Publish(IContent content, int userId = 0); - - /// - /// Publishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// The published status attempt - Attempt PublishWithStatus(IContent content, int userId = 0); - - /// - /// 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 - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use PublishWithChildrenWithStatus instead, that method will provide more detailed information on the outcome and also allows the includeUnpublished flag")] - bool PublishWithChildren(IContent content, int userId = 0); - - /// - /// 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> PublishWithChildrenWithStatus(IContent content, int userId = 0, bool includeUnpublished = false); - - /// - /// UnPublishes a single object - /// - /// The to publish - /// Optional Id of the User issueing the publishing - /// True if unpublishing succeeded, otherwise False - bool UnPublish(IContent content, int userId = 0); - - /// - /// 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 - [Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")] - [EditorBrowsable(EditorBrowsableState.Never)] - bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); - - /// - /// 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 SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true); - - /// - /// 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 - void Delete(IContent content, int userId = 0); - - /// - /// 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. - /// - /// The to copy - /// Id of the Content's new Parent - /// Boolean indicating whether the copy should be related to the original - /// Optional Id of the User copying the Content - /// The newly created object - IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0); - - /// - /// 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. - /// - /// The to copy - /// Id of the Content's new Parent - /// Boolean indicating whether the copy should be related to the original - /// A value indicating whether to recursively copy children. - /// Optional Id of the User copying the Content - /// The newly created object - IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = 0); - - /// - /// Checks if the passed in can be published based on the anscestors publish state. - /// - /// to check if anscestors are published - /// True if the Content can be published, otherwise False - bool IsPublishable(IContent content); - - /// - /// Gets a collection of objects, which are ancestors of the current content. - /// - /// Id of the to retrieve ancestors for - /// An Enumerable list of objects - IEnumerable GetAncestors(int id); - - /// - /// Gets a collection of objects, which are ancestors of the current content. - /// - /// to retrieve ancestors for - /// An Enumerable list of objects - IEnumerable GetAncestors(IContent content); - - /// - /// Sorts a collection of objects by updating the SortOrder according - /// to the ordering of items in the passed in . - /// - /// - /// Using this method will ensure that the Published-state is maintained upon sorting - /// so the cache is updated accordingly - as needed. - /// - /// - /// - /// - /// True if sorting succeeded, otherwise False - bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true); - - /// - /// Gets the parent of the current content as an item. - /// - /// Id of the to retrieve the parent from - /// Parent object - IContent GetParent(int id); - - /// - /// Gets the parent of the current content as an item. - /// - /// to retrieve the parent from - /// Parent object - IContent GetParent(IContent content); - - /// - /// Creates and saves an object using the alias of the - /// that this Content should based on. - /// - /// - /// 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 - /// - IContent CreateContentWithIdentity(string name, IContent parent, string contentTypeAlias, int userId = 0); - - /// - /// Creates and saves an object using the alias of the - /// that this Content should based on. - /// - /// - /// 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 - /// - IContent CreateContentWithIdentity(string name, int parentId, string contentTypeAlias, int userId = 0); - } + /// + /// A temporary interface until we are in v8, this is used to return a different result for the same method and this interface gets implemented + /// explicitly. These methods will replace the normal ones in IContentService in v8 and this will be removed. + /// + public interface IContentServiceOperations + { + //TODO: Remove this class in v8 + + //TODO: There's probably more that needs to be added like the EmptyRecycleBin, etc... + + /// + /// Saves a single object + /// + /// The to save + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + Attempt Save(IContent content, int userId = 0, bool raiseEvents = true); + + /// + /// 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 Save(IEnumerable contents, int userId = 0, bool raiseEvents = true); + + /// + /// 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 Delete(IContent content, int userId = 0); + + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// The published status attempt + Attempt Publish(IContent content, int userId = 0); + + /// + /// 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> PublishWithChildren(IContent content, int userId = 0, bool includeUnpublished = false); + + /// + /// 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 SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); + + /// + /// 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 MoveToRecycleBin(IContent content, int userId = 0); + + /// + /// UnPublishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if unpublishing succeeded, otherwise False + Attempt UnPublish(IContent content, int userId = 0); + } + + /// + /// Defines the ContentService, which is an easy access to operations involving + /// + public interface IContentService : IService + { + /// + /// 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 + /// + void RebuildXmlStructures(params int[] contentTypeIds); + + int CountPublished(string contentTypeAlias = null); + int Count(string contentTypeAlias = null); + int CountChildren(int parentId, string contentTypeAlias = null); + int CountDescendants(int parentId, string contentTypeAlias = null); + + /// + /// 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. + /// + /// + void ReplaceContentPermissions(EntityPermissionSet permissionSet); + + /// + /// Assigns a single permission to the current content item for the specified user ids + /// + /// + /// + /// + void AssignContentPermission(IContent entity, char permission, IEnumerable userIds); + + /// + /// Gets the list of permissions for the content item + /// + /// + /// + IEnumerable GetPermissionsForEntity(IContent content); + + bool SendToPublication(IContent content, int userId = 0); + + IEnumerable GetByIds(IEnumerable ids); + + /// + /// Creates an object using the alias of the + /// that this Content should based on. + /// + /// + /// 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. + /// + /// Name of the Content object + /// Id of Parent for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + IContent CreateContent(string name, int parentId, string contentTypeAlias, int userId = 0); + + /// + /// Creates an object using the alias of the + /// that this Content should based on. + /// + /// + /// 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. + /// + /// Name of the Content object + /// Parent object for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + IContent CreateContent(string name, IContent parent, string contentTypeAlias, int userId = 0); + + /// + /// Gets an object by Id + /// + /// Id of the Content to retrieve + /// + IContent GetById(int id); + + /// + /// Gets an object by its 'UniqueId' + /// + /// Guid key of the Content to retrieve + /// + IContent GetById(Guid key); + + /// + /// Gets a collection of objects by the Id of the + /// + /// Id of the + /// An Enumerable list of objects + IEnumerable GetContentOfContentType(int id); + + /// + /// Gets a collection of objects by Level + /// + /// The level to retrieve Content from + /// An Enumerable list of objects + IEnumerable GetByLevel(int level); + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// An Enumerable list of objects + IEnumerable GetChildren(int id); + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, + string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + + /// + /// Gets a collection of an objects versions by its Id + /// + /// + /// An Enumerable list of objects + IEnumerable GetVersions(int id); + + /// + /// Gets a collection of objects, which reside at the first level / root + /// + /// An Enumerable list of objects + IEnumerable GetRootContent(); + + /// + /// Gets a collection of objects, which has an expiration date greater then today + /// + /// An Enumerable list of objects + IEnumerable GetContentForExpiration(); + + /// + /// Gets a collection of objects, which has a release date greater then today + /// + /// An Enumerable list of objects + IEnumerable GetContentForRelease(); + + /// + /// Gets a collection of an objects, which resides in the Recycle Bin + /// + /// An Enumerable list of objects + IEnumerable GetContentInRecycleBin(); + + /// + /// Saves a single object + /// + /// The to save + /// Optional Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + void Save(IContent content, int userId = 0, bool raiseEvents = true); + + /// + /// 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. + void Save(IEnumerable contents, int userId = 0, bool raiseEvents = true); + + /// + /// 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 + void DeleteContentOfType(int contentTypeId, int userId = 0); + + /// + /// Permanently deletes versions from an object prior to a specific date. + /// + /// Id of the object to delete versions from + /// Latest version date + /// Optional Id of the User deleting versions of a Content object + void DeleteVersions(int id, DateTime versionDate, int userId = 0); + + /// + /// Permanently deletes a specific version from an object. + /// + /// Id of the object to delete a version from + /// Id of the version to delete + /// Boolean indicating whether to delete versions prior to the versionId + /// Optional Id of the User deleting versions of a Content object + void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0); + + /// + /// 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 + void MoveToRecycleBin(IContent content, int userId = 0); + + /// + /// Moves an object to a new location + /// + /// The to move + /// Id of the Content's new Parent + /// Optional Id of the User moving the Content + void Move(IContent content, int parentId, int userId = 0); + + /// + /// Empties the Recycle Bin by deleting all that resides in the bin + /// + void EmptyRecycleBin(); + + /// + /// Rollback an object to a previous version. + /// This will create a new version, which is a copy of all the old data. + /// + /// Id of the being rolled back + /// Id of the version to rollback to + /// Optional Id of the User issueing the rollback of the Content + /// The newly created object + IContent Rollback(int id, Guid versionId, int userId = 0); + + /// + /// Gets a collection of objects by its name or partial name + /// + /// Id of the Parent to retrieve Children from + /// Full or partial name of the children + /// An Enumerable list of objects + IEnumerable GetChildrenByName(int parentId, string name); + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// An Enumerable list of objects + IEnumerable GetDescendants(int id); + + /// + /// Gets a collection of objects by Parent Id + /// + /// item to retrieve Descendants from + /// An Enumerable list of objects + IEnumerable GetDescendants(IContent content); + + /// + /// Gets a specific version of an item. + /// + /// Id of the version to retrieve + /// An item + IContent GetByVersion(Guid versionId); + + /// + /// Gets the published version of an item + /// + /// Id of the to retrieve version from + /// An item + IContent GetPublishedVersion(int id); + + /// + /// Gets the published version of a item. + /// + /// The content item. + /// The published version, if any; otherwise, null. + IContent GetPublishedVersion(IContent content); + + /// + /// Checks whether an item has any children + /// + /// Id of the + /// True if the content has any children otherwise False + bool HasChildren(int id); + + /// + /// Checks whether an item has any published versions + /// + /// Id of the + /// True if the content has any published version otherwise False + bool HasPublishedVersion(int id); + + /// + /// Re-Publishes all Content + /// + /// Optional Id of the User issueing the publishing + /// True if publishing succeeded, otherwise False + bool RePublishAll(int userId = 0); + + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if publishing succeeded, otherwise False + bool Publish(IContent content, int userId = 0); + + /// + /// Publishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// The published status attempt + Attempt PublishWithStatus(IContent content, int userId = 0); + + /// + /// 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 + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use PublishWithChildrenWithStatus instead, that method will provide more detailed information on the outcome and also allows the includeUnpublished flag")] + bool PublishWithChildren(IContent content, int userId = 0); + + /// + /// 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> PublishWithChildrenWithStatus(IContent content, int userId = 0, bool includeUnpublished = false); + + /// + /// UnPublishes a single object + /// + /// The to publish + /// Optional Id of the User issueing the publishing + /// True if unpublishing succeeded, otherwise False + bool UnPublish(IContent content, int userId = 0); + + /// + /// 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 + [Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")] + [EditorBrowsable(EditorBrowsableState.Never)] + bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); + + /// + /// 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 SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true); + + /// + /// 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 + void Delete(IContent content, int userId = 0); + + /// + /// 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. + /// + /// The to copy + /// Id of the Content's new Parent + /// Boolean indicating whether the copy should be related to the original + /// Optional Id of the User copying the Content + /// The newly created object + IContent Copy(IContent content, int parentId, bool relateToOriginal, int userId = 0); + + /// + /// 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. + /// + /// The to copy + /// Id of the Content's new Parent + /// Boolean indicating whether the copy should be related to the original + /// A value indicating whether to recursively copy children. + /// Optional Id of the User copying the Content + /// The newly created object + IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = 0); + + /// + /// Checks if the passed in can be published based on the anscestors publish state. + /// + /// to check if anscestors are published + /// True if the Content can be published, otherwise False + bool IsPublishable(IContent content); + + /// + /// Gets a collection of objects, which are ancestors of the current content. + /// + /// Id of the to retrieve ancestors for + /// An Enumerable list of objects + IEnumerable GetAncestors(int id); + + /// + /// Gets a collection of objects, which are ancestors of the current content. + /// + /// to retrieve ancestors for + /// An Enumerable list of objects + IEnumerable GetAncestors(IContent content); + + /// + /// Sorts a collection of objects by updating the SortOrder according + /// to the ordering of items in the passed in . + /// + /// + /// Using this method will ensure that the Published-state is maintained upon sorting + /// so the cache is updated accordingly - as needed. + /// + /// + /// + /// + /// True if sorting succeeded, otherwise False + bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true); + + /// + /// Gets the parent of the current content as an item. + /// + /// Id of the to retrieve the parent from + /// Parent object + IContent GetParent(int id); + + /// + /// Gets the parent of the current content as an item. + /// + /// to retrieve the parent from + /// Parent object + IContent GetParent(IContent content); + + /// + /// Creates and saves an object using the alias of the + /// that this Content should based on. + /// + /// + /// 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 + /// + IContent CreateContentWithIdentity(string name, IContent parent, string contentTypeAlias, int userId = 0); + + /// + /// Creates and saves an object using the alias of the + /// that this Content should based on. + /// + /// + /// 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 + /// + IContent CreateContentWithIdentity(string name, int parentId, string contentTypeAlias, int userId = 0); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index 0bb56bb44b..51c01703d5 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -7,370 +7,370 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Services { - /// - /// A temporary interface until we are in v8, this is used to return a different result for the same method and this interface gets implemented - /// explicitly. These methods will replace the normal ones in IContentService in v8 and this will be removed. - /// - public interface IMediaServiceOperations - { - //TODO: Remove this class in v8 + /// + /// A temporary interface until we are in v8, this is used to return a different result for the same method and this interface gets implemented + /// explicitly. These methods will replace the normal ones in IContentService in v8 and this will be removed. + /// + public interface IMediaServiceOperations + { + //TODO: Remove this class in v8 - //TODO: There's probably more that needs to be added like the EmptyRecycleBin, etc... + //TODO: There's probably more that needs to be added like the EmptyRecycleBin, etc... - /// - /// Deletes an object by moving it to the Recycle Bin - /// - /// The to delete - /// Id of the User deleting the Media - Attempt MoveToRecycleBin(IMedia media, int userId = 0); + /// + /// Deletes an object by moving it to the Recycle Bin + /// + /// The to delete + /// Id of the User deleting the Media + Attempt MoveToRecycleBin(IMedia media, int userId = 0); - /// - /// Permanently deletes an object - /// - /// - /// Please note that this method will completely remove the Media from the database, - /// but current not from the file system. - /// - /// The to delete - /// Id of the User deleting the Media - Attempt Delete(IMedia media, int userId = 0); + /// + /// Permanently deletes an object + /// + /// + /// Please note that this method will completely remove the Media from the database, + /// but current not from the file system. + /// + /// The to delete + /// Id of the User deleting the Media + Attempt Delete(IMedia media, int userId = 0); - /// - /// Saves a single object - /// - /// The to save - /// Id of the User saving the Media - /// Optional boolean indicating whether or not to raise events. - Attempt Save(IMedia media, int userId = 0, bool raiseEvents = true); + /// + /// Saves a single object + /// + /// The to save + /// Id of the User saving the Media + /// Optional boolean indicating whether or not to raise events. + Attempt Save(IMedia media, int userId = 0, bool raiseEvents = true); - /// - /// Saves a collection of objects - /// - /// Collection of to save - /// Id of the User saving the Media - /// Optional boolean indicating whether or not to raise events. - Attempt Save(IEnumerable medias, int userId = 0, bool raiseEvents = true); - } + /// + /// Saves a collection of objects + /// + /// Collection of to save + /// Id of the User saving the Media + /// Optional boolean indicating whether or not to raise events. + Attempt Save(IEnumerable medias, int userId = 0, bool raiseEvents = true); + } - /// - /// Defines the Media Service, which is an easy access to operations involving - /// - public interface IMediaService : IService - { - /// - /// Rebuilds all xml content in the cmsContentXml table for all media - /// - /// - /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures - /// for all media - /// - void RebuildXmlStructures(params int[] contentTypeIds); + /// + /// Defines the Media Service, which is an easy access to operations involving + /// + public interface IMediaService : IService + { + /// + /// Rebuilds all xml content in the cmsContentXml table for all media + /// + /// + /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures + /// for all media + /// + void RebuildXmlStructures(params int[] contentTypeIds); - int Count(string contentTypeAlias = null); - int CountChildren(int parentId, string contentTypeAlias = null); - int CountDescendants(int parentId, string contentTypeAlias = null); + int Count(string contentTypeAlias = null); + int CountChildren(int parentId, string contentTypeAlias = null); + int CountDescendants(int parentId, string contentTypeAlias = null); - IEnumerable GetByIds(IEnumerable ids); + IEnumerable GetByIds(IEnumerable ids); - /// - /// Creates an object using the alias of the - /// that this Media should based on. - /// - /// - /// Note that using this method will simply return a new IMedia without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects - /// that does not invoke a save operation against the database. - /// - /// Name of the Media object - /// Id of Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// - IMedia CreateMedia(string name, int parentId, string mediaTypeAlias, int userId = 0); + /// + /// Creates an object using the alias of the + /// that this Media should based on. + /// + /// + /// Note that using this method will simply return a new IMedia without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects + /// that does not invoke a save operation against the database. + /// + /// Name of the Media object + /// Id of Parent for the new Media item + /// Alias of the + /// Optional id of the user creating the media item + /// + IMedia CreateMedia(string name, int parentId, string mediaTypeAlias, int userId = 0); - /// - /// Creates an object using the alias of the - /// that this Media should based on. - /// - /// - /// Note that using this method will simply return a new IMedia without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects - /// that does not invoke a save operation against the database. - /// - /// Name of the Media object - /// Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// - IMedia CreateMedia(string name, IMedia parent, string mediaTypeAlias, int userId = 0); + /// + /// Creates an object using the alias of the + /// that this Media should based on. + /// + /// + /// Note that using this method will simply return a new IMedia without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects + /// that does not invoke a save operation against the database. + /// + /// Name of the Media object + /// Parent for the new Media item + /// Alias of the + /// Optional id of the user creating the media item + /// + IMedia CreateMedia(string name, IMedia parent, string mediaTypeAlias, int userId = 0); - /// - /// Gets an object by Id - /// - /// Id of the Content to retrieve - /// - IMedia GetById(int id); + /// + /// Gets an object by Id + /// + /// Id of the Content to retrieve + /// + IMedia GetById(int id); - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// An Enumerable list of objects - IEnumerable GetChildren(int id); + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// An Enumerable list of objects + IEnumerable GetChildren(int id); - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// An Enumerable list of objects - IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, + string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// An Enumerable list of objects - IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); - /// - /// Gets descendants of a object by its Id - /// - /// Id of the Parent to retrieve descendants from - /// An Enumerable flat list of objects - IEnumerable GetDescendants(int id); + /// + /// Gets descendants of a object by its Id + /// + /// Id of the Parent to retrieve descendants from + /// An Enumerable flat list of objects + IEnumerable GetDescendants(int id); - /// - /// Gets a collection of objects by the Id of the - /// - /// Id of the - /// An Enumerable list of objects - IEnumerable GetMediaOfMediaType(int id); + /// + /// Gets a collection of objects by the Id of the + /// + /// Id of the + /// An Enumerable list of objects + IEnumerable GetMediaOfMediaType(int id); - /// - /// Gets a collection of objects, which reside at the first level / root - /// - /// An Enumerable list of objects - IEnumerable GetRootMedia(); + /// + /// Gets a collection of objects, which reside at the first level / root + /// + /// An Enumerable list of objects + IEnumerable GetRootMedia(); - /// - /// Gets a collection of an objects, which resides in the Recycle Bin - /// - /// An Enumerable list of objects - IEnumerable GetMediaInRecycleBin(); + /// + /// Gets a collection of an objects, which resides in the Recycle Bin + /// + /// An Enumerable list of objects + IEnumerable GetMediaInRecycleBin(); - /// - /// Moves an object to a new location - /// - /// The to move - /// Id of the Media's new Parent - /// Id of the User moving the Media - void Move(IMedia media, int parentId, int userId = 0); + /// + /// Moves an object to a new location + /// + /// The to move + /// Id of the Media's new Parent + /// Id of the User moving the Media + void Move(IMedia media, int parentId, int userId = 0); - /// - /// Deletes an object by moving it to the Recycle Bin - /// - /// The to delete - /// Id of the User deleting the Media - void MoveToRecycleBin(IMedia media, int userId = 0); + /// + /// Deletes an object by moving it to the Recycle Bin + /// + /// The to delete + /// Id of the User deleting the Media + void MoveToRecycleBin(IMedia media, int userId = 0); - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - void EmptyRecycleBin(); + /// + /// Empties the Recycle Bin by deleting all that resides in the bin + /// + void EmptyRecycleBin(); - /// - /// Deletes all media of specified type. All children of deleted media 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 deleting Media - void DeleteMediaOfType(int mediaTypeId, int userId = 0); + /// + /// Deletes all media of specified type. All children of deleted media 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 deleting Media + void DeleteMediaOfType(int mediaTypeId, int userId = 0); - /// - /// Permanently deletes an object - /// - /// - /// Please note that this method will completely remove the Media from the database, - /// but current not from the file system. - /// - /// The to delete - /// Id of the User deleting the Media - void Delete(IMedia media, int userId = 0); + /// + /// Permanently deletes an object + /// + /// + /// Please note that this method will completely remove the Media from the database, + /// but current not from the file system. + /// + /// The to delete + /// Id of the User deleting the Media + void Delete(IMedia media, int userId = 0); - /// - /// Saves a single object - /// - /// The to save - /// Id of the User saving the Media - /// Optional boolean indicating whether or not to raise events. - void Save(IMedia media, int userId = 0, bool raiseEvents = true); + /// + /// Saves a single object + /// + /// The to save + /// Id of the User saving the Media + /// Optional boolean indicating whether or not to raise events. + void Save(IMedia media, int userId = 0, bool raiseEvents = true); - /// - /// Saves a collection of objects - /// - /// Collection of to save - /// Id of the User saving the Media - /// Optional boolean indicating whether or not to raise events. - void Save(IEnumerable medias, int userId = 0, bool raiseEvents = true); + /// + /// Saves a collection of objects + /// + /// Collection of to save + /// Id of the User saving the Media + /// Optional boolean indicating whether or not to raise events. + void Save(IEnumerable medias, int userId = 0, bool raiseEvents = true); - /// - /// Gets an object by its 'UniqueId' - /// - /// Guid key of the Media to retrieve - /// - IMedia GetById(Guid key); + /// + /// Gets an object by its 'UniqueId' + /// + /// Guid key of the Media to retrieve + /// + IMedia GetById(Guid key); - /// - /// Gets a collection of objects by Level - /// - /// The level to retrieve Media from - /// An Enumerable list of objects - IEnumerable GetByLevel(int level); + /// + /// Gets a collection of objects by Level + /// + /// The level to retrieve Media from + /// An Enumerable list of objects + IEnumerable GetByLevel(int level); - /// - /// Gets a specific version of an item. - /// - /// Id of the version to retrieve - /// An item - IMedia GetByVersion(Guid versionId); + /// + /// Gets a specific version of an item. + /// + /// Id of the version to retrieve + /// An item + IMedia GetByVersion(Guid versionId); - /// - /// Gets a collection of an objects versions by Id - /// - /// - /// An Enumerable list of objects - IEnumerable GetVersions(int id); + /// + /// Gets a collection of an objects versions by Id + /// + /// + /// An Enumerable list of objects + IEnumerable GetVersions(int id); - /// - /// Checks whether an item has any children - /// - /// Id of the - /// True if the media has any children otherwise False - bool HasChildren(int id); + /// + /// Checks whether an item has any children + /// + /// Id of the + /// True if the media has any children otherwise False + bool HasChildren(int id); - /// - /// Permanently deletes versions from an object prior to a specific date. - /// - /// Id of the object to delete versions from - /// Latest version date - /// Optional Id of the User deleting versions of a Content object - void DeleteVersions(int id, DateTime versionDate, int userId = 0); + /// + /// Permanently deletes versions from an object prior to a specific date. + /// + /// Id of the object to delete versions from + /// Latest version date + /// Optional Id of the User deleting versions of a Content object + void DeleteVersions(int id, DateTime versionDate, int userId = 0); - /// - /// Permanently deletes specific version(s) from an object. - /// - /// Id of the object to delete a version from - /// Id of the version to delete - /// Boolean indicating whether to delete versions prior to the versionId - /// Optional Id of the User deleting versions of a Content object - void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0); + /// + /// Permanently deletes specific version(s) from an object. + /// + /// Id of the object to delete a version from + /// Id of the version to delete + /// Boolean indicating whether to delete versions prior to the versionId + /// Optional Id of the User deleting versions of a Content object + void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0); - /// - /// Gets an object from the path stored in the 'umbracoFile' property. - /// - /// Path of the media item to retrieve (for example: /media/1024/koala_403x328.jpg) - /// - IMedia GetMediaByPath(string mediaPath); + /// + /// Gets an object from the path stored in the 'umbracoFile' property. + /// + /// Path of the media item to retrieve (for example: /media/1024/koala_403x328.jpg) + /// + IMedia GetMediaByPath(string mediaPath); - /// - /// Gets a collection of objects, which are ancestors of the current media. - /// - /// Id of the to retrieve ancestors for - /// An Enumerable list of objects - IEnumerable GetAncestors(int id); + /// + /// Gets a collection of objects, which are ancestors of the current media. + /// + /// Id of the to retrieve ancestors for + /// An Enumerable list of objects + IEnumerable GetAncestors(int id); - /// - /// Gets a collection of objects, which are ancestors of the current media. - /// - /// to retrieve ancestors for - /// An Enumerable list of objects - IEnumerable GetAncestors(IMedia media); + /// + /// Gets a collection of objects, which are ancestors of the current media. + /// + /// to retrieve ancestors for + /// An Enumerable list of objects + IEnumerable GetAncestors(IMedia media); - /// - /// Gets descendants of a object by its Id - /// - /// The Parent object to retrieve descendants from - /// An Enumerable flat list of objects - IEnumerable GetDescendants(IMedia media); + /// + /// Gets descendants of a object by its Id + /// + /// The Parent object to retrieve descendants from + /// An Enumerable flat list of objects + IEnumerable GetDescendants(IMedia media); - /// - /// Gets the parent of the current media as an item. - /// - /// Id of the to retrieve the parent from - /// Parent object - IMedia GetParent(int id); + /// + /// Gets the parent of the current media as an item. + /// + /// Id of the to retrieve the parent from + /// Parent object + IMedia GetParent(int id); - /// - /// Gets the parent of the current media as an item. - /// - /// to retrieve the parent from - /// Parent object - IMedia GetParent(IMedia media); + /// + /// Gets the parent of the current media as an item. + /// + /// to retrieve the parent from + /// Parent object + IMedia GetParent(IMedia media); - /// - /// Sorts a collection of objects by updating the SortOrder according - /// to the ordering of items in the passed in . - /// - /// - /// - /// - /// True if sorting succeeded, otherwise False - bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true); + /// + /// Sorts a collection of objects by updating the SortOrder according + /// to the ordering of items in the passed in . + /// + /// + /// + /// + /// True if sorting succeeded, otherwise False + bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true); - /// - /// Creates an object using the alias of the - /// that this Media should based on. - /// - /// - /// This method returns an object that has been persisted to the database - /// and therefor has an identity. - /// - /// Name of the Media object - /// Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// - IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = 0); + /// + /// Creates an object using the alias of the + /// that this Media should based on. + /// + /// + /// This method returns an object that has been persisted to the database + /// and therefor has an identity. + /// + /// Name of the Media object + /// Parent for the new Media item + /// Alias of the + /// Optional id of the user creating the media item + /// + IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = 0); - /// - /// Creates an object using the alias of the - /// that this Media should based on. - /// - /// - /// This method returns an object that has been persisted to the database - /// and therefor has an identity. - /// - /// Name of the Media object - /// Id of Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// - IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = 0); - } + /// + /// Creates an object using the alias of the + /// that this Media should based on. + /// + /// + /// This method returns an object that has been persisted to the database + /// and therefor has an identity. + /// + /// Name of the Media object + /// Id of Parent for the new Media item + /// Alias of the + /// Optional id of the user creating the media item + /// + IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = 0); + } } diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 2177d3b14e..3756ca246d 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -8,214 +8,214 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Services { - /// - /// Defines the MemberService, which is an easy access to operations involving (umbraco) members. - /// - public interface IMemberService : IMembershipMemberService - { - /// - /// 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 - /// - void RebuildXmlStructures(params int[] contentTypeIds); + /// + /// Defines the MemberService, which is an easy access to operations involving (umbraco) members. + /// + public interface IMemberService : IMembershipMemberService + { + /// + /// 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 + /// + void RebuildXmlStructures(params int[] contentTypeIds); - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = ""); + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = ""); - /// - /// Gets a list of paged objects - /// - /// An can be of type - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// - /// Search text filter - /// - IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = ""); + /// + /// Gets a list of paged objects + /// + /// An can be of type + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// + /// Search text filter + /// + IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = ""); - /// - /// Creates an object without persisting it - /// - /// This method is convenient for when you need to add properties to a new Member - /// before persisting it in order to limit the amount of times its saved. - /// Also note that the returned will not have an Id until its saved. - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// Alias of the MemberType the Member should be based on - /// - IMember CreateMember(string username, string email, string name, string memberTypeAlias); + /// + /// Creates an object without persisting it + /// + /// This method is convenient for when you need to add properties to a new Member + /// before persisting it in order to limit the amount of times its saved. + /// Also note that the returned will not have an Id until its saved. + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// Alias of the MemberType the Member should be based on + /// + IMember CreateMember(string username, string email, string name, string memberTypeAlias); - /// - /// Creates an object without persisting it - /// - /// This method is convenient for when you need to add properties to a new Member - /// before persisting it in order to limit the amount of times its saved. - /// Also note that the returned will not have an Id until its saved. - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// MemberType the Member should be based on - /// - IMember CreateMember(string username, string email, string name, IMemberType memberType); + /// + /// Creates an object without persisting it + /// + /// This method is convenient for when you need to add properties to a new Member + /// before persisting it in order to limit the amount of times its saved. + /// Also note that the returned will not have an Id until its saved. + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// MemberType the Member should be based on + /// + IMember CreateMember(string username, string email, string name, IMemberType memberType); - /// - /// Creates and persists a Member - /// - /// Using this method will persist the Member object before its returned - /// meaning that it will have an Id available (unlike the CreateMember method) - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// Alias of the MemberType the Member should be based on - /// - IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias); + /// + /// Creates and persists a Member + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// Alias of the MemberType the Member should be based on + /// + IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias); - /// - /// Creates and persists a Member - /// - /// Using this method will persist the Member object before its returned - /// meaning that it will have an Id available (unlike the CreateMember method) - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// MemberType the Member should be based on - /// - IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType); + /// + /// Creates and persists a Member + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// MemberType the Member should be based on + /// + IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType); - /// - /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method - /// - /// This method exists so that Umbraco developers can use one entry point to create/update - /// Members if they choose to. - /// The Member to save the password for - /// The password to encrypt and save - void SavePassword(IMember member, string password); + /// + /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method + /// + /// This method exists so that Umbraco developers can use one entry point to create/update + /// Members if they choose to. + /// The Member to save the password for + /// The password to encrypt and save + void SavePassword(IMember member, string password); - /// - /// Gets the count of Members by an optional MemberType alias - /// - /// If no alias is supplied then the count for all Member will be returned - /// Optional alias for the MemberType when counting number of Members - /// with number of Members - int Count(string memberTypeAlias = null); + /// + /// Gets the count of Members by an optional MemberType alias + /// + /// If no alias is supplied then the count for all Member will be returned + /// Optional alias for the MemberType when counting number of Members + /// with number of Members + int Count(string memberTypeAlias = null); - /// - /// Checks if a Member with the id exists - /// - /// Id of the Member - /// True if the Member exists otherwise False - bool Exists(int id); + /// + /// Checks if a Member with the id exists + /// + /// Id of the Member + /// True if the Member exists otherwise False + bool Exists(int id); - /// - /// Gets a Member by the unique key - /// - /// The guid key corresponds to the unique id in the database - /// and the user id in the membership provider. - /// Id - /// - IMember GetByKey(Guid id); + /// + /// Gets a Member by the unique key + /// + /// The guid key corresponds to the unique id in the database + /// and the user id in the membership provider. + /// Id + /// + IMember GetByKey(Guid id); - /// - /// Gets a Member by its integer id - /// - /// Id - /// - IMember GetById(int id); + /// + /// Gets a Member by its integer id + /// + /// Id + /// + IMember GetById(int id); - /// - /// Gets all Members for the specified MemberType alias - /// - /// Alias of the MemberType - /// - IEnumerable GetMembersByMemberType(string memberTypeAlias); + /// + /// Gets all Members for the specified MemberType alias + /// + /// Alias of the MemberType + /// + IEnumerable GetMembersByMemberType(string memberTypeAlias); - /// - /// Gets all Members for the MemberType id - /// - /// Id of the MemberType - /// - IEnumerable GetMembersByMemberType(int memberTypeId); + /// + /// Gets all Members for the MemberType id + /// + /// Id of the MemberType + /// + IEnumerable GetMembersByMemberType(int memberTypeId); - /// - /// Gets all Members within the specified MemberGroup name - /// - /// Name of the MemberGroup - /// - IEnumerable GetMembersByGroup(string memberGroupName); + /// + /// Gets all Members within the specified MemberGroup name + /// + /// Name of the MemberGroup + /// + IEnumerable GetMembersByGroup(string memberGroupName); - /// - /// Gets all Members with the ids specified - /// - /// If no Ids are specified all Members will be retrieved - /// Optional list of Member Ids - /// - IEnumerable GetAllMembers(params int[] ids); + /// + /// Gets all Members with the ids specified + /// + /// If no Ids are specified all Members will be retrieved + /// Optional list of Member Ids + /// + IEnumerable GetAllMembers(params int[] ids); - /// - /// Delete Members of the specified MemberType id - /// - /// Id of the MemberType - void DeleteMembersOfType(int memberTypeId); + /// + /// Delete Members of the specified MemberType id + /// + /// Id of the MemberType + void DeleteMembersOfType(int memberTypeId); - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable FindMembersByDisplayName(string displayNameToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + IEnumerable FindMembersByDisplayName(string displayNameToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); - /// - /// Finds Members based on their display name - /// - /// Display name to match - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// The type of match to make as . Default is - /// - IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + /// + /// Finds Members based on their display name + /// + /// Display name to match + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// The type of match to make as . Default is + /// + IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// The type of match to make as . Default is - /// - IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact); + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// The type of match to make as . Default is + /// + IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact); - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// The type of match to make as . Default is - /// - IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact); + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// The type of match to make as . Default is + /// + IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact); - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// - IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, bool value); + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// + IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, bool value); - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// The type of match to make as . Default is - /// - IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact); - } + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// The type of match to make as . Default is + /// + IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact); + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 5af8487b3f..3bbf8860ea 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -22,1318 +22,1318 @@ using Umbraco.Core.Publishing; namespace Umbraco.Core.Services { - /// - /// Represents the Media Service, which is an easy access to operations involving - /// - public class MediaService : RepositoryService, IMediaService, IMediaServiceOperations - { - - //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); - - private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); - private readonly IDataTypeService _dataTypeService; - private readonly IUserService _userService; - - public MediaService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IDataTypeService dataTypeService, IUserService userService) - : base(provider, repositoryFactory, logger, eventMessagesFactory) - { - if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); - if (userService == null) throw new ArgumentNullException("userService"); - _dataTypeService = dataTypeService; - _userService = userService; - } - - /// - /// Creates an object using the alias of the - /// that this Media should based on. - /// - /// - /// Note that using this method will simply return a new IMedia without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects - /// that does not invoke a save operation against the database. - /// - /// Name of the Media object - /// Id of Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// - public IMedia CreateMedia(string name, int parentId, string mediaTypeAlias, int userId = 0) - { - var mediaType = FindMediaTypeByAlias(mediaTypeAlias); - var media = new Models.Media(name, parentId, mediaType); - var parent = GetById(media.ParentId); - media.Path = string.Concat(parent.IfNotNull(x => x.Path, media.ParentId.ToString()), ",", media.Id); - - if (Creating.IsRaisedEventCancelled(new NewEventArgs(media, mediaTypeAlias, parentId), this)) - { - media.WasCancelled = true; - return media; - } - - media.CreatorId = userId; - - Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parentId), this); - - Audit(AuditType.New, string.Format("Media '{0}' was created", name), media.CreatorId, media.Id); - - return media; - } - - /// - /// Creates an object using the alias of the - /// that this Media should based on. - /// - /// - /// Note that using this method will simply return a new IMedia without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects - /// that does not invoke a save operation against the database. - /// - /// Name of the Media object - /// Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// - public IMedia CreateMedia(string name, IMedia parent, string mediaTypeAlias, int userId = 0) - { - if (parent == null) throw new ArgumentNullException("parent"); - - var mediaType = FindMediaTypeByAlias(mediaTypeAlias); - var media = new Models.Media(name, parent, mediaType); - media.Path = string.Concat(parent.Path, ",", media.Id); - - if (Creating.IsRaisedEventCancelled(new NewEventArgs(media, mediaTypeAlias, parent), this)) - { - media.WasCancelled = true; - return media; - } - - media.CreatorId = userId; - - Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parent), this); - - Audit(AuditType.New, string.Format("Media '{0}' was created", name), media.CreatorId, media.Id); - - return media; - } - - /// - /// Creates an object using the alias of the - /// that this Media should based on. - /// - /// - /// This method returns an object that has been persisted to the database - /// and therefor has an identity. - /// - /// Name of the Media object - /// Id of Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// - public IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = 0) - { - var mediaType = FindMediaTypeByAlias(mediaTypeAlias); - var media = new Models.Media(name, parentId, mediaType); - - //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(media, mediaTypeAlias, parentId), this)) - { - media.WasCancelled = true; - return media; - } - - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(media), this)) - { - media.WasCancelled = true; - return media; - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - media.CreatorId = userId; - repository.AddOrUpdate(media); - - repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - } - - uow.Commit(); - } - - Saved.RaiseEvent(new SaveEventArgs(media, false), this); - - Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parentId), this); - - Audit(AuditType.New, string.Format("Media '{0}' was created with Id {1}", name, media.Id), media.CreatorId, media.Id); - - return media; - } - - /// - /// Creates an object using the alias of the - /// that this Media should based on. - /// - /// - /// This method returns an object that has been persisted to the database - /// and therefor has an identity. - /// - /// Name of the Media object - /// Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// - public IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = 0) - { - if (parent == null) throw new ArgumentNullException("parent"); - - var mediaType = FindMediaTypeByAlias(mediaTypeAlias); - var media = new Models.Media(name, parent, mediaType); - - //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(media, mediaTypeAlias, parent), this)) - { - media.WasCancelled = true; - return media; - } - - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(media), this)) - { - media.WasCancelled = true; - return media; - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - media.CreatorId = userId; - repository.AddOrUpdate(media); - repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - } - - uow.Commit(); - } - - Saved.RaiseEvent(new SaveEventArgs(media, false), this); - - Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parent), this); - - Audit(AuditType.New, string.Format("Media '{0}' was created with Id {1}", name, media.Id), media.CreatorId, media.Id); - - return media; - } - - /// - /// Gets an object by Id - /// - /// Id of the Content to retrieve - /// - public IMedia GetById(int id) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - return repository.Get(id); - } - } - - public int Count(string contentTypeAlias = null) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - return repository.Count(contentTypeAlias); - } - } - - public int CountChildren(int parentId, string contentTypeAlias = null) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - return repository.CountChildren(parentId, contentTypeAlias); - } - } - - public int CountDescendants(int parentId, string contentTypeAlias = null) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - return repository.CountDescendants(parentId, contentTypeAlias); - } - } - - /// - /// Gets an object by Id - /// - /// Ids of the Media to retrieve - /// - public IEnumerable GetByIds(IEnumerable ids) - { - if (ids.Any() == false) return Enumerable.Empty(); - - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - return repository.GetAll(ids.ToArray()); - } - } - - /// - /// Gets an object by its 'UniqueId' - /// - /// Guid key of the Media to retrieve - /// - public IMedia GetById(Guid key) - { - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Key == key); - var contents = repository.GetByQuery(query); - return contents.SingleOrDefault(); - } - } - - /// - /// Gets a collection of objects by Level - /// - /// The level to retrieve Media from - /// An Enumerable list of objects - public IEnumerable GetByLevel(int level) - { - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Level == level && !x.Path.StartsWith("-21")); - var contents = repository.GetByQuery(query); - - return contents; - } - } - - /// - /// Gets a specific version of an item. - /// - /// Id of the version to retrieve - /// An item - public IMedia GetByVersion(Guid versionId) - { - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - return repository.GetByVersion(versionId); - } - } - - /// - /// Gets a collection of an objects versions by Id - /// - /// - /// An Enumerable list of objects - public IEnumerable GetVersions(int id) - { - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - var versions = repository.GetAllVersions(id); - return versions; - } - } - - /// - /// Gets a collection of objects, which are ancestors of the current media. - /// - /// Id of the to retrieve ancestors for - /// An Enumerable list of objects - public IEnumerable GetAncestors(int id) - { - var media = GetById(id); - return GetAncestors(media); - } - - /// - /// Gets a collection of objects, which are ancestors of the current media. - /// - /// to retrieve ancestors for - /// An Enumerable list of objects - public IEnumerable GetAncestors(IMedia media) - { - var ids = media.Path.Split(',').Where(x => x != "-1" && x != media.Id.ToString(CultureInfo.InvariantCulture)).Select(int.Parse).ToArray(); - if (ids.Any() == false) - return new List(); - - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - return repository.GetAll(ids); - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// An Enumerable list of objects - public IEnumerable GetChildren(int id) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - var query = Query.Builder.Where(x => x.ParentId == id); - var medias = repository.GetByQuery(query); - - return medias; - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, - string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder; - query.Where(x => x.ParentId == id); - - long total; - var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, orderBySystemField, filter); - - totalChildren = Convert.ToInt32(total); - return medias; - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// Page index (zero based) - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// An Enumerable list of objects - public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder; - query.Where(x => x.ParentId == id); - - var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); - - return medias; - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - - var query = Query.Builder; - //if the id is -1, then just get all - if (id != -1) - { - query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); - } - long total; - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, orderBySystemField, filter); - totalChildren = Convert.ToInt32(total); - return contents; - } - } - - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Descendants from - /// Page number - /// Page size - /// Total records query would return without paging - /// Field to order by - /// Direction to order by - /// Flag to indicate when ordering by system field - /// Search text filter - /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - - var query = Query.Builder; - //if the id is -1, then just get all - if (id != -1) - { - query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); - } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); - - return contents; - } - } - - /// - /// Gets descendants of a object by its Id - /// - /// Id of the Parent to retrieve descendants from - /// An Enumerable flat list of objects - public IEnumerable GetDescendants(int id) - { - var media = GetById(id); - if (media == null) - { - return Enumerable.Empty(); - } - return GetDescendants(media); - } - - /// - /// Gets descendants of a object by its Id - /// - /// The Parent object to retrieve descendants from - /// An Enumerable flat list of objects - public IEnumerable GetDescendants(IMedia media) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - var pathMatch = media.Path + ","; - var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != media.Id); - var medias = repository.GetByQuery(query); - - return medias; - } - } - - /// - /// Gets the parent of the current media as an item. - /// - /// Id of the to retrieve the parent from - /// Parent object - public IMedia GetParent(int id) - { - var media = GetById(id); - return GetParent(media); - } - - /// - /// Gets the parent of the current media as an item. - /// - /// to retrieve the parent from - /// Parent object - public IMedia GetParent(IMedia media) - { - if (media.ParentId == -1 || media.ParentId == -21) - return null; - - return GetById(media.ParentId); - } - - /// - /// Gets a collection of objects by the Id of the - /// - /// Id of the - /// An Enumerable list of objects - public IEnumerable GetMediaOfMediaType(int id) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - var query = Query.Builder.Where(x => x.ContentTypeId == id); - var medias = repository.GetByQuery(query); - - return medias; - } - } - - /// - /// Gets a collection of objects, which reside at the first level / root - /// - /// An Enumerable list of objects - public IEnumerable GetRootMedia() - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - var query = Query.Builder.Where(x => x.ParentId == -1); - var medias = repository.GetByQuery(query); - - return medias; - } - } - - /// - /// Gets a collection of an objects, which resides in the Recycle Bin - /// - /// An Enumerable list of objects - public IEnumerable GetMediaInRecycleBin() - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - var query = Query.Builder.Where(x => x.Path.Contains("-21")); - var medias = repository.GetByQuery(query); - - return medias; - } - } - - /// - /// Gets an object from the path stored in the 'umbracoFile' property. - /// - /// Path of the media item to retrieve (for example: /media/1024/koala_403x328.jpg) - /// - public IMedia GetMediaByPath(string mediaPath) - { - var umbracoFileValue = mediaPath; - 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) - { - var underscoreIndex = mediaPath.LastIndexOf('_'); - var dotIndex = mediaPath.LastIndexOf('.'); - umbracoFileValue = string.Concat(mediaPath.Substring(0, underscoreIndex), mediaPath.Substring(dotIndex)); - } - - Func createSql = url => new Sql().Select("*") - .From() - .InnerJoin() - .On(left => left.PropertyTypeId, right => right.Id) - .Where(x => x.Alias == "umbracoFile") - .Where(x => x.VarChar == url); - - var sql = createSql(umbracoFileValue); - - using (var uow = UowProvider.GetUnitOfWork()) - { - var propertyDataDto = uow.Database.Fetch(sql).FirstOrDefault(); - - // If the stripped-down url returns null, we try again with the original url. - // Previously, the function would fail on e.g. "my_x_image.jpg" - if (propertyDataDto == null) - { - sql = createSql(mediaPath); - propertyDataDto = uow.Database.Fetch(sql).FirstOrDefault(); - } - - return propertyDataDto == null ? null : GetById(propertyDataDto.NodeId); - } - } - - /// - /// Checks whether an item has any children - /// - /// Id of the - /// True if the media has any children otherwise False - public bool HasChildren(int id) - { - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.ParentId == id); - int count = repository.Count(query); - return count > 0; - } - } - - /// - /// Moves an object to a new location - /// - /// The to move - /// Id of the Media's new Parent - /// Id of the User moving the Media - public void Move(IMedia media, int parentId, int userId = 0) - { - //TODO: This all needs to be on the repo layer in one transaction! - - if (media == null) throw new ArgumentNullException("media"); - - using (new WriteLock(Locker)) - { - //This ensures that the correct method is called if this method is used to Move to recycle bin. - if (parentId == -21) - { - MoveToRecycleBin(media, userId); - return; - } - - var originalPath = media.Path; - - if (Moving.IsRaisedEventCancelled( - new MoveEventArgs( - new MoveEventInfo(media, originalPath, parentId)), this)) - { - return; - } - - media.ParentId = parentId; - if (media.Trashed) - { - media.ChangeTrashedState(false, parentId); - } - Save(media, userId, - //no events! - false); - - //used to track all the moved entities to be given to the event - var moveInfo = new List> - { - new MoveEventInfo(media, originalPath, parentId) - }; - - //Ensure that relevant properties are updated on children - var children = GetChildren(media.Id).ToArray(); - if (children.Any()) - { - var parentPath = media.Path; - var parentLevel = media.Level; - var parentTrashed = media.Trashed; - var updatedDescendants = UpdatePropertiesOnChildren(children, parentPath, parentLevel, parentTrashed, moveInfo); - Save(updatedDescendants, userId, - //no events! - false); - } - - Moved.RaiseEvent(new MoveEventArgs(false, moveInfo.ToArray()), this); - - Audit(AuditType.Move, "Move Media performed by user", userId, media.Id); - } - } - - /// - /// Deletes an object by moving it to the Recycle Bin - /// - /// The to delete - /// Id of the User deleting the Media - public void MoveToRecycleBin(IMedia media, int userId = 0) - { - ((IMediaServiceOperations)this).MoveToRecycleBin(media, userId); - } - - /// - /// Permanently deletes an object - /// - /// - /// Please note that this method will completely remove the Media from the database, - /// but current not from the file system. - /// - /// The to delete - /// Id of the User deleting the Media - Attempt IMediaServiceOperations.Delete(IMedia media, int userId) - { - //TODO: IT would be much nicer to mass delete all in one trans in the repo level! - var evtMsgs = EventMessagesFactory.Get(); - - if (Deleting.IsRaisedEventCancelled( - new DeleteEventArgs(media, evtMsgs), this)) - { - return OperationStatus.Cancelled(evtMsgs); - } - - //Delete children before deleting the 'possible parent' - var children = GetChildren(media.Id); - foreach (var child in children) - { - Delete(child, userId); - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - repository.Delete(media); - uow.Commit(); - - var args = new DeleteEventArgs(media, false, evtMsgs); - Deleted.RaiseEvent(args, this); - - //remove any flagged media files - repository.DeleteMediaFiles(args.MediaFilesToDelete); - } - - Audit(AuditType.Delete, "Delete Media performed by user", userId, media.Id); - - return OperationStatus.Success(evtMsgs); - } - - /// - /// Saves a single object - /// - /// The to save - /// Id of the User saving the Media - /// Optional boolean indicating whether or not to raise events. - Attempt IMediaServiceOperations.Save(IMedia media, int userId, bool raiseEvents) - { - var evtMsgs = EventMessagesFactory.Get(); - - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled( - new SaveEventArgs(media, evtMsgs), - this)) - { - return OperationStatus.Cancelled(evtMsgs); - } - - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - media.CreatorId = userId; - repository.AddOrUpdate(media); - repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - } - - uow.Commit(); - } - - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(media, false, evtMsgs), this); - - Audit(AuditType.Save, "Save Media performed by user", userId, media.Id); - - return OperationStatus.Success(evtMsgs); - } - - /// - /// Saves a collection of objects - /// - /// Collection of to save - /// Id of the User saving the Media - /// Optional boolean indicating whether or not to raise events. - Attempt IMediaServiceOperations.Save(IEnumerable medias, int userId, bool raiseEvents) - { - var asArray = medias.ToArray(); - var evtMsgs = EventMessagesFactory.Get(); - - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled( - new SaveEventArgs(asArray, evtMsgs), - this)) - { - return OperationStatus.Cancelled(evtMsgs); - } - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - foreach (var media in asArray) - { - media.CreatorId = userId; - repository.AddOrUpdate(media); - repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - } - } - - //commit the whole lot in one go - uow.Commit(); - } - - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(asArray, false, evtMsgs), this); - - Audit(AuditType.Save, "Save Media items performed by user", userId, -1); - - return OperationStatus.Success(evtMsgs); - } - - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - public void EmptyRecycleBin() - { - using (new WriteLock(Locker)) - { - Dictionary> entities; - List files; - bool success; - var nodeObjectType = new Guid(Constants.ObjectTypes.Media); - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - //Create a dictionary of ids -> dictionary of property aliases + values - entities = repository.GetEntitiesInRecycleBin() - .ToDictionary( - key => key.Id, - val => (IEnumerable)val.Properties); - - files = ((MediaRepository)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); - } - } - Audit(AuditType.Delete, "Empty Media Recycle Bin performed by user", 0, -21); - } - - /// - /// Deletes all media of specified type. All children of deleted media 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 deleting the media - public void DeleteMediaOfType(int mediaTypeId, int userId = 0) - { - //TODO: This all needs to be done on the repo level in one trans - - using (new WriteLock(Locker)) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - //NOTE What about media that has the contenttype as part of its composition? - //The ContentType has to be removed from the composition somehow as it would otherwise break - //Dbl.check+test that the ContentType's Id is removed from the ContentType2ContentType table - var query = Query.Builder.Where(x => x.ContentTypeId == mediaTypeId); - 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 = Query.Builder.Where(x => x.Path.StartsWith(c.Path)); - var children = repository.GetByQuery(childQuery); - - foreach (var child in children) - { - if (child.ContentType.Id != mediaTypeId) - MoveToRecycleBin(child, userId); - } - - //Permanently delete the content - Delete(content, userId); - } - } - - Audit(AuditType.Delete, "Delete Media items by Type performed by user", userId, -1); - } - } - - /// - /// Deletes an object by moving it to the Recycle Bin - /// - /// The to delete - /// Id of the User deleting the Media - Attempt IMediaServiceOperations.MoveToRecycleBin(IMedia media, int userId) - { - if (media == null) throw new ArgumentNullException("media"); - - var originalPath = media.Path; - - var evtMsgs = EventMessagesFactory.Get(); - - if (Trashing.IsRaisedEventCancelled( - new MoveEventArgs(new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia)), this)) - { - return OperationStatus.Cancelled(evtMsgs); - } - - var moveInfo = new List> - { - new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia) - }; - - //Find Descendants, which will be moved to the recycle bin along with the parent/grandparent. - var descendants = GetDescendants(media).OrderBy(x => x.Level).ToList(); - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - //TODO: This should be part of the repo! - - //Remove 'published' xml from the cmsContentXml table for the unpublished media - uow.Database.Delete("WHERE nodeId = @Id", new { Id = media.Id }); - - media.ChangeTrashedState(true, Constants.System.RecycleBinMedia); - repository.AddOrUpdate(media); - - //Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId - foreach (var descendant in descendants) - { - //Remove 'published' xml from the cmsContentXml table for the unpublished media - uow.Database.Delete("WHERE nodeId = @Id", new { Id = descendant.Id }); - - descendant.ChangeTrashedState(true, descendant.ParentId); - repository.AddOrUpdate(descendant); - - moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); - } - - uow.Commit(); - } - - Trashed.RaiseEvent( - new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), this); - - Audit(AuditType.Move, "Move Media to Recycle Bin performed by user", userId, media.Id); - - return OperationStatus.Success(evtMsgs); - } - - /// - /// Permanently deletes an object as well as all of its Children. - /// - /// - /// Please note that this method will completely remove the Media from the database, - /// as well as associated media files from the file system. - /// - /// The to delete - /// Id of the User deleting the Media - public void Delete(IMedia media, int userId = 0) - { - ((IMediaServiceOperations)this).Delete(media, userId); - } - - - - /// - /// Permanently deletes versions from an object prior to a specific date. - /// This method will never delete the latest version of a content item. - /// - /// Id of the object to delete versions from - /// Latest version date - /// Optional Id of the User deleting versions of a Content object - public void DeleteVersions(int id, DateTime versionDate, int userId = 0) - { - if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, dateToRetain: versionDate), this)) - return; - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - repository.DeleteVersions(id, versionDate); - uow.Commit(); - } - - DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate), this); - - Audit(AuditType.Delete, "Delete Media by version date performed by user", userId, -1); - } - - /// - /// Permanently deletes specific version(s) from an object. - /// This method will never delete the latest version of a content item. - /// - /// Id of the object to delete a version from - /// Id of the version to delete - /// Boolean indicating whether to delete versions prior to the versionId - /// Optional Id of the User deleting versions of a Content object - public void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0) - { - if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, specificVersion: versionId), this)) - return; - - if (deletePriorVersions) - { - var content = GetByVersion(versionId); - DeleteVersions(id, content.UpdateDate, userId); - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - repository.DeleteVersion(versionId); - uow.Commit(); - } - - DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), this); - - Audit(AuditType.Delete, "Delete Media by version performed by user", userId, -1); - } - - /// - /// Saves a single object - /// - /// The to save - /// Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - public void Save(IMedia media, int userId = 0, bool raiseEvents = true) - { - ((IMediaServiceOperations)this).Save(media, userId, raiseEvents); - } - - /// - /// Saves a collection of objects - /// - /// Collection of to save - /// Id of the User saving the Content - /// Optional boolean indicating whether or not to raise events. - public void Save(IEnumerable medias, int userId = 0, bool raiseEvents = true) - { - ((IMediaServiceOperations)this).Save(medias, userId, raiseEvents); - } - - /// - /// Sorts a collection of objects by updating the SortOrder according - /// to the ordering of items in the passed in . - /// - /// - /// - /// - /// True if sorting succeeded, otherwise False - public bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true) - { - var asArray = items.ToArray(); - - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(asArray), this)) - return false; - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - int i = 0; - foreach (var media in asArray) - { - //If the current sort order equals that of the media - //we don't need to update it, so just increment the sort order - //and continue. - if (media.SortOrder == i) - { - i++; - continue; - } - - media.SortOrder = i; - i++; - - repository.AddOrUpdate(media); - repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); - } - } - - uow.Commit(); - } - - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(asArray, false), this); - - Audit(AuditType.Sort, "Sorting Media performed by user", userId, 0); - - return true; - } - - /// - /// Rebuilds all xml content in the cmsContentXml table for all media - /// - /// - /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures - /// for all media - /// - public void RebuildXmlStructures(params int[] contentTypeIds) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaRepository(uow)) - { - repository.RebuildXmlStructures( - media => _entitySerializer.Serialize(this, _dataTypeService, _userService, media), - contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds); - } - - Audit(AuditType.Publish, "MediaService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1); - } - - /// - /// Updates the Path and Level on a collection of objects - /// based on the Parent's Path and Level. Also change the trashed state if relevant. - /// - /// Collection of objects to update - /// Path of the Parent media - /// Level of the Parent media - /// Indicates whether the Parent is trashed or not - /// Used to track the objects to be used in the move event - /// Collection of updated objects - private IEnumerable UpdatePropertiesOnChildren(IEnumerable children, string parentPath, int parentLevel, bool parentTrashed, ICollection> eventInfo) - { - var list = new List(); - foreach (var child in children) - { - var originalPath = child.Path; - child.Path = string.Concat(parentPath, ",", child.Id); - child.Level = parentLevel + 1; - if (parentTrashed != child.Trashed) - { - child.ChangeTrashedState(parentTrashed, child.ParentId); - } - - eventInfo.Add(new MoveEventInfo(child, originalPath, child.ParentId)); - list.Add(child); - - var grandkids = GetChildren(child.Id).ToArray(); - if (grandkids.Any()) - { - list.AddRange(UpdatePropertiesOnChildren(grandkids, child.Path, child.Level, child.Trashed, eventInfo)); - } - } - return list; - } - - //private void CreateAndSaveMediaXml(XElement xml, int id, UmbracoDatabase db) - //{ - // var poco = new ContentXmlDto { NodeId = id, Xml = xml.ToDataString() }; - // var exists = db.FirstOrDefault("WHERE nodeId = @Id", new { Id = id }) != null; - // int result = exists ? db.Update(poco) : Convert.ToInt32(db.Insert(poco)); - //} - - private IMediaType FindMediaTypeByAlias(string mediaTypeAlias) - { - Mandate.ParameterNotNullOrEmpty(mediaTypeAlias, "mediaTypeAlias"); - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMediaTypeRepository(uow)) - { - var query = Query.Builder.Where(x => x.Alias == mediaTypeAlias); - var mediaTypes = repository.GetByQuery(query); - - if (mediaTypes.Any() == false) - throw new Exception(string.Format("No MediaType matching the passed in Alias: '{0}' was found", - mediaTypeAlias)); - - var mediaType = mediaTypes.First(); - - if (mediaType == null) - throw new Exception(string.Format("MediaType matching the passed in Alias: '{0}' was null", - mediaTypeAlias)); - - return mediaType; - } - } - - private void Audit(AuditType type, string message, int userId, int objectId) - { - var uow = UowProvider.GetUnitOfWork(); - using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) - { - auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); - uow.Commit(); - } - } - - #region Event Handlers - - /// - /// Occurs before Delete - /// - public static event TypedEventHandler DeletingVersions; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler DeletedVersions; - - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> Deleting; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> Deleted; - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> Saving; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> Saved; - - /// - /// Occurs before Create - /// - [Obsolete("Use the Created event instead, the Creating and Created events both offer the same functionality, Creating event has been deprecated.")] - public static event TypedEventHandler> Creating; - - /// - /// Occurs after Create - /// - /// - /// Please note that the Media object has been created, but not saved - /// so it does not have an identity yet (meaning no Id has been set). - /// - public static event TypedEventHandler> Created; - - /// - /// Occurs before Content is moved to Recycle Bin - /// - public static event TypedEventHandler> Trashing; - - /// - /// Occurs after Content is moved to Recycle Bin - /// - public static event TypedEventHandler> Trashed; - - /// - /// Occurs before Move - /// - public static event TypedEventHandler> Moving; - - /// - /// Occurs after Move - /// - public static event TypedEventHandler> Moved; - - /// - /// Occurs before the Recycle Bin is emptied - /// - public static event TypedEventHandler EmptyingRecycleBin; - - /// - /// Occurs after the Recycle Bin has been Emptied - /// - public static event TypedEventHandler EmptiedRecycleBin; - #endregion - } + /// + /// Represents the Media Service, which is an easy access to operations involving + /// + public class MediaService : RepositoryService, IMediaService, IMediaServiceOperations + { + + //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); + + private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); + private readonly IDataTypeService _dataTypeService; + private readonly IUserService _userService; + + public MediaService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IDataTypeService dataTypeService, IUserService userService) + : base(provider, repositoryFactory, logger, eventMessagesFactory) + { + if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); + if (userService == null) throw new ArgumentNullException("userService"); + _dataTypeService = dataTypeService; + _userService = userService; + } + + /// + /// Creates an object using the alias of the + /// that this Media should based on. + /// + /// + /// Note that using this method will simply return a new IMedia without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects + /// that does not invoke a save operation against the database. + /// + /// Name of the Media object + /// Id of Parent for the new Media item + /// Alias of the + /// Optional id of the user creating the media item + /// + public IMedia CreateMedia(string name, int parentId, string mediaTypeAlias, int userId = 0) + { + var mediaType = FindMediaTypeByAlias(mediaTypeAlias); + var media = new Models.Media(name, parentId, mediaType); + var parent = GetById(media.ParentId); + media.Path = string.Concat(parent.IfNotNull(x => x.Path, media.ParentId.ToString()), ",", media.Id); + + if (Creating.IsRaisedEventCancelled(new NewEventArgs(media, mediaTypeAlias, parentId), this)) + { + media.WasCancelled = true; + return media; + } + + media.CreatorId = userId; + + Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parentId), this); + + Audit(AuditType.New, string.Format("Media '{0}' was created", name), media.CreatorId, media.Id); + + return media; + } + + /// + /// Creates an object using the alias of the + /// that this Media should based on. + /// + /// + /// Note that using this method will simply return a new IMedia without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects + /// that does not invoke a save operation against the database. + /// + /// Name of the Media object + /// Parent for the new Media item + /// Alias of the + /// Optional id of the user creating the media item + /// + public IMedia CreateMedia(string name, IMedia parent, string mediaTypeAlias, int userId = 0) + { + if (parent == null) throw new ArgumentNullException("parent"); + + var mediaType = FindMediaTypeByAlias(mediaTypeAlias); + var media = new Models.Media(name, parent, mediaType); + media.Path = string.Concat(parent.Path, ",", media.Id); + + if (Creating.IsRaisedEventCancelled(new NewEventArgs(media, mediaTypeAlias, parent), this)) + { + media.WasCancelled = true; + return media; + } + + media.CreatorId = userId; + + Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parent), this); + + Audit(AuditType.New, string.Format("Media '{0}' was created", name), media.CreatorId, media.Id); + + return media; + } + + /// + /// Creates an object using the alias of the + /// that this Media should based on. + /// + /// + /// This method returns an object that has been persisted to the database + /// and therefor has an identity. + /// + /// Name of the Media object + /// Id of Parent for the new Media item + /// Alias of the + /// Optional id of the user creating the media item + /// + public IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = 0) + { + var mediaType = FindMediaTypeByAlias(mediaTypeAlias); + var media = new Models.Media(name, parentId, mediaType); + + //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(media, mediaTypeAlias, parentId), this)) + { + media.WasCancelled = true; + return media; + } + + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(media), this)) + { + media.WasCancelled = true; + return media; + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + media.CreatorId = userId; + repository.AddOrUpdate(media); + + repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + { + repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + } + + uow.Commit(); + } + + Saved.RaiseEvent(new SaveEventArgs(media, false), this); + + Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parentId), this); + + Audit(AuditType.New, string.Format("Media '{0}' was created with Id {1}", name, media.Id), media.CreatorId, media.Id); + + return media; + } + + /// + /// Creates an object using the alias of the + /// that this Media should based on. + /// + /// + /// This method returns an object that has been persisted to the database + /// and therefor has an identity. + /// + /// Name of the Media object + /// Parent for the new Media item + /// Alias of the + /// Optional id of the user creating the media item + /// + public IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = 0) + { + if (parent == null) throw new ArgumentNullException("parent"); + + var mediaType = FindMediaTypeByAlias(mediaTypeAlias); + var media = new Models.Media(name, parent, mediaType); + + //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(media, mediaTypeAlias, parent), this)) + { + media.WasCancelled = true; + return media; + } + + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(media), this)) + { + media.WasCancelled = true; + return media; + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + media.CreatorId = userId; + repository.AddOrUpdate(media); + repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + { + repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + } + + uow.Commit(); + } + + Saved.RaiseEvent(new SaveEventArgs(media, false), this); + + Created.RaiseEvent(new NewEventArgs(media, false, mediaTypeAlias, parent), this); + + Audit(AuditType.New, string.Format("Media '{0}' was created with Id {1}", name, media.Id), media.CreatorId, media.Id); + + return media; + } + + /// + /// Gets an object by Id + /// + /// Id of the Content to retrieve + /// + public IMedia GetById(int id) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + return repository.Get(id); + } + } + + public int Count(string contentTypeAlias = null) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + return repository.Count(contentTypeAlias); + } + } + + public int CountChildren(int parentId, string contentTypeAlias = null) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + return repository.CountChildren(parentId, contentTypeAlias); + } + } + + public int CountDescendants(int parentId, string contentTypeAlias = null) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + return repository.CountDescendants(parentId, contentTypeAlias); + } + } + + /// + /// Gets an object by Id + /// + /// Ids of the Media to retrieve + /// + public IEnumerable GetByIds(IEnumerable ids) + { + if (ids.Any() == false) return Enumerable.Empty(); + + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetAll(ids.ToArray()); + } + } + + /// + /// Gets an object by its 'UniqueId' + /// + /// Guid key of the Media to retrieve + /// + public IMedia GetById(Guid key) + { + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Key == key); + var contents = repository.GetByQuery(query); + return contents.SingleOrDefault(); + } + } + + /// + /// Gets a collection of objects by Level + /// + /// The level to retrieve Media from + /// An Enumerable list of objects + public IEnumerable GetByLevel(int level) + { + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Level == level && !x.Path.StartsWith("-21")); + var contents = repository.GetByQuery(query); + + return contents; + } + } + + /// + /// Gets a specific version of an item. + /// + /// Id of the version to retrieve + /// An item + public IMedia GetByVersion(Guid versionId) + { + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetByVersion(versionId); + } + } + + /// + /// Gets a collection of an objects versions by Id + /// + /// + /// An Enumerable list of objects + public IEnumerable GetVersions(int id) + { + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + var versions = repository.GetAllVersions(id); + return versions; + } + } + + /// + /// Gets a collection of objects, which are ancestors of the current media. + /// + /// Id of the to retrieve ancestors for + /// An Enumerable list of objects + public IEnumerable GetAncestors(int id) + { + var media = GetById(id); + return GetAncestors(media); + } + + /// + /// Gets a collection of objects, which are ancestors of the current media. + /// + /// to retrieve ancestors for + /// An Enumerable list of objects + public IEnumerable GetAncestors(IMedia media) + { + var ids = media.Path.Split(',').Where(x => x != "-1" && x != media.Id.ToString(CultureInfo.InvariantCulture)).Select(int.Parse).ToArray(); + if (ids.Any() == false) + return new List(); + + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetAll(ids); + } + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// An Enumerable list of objects + public IEnumerable GetChildren(int id) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + var query = Query.Builder.Where(x => x.ParentId == id); + var medias = repository.GetByQuery(query); + + return medias; + } + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, + string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder; + query.Where(x => x.ParentId == id); + + long total; + var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, orderBySystemField, filter); + + totalChildren = Convert.ToInt32(total); + return medias; + } + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page index (zero based) + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, + string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder; + query.Where(x => x.ParentId == id); + + var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + + return medias; + } + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + + var query = Query.Builder; + //if the id is -1, then just get all + if (id != -1) + { + query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); + } + long total; + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, orderBySystemField, filter); + totalChildren = Convert.ToInt32(total); + return contents; + } + } + + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + + var query = Query.Builder; + //if the id is -1, then just get all + if (id != -1) + { + query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); + } + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + + return contents; + } + } + + /// + /// Gets descendants of a object by its Id + /// + /// Id of the Parent to retrieve descendants from + /// An Enumerable flat list of objects + public IEnumerable GetDescendants(int id) + { + var media = GetById(id); + if (media == null) + { + return Enumerable.Empty(); + } + return GetDescendants(media); + } + + /// + /// Gets descendants of a object by its Id + /// + /// The Parent object to retrieve descendants from + /// An Enumerable flat list of objects + public IEnumerable GetDescendants(IMedia media) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + var pathMatch = media.Path + ","; + var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != media.Id); + var medias = repository.GetByQuery(query); + + return medias; + } + } + + /// + /// Gets the parent of the current media as an item. + /// + /// Id of the to retrieve the parent from + /// Parent object + public IMedia GetParent(int id) + { + var media = GetById(id); + return GetParent(media); + } + + /// + /// Gets the parent of the current media as an item. + /// + /// to retrieve the parent from + /// Parent object + public IMedia GetParent(IMedia media) + { + if (media.ParentId == -1 || media.ParentId == -21) + return null; + + return GetById(media.ParentId); + } + + /// + /// Gets a collection of objects by the Id of the + /// + /// Id of the + /// An Enumerable list of objects + public IEnumerable GetMediaOfMediaType(int id) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + var query = Query.Builder.Where(x => x.ContentTypeId == id); + var medias = repository.GetByQuery(query); + + return medias; + } + } + + /// + /// Gets a collection of objects, which reside at the first level / root + /// + /// An Enumerable list of objects + public IEnumerable GetRootMedia() + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + var query = Query.Builder.Where(x => x.ParentId == -1); + var medias = repository.GetByQuery(query); + + return medias; + } + } + + /// + /// Gets a collection of an objects, which resides in the Recycle Bin + /// + /// An Enumerable list of objects + public IEnumerable GetMediaInRecycleBin() + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + var query = Query.Builder.Where(x => x.Path.Contains("-21")); + var medias = repository.GetByQuery(query); + + return medias; + } + } + + /// + /// Gets an object from the path stored in the 'umbracoFile' property. + /// + /// Path of the media item to retrieve (for example: /media/1024/koala_403x328.jpg) + /// + public IMedia GetMediaByPath(string mediaPath) + { + var umbracoFileValue = mediaPath; + 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) + { + var underscoreIndex = mediaPath.LastIndexOf('_'); + var dotIndex = mediaPath.LastIndexOf('.'); + umbracoFileValue = string.Concat(mediaPath.Substring(0, underscoreIndex), mediaPath.Substring(dotIndex)); + } + + Func createSql = url => new Sql().Select("*") + .From() + .InnerJoin() + .On(left => left.PropertyTypeId, right => right.Id) + .Where(x => x.Alias == "umbracoFile") + .Where(x => x.VarChar == url); + + var sql = createSql(umbracoFileValue); + + using (var uow = UowProvider.GetUnitOfWork()) + { + var propertyDataDto = uow.Database.Fetch(sql).FirstOrDefault(); + + // If the stripped-down url returns null, we try again with the original url. + // Previously, the function would fail on e.g. "my_x_image.jpg" + if (propertyDataDto == null) + { + sql = createSql(mediaPath); + propertyDataDto = uow.Database.Fetch(sql).FirstOrDefault(); + } + + return propertyDataDto == null ? null : GetById(propertyDataDto.NodeId); + } + } + + /// + /// Checks whether an item has any children + /// + /// Id of the + /// True if the media has any children otherwise False + public bool HasChildren(int id) + { + using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ParentId == id); + int count = repository.Count(query); + return count > 0; + } + } + + /// + /// Moves an object to a new location + /// + /// The to move + /// Id of the Media's new Parent + /// Id of the User moving the Media + public void Move(IMedia media, int parentId, int userId = 0) + { + //TODO: This all needs to be on the repo layer in one transaction! + + if (media == null) throw new ArgumentNullException("media"); + + using (new WriteLock(Locker)) + { + //This ensures that the correct method is called if this method is used to Move to recycle bin. + if (parentId == -21) + { + MoveToRecycleBin(media, userId); + return; + } + + var originalPath = media.Path; + + if (Moving.IsRaisedEventCancelled( + new MoveEventArgs( + new MoveEventInfo(media, originalPath, parentId)), this)) + { + return; + } + + media.ParentId = parentId; + if (media.Trashed) + { + media.ChangeTrashedState(false, parentId); + } + Save(media, userId, + //no events! + false); + + //used to track all the moved entities to be given to the event + var moveInfo = new List> + { + new MoveEventInfo(media, originalPath, parentId) + }; + + //Ensure that relevant properties are updated on children + var children = GetChildren(media.Id).ToArray(); + if (children.Any()) + { + var parentPath = media.Path; + var parentLevel = media.Level; + var parentTrashed = media.Trashed; + var updatedDescendants = UpdatePropertiesOnChildren(children, parentPath, parentLevel, parentTrashed, moveInfo); + Save(updatedDescendants, userId, + //no events! + false); + } + + Moved.RaiseEvent(new MoveEventArgs(false, moveInfo.ToArray()), this); + + Audit(AuditType.Move, "Move Media performed by user", userId, media.Id); + } + } + + /// + /// Deletes an object by moving it to the Recycle Bin + /// + /// The to delete + /// Id of the User deleting the Media + public void MoveToRecycleBin(IMedia media, int userId = 0) + { + ((IMediaServiceOperations)this).MoveToRecycleBin(media, userId); + } + + /// + /// Permanently deletes an object + /// + /// + /// Please note that this method will completely remove the Media from the database, + /// but current not from the file system. + /// + /// The to delete + /// Id of the User deleting the Media + Attempt IMediaServiceOperations.Delete(IMedia media, int userId) + { + //TODO: IT would be much nicer to mass delete all in one trans in the repo level! + var evtMsgs = EventMessagesFactory.Get(); + + if (Deleting.IsRaisedEventCancelled( + new DeleteEventArgs(media, evtMsgs), this)) + { + return OperationStatus.Cancelled(evtMsgs); + } + + //Delete children before deleting the 'possible parent' + var children = GetChildren(media.Id); + foreach (var child in children) + { + Delete(child, userId); + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + repository.Delete(media); + uow.Commit(); + + var args = new DeleteEventArgs(media, false, evtMsgs); + Deleted.RaiseEvent(args, this); + + //remove any flagged media files + repository.DeleteMediaFiles(args.MediaFilesToDelete); + } + + Audit(AuditType.Delete, "Delete Media performed by user", userId, media.Id); + + return OperationStatus.Success(evtMsgs); + } + + /// + /// Saves a single object + /// + /// The to save + /// Id of the User saving the Media + /// Optional boolean indicating whether or not to raise events. + Attempt IMediaServiceOperations.Save(IMedia media, int userId, bool raiseEvents) + { + var evtMsgs = EventMessagesFactory.Get(); + + if (raiseEvents) + { + if (Saving.IsRaisedEventCancelled( + new SaveEventArgs(media, evtMsgs), + this)) + { + return OperationStatus.Cancelled(evtMsgs); + } + + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + media.CreatorId = userId; + repository.AddOrUpdate(media); + repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + { + repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + } + + uow.Commit(); + } + + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(media, false, evtMsgs), this); + + Audit(AuditType.Save, "Save Media performed by user", userId, media.Id); + + return OperationStatus.Success(evtMsgs); + } + + /// + /// Saves a collection of objects + /// + /// Collection of to save + /// Id of the User saving the Media + /// Optional boolean indicating whether or not to raise events. + Attempt IMediaServiceOperations.Save(IEnumerable medias, int userId, bool raiseEvents) + { + var asArray = medias.ToArray(); + var evtMsgs = EventMessagesFactory.Get(); + + if (raiseEvents) + { + if (Saving.IsRaisedEventCancelled( + new SaveEventArgs(asArray, evtMsgs), + this)) + { + return OperationStatus.Cancelled(evtMsgs); + } + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + foreach (var media in asArray) + { + media.CreatorId = userId; + repository.AddOrUpdate(media); + repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + { + repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + } + } + + //commit the whole lot in one go + uow.Commit(); + } + + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(asArray, false, evtMsgs), this); + + Audit(AuditType.Save, "Save Media items performed by user", userId, -1); + + return OperationStatus.Success(evtMsgs); + } + + /// + /// Empties the Recycle Bin by deleting all that resides in the bin + /// + public void EmptyRecycleBin() + { + using (new WriteLock(Locker)) + { + Dictionary> entities; + List files; + bool success; + var nodeObjectType = new Guid(Constants.ObjectTypes.Media); + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + //Create a dictionary of ids -> dictionary of property aliases + values + entities = repository.GetEntitiesInRecycleBin() + .ToDictionary( + key => key.Id, + val => (IEnumerable)val.Properties); + + files = ((MediaRepository)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); + } + } + Audit(AuditType.Delete, "Empty Media Recycle Bin performed by user", 0, -21); + } + + /// + /// Deletes all media of specified type. All children of deleted media 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 deleting the media + public void DeleteMediaOfType(int mediaTypeId, int userId = 0) + { + //TODO: This all needs to be done on the repo level in one trans + + using (new WriteLock(Locker)) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + //NOTE What about media that has the contenttype as part of its composition? + //The ContentType has to be removed from the composition somehow as it would otherwise break + //Dbl.check+test that the ContentType's Id is removed from the ContentType2ContentType table + var query = Query.Builder.Where(x => x.ContentTypeId == mediaTypeId); + 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 = Query.Builder.Where(x => x.Path.StartsWith(c.Path)); + var children = repository.GetByQuery(childQuery); + + foreach (var child in children) + { + if (child.ContentType.Id != mediaTypeId) + MoveToRecycleBin(child, userId); + } + + //Permanently delete the content + Delete(content, userId); + } + } + + Audit(AuditType.Delete, "Delete Media items by Type performed by user", userId, -1); + } + } + + /// + /// Deletes an object by moving it to the Recycle Bin + /// + /// The to delete + /// Id of the User deleting the Media + Attempt IMediaServiceOperations.MoveToRecycleBin(IMedia media, int userId) + { + if (media == null) throw new ArgumentNullException("media"); + + var originalPath = media.Path; + + var evtMsgs = EventMessagesFactory.Get(); + + if (Trashing.IsRaisedEventCancelled( + new MoveEventArgs(new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia)), this)) + { + return OperationStatus.Cancelled(evtMsgs); + } + + var moveInfo = new List> + { + new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia) + }; + + //Find Descendants, which will be moved to the recycle bin along with the parent/grandparent. + var descendants = GetDescendants(media).OrderBy(x => x.Level).ToList(); + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + //TODO: This should be part of the repo! + + //Remove 'published' xml from the cmsContentXml table for the unpublished media + uow.Database.Delete("WHERE nodeId = @Id", new { Id = media.Id }); + + media.ChangeTrashedState(true, Constants.System.RecycleBinMedia); + repository.AddOrUpdate(media); + + //Loop through descendants to update their trash state, but ensuring structure by keeping the ParentId + foreach (var descendant in descendants) + { + //Remove 'published' xml from the cmsContentXml table for the unpublished media + uow.Database.Delete("WHERE nodeId = @Id", new { Id = descendant.Id }); + + descendant.ChangeTrashedState(true, descendant.ParentId); + repository.AddOrUpdate(descendant); + + moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); + } + + uow.Commit(); + } + + Trashed.RaiseEvent( + new MoveEventArgs(false, evtMsgs, moveInfo.ToArray()), this); + + Audit(AuditType.Move, "Move Media to Recycle Bin performed by user", userId, media.Id); + + return OperationStatus.Success(evtMsgs); + } + + /// + /// Permanently deletes an object as well as all of its Children. + /// + /// + /// Please note that this method will completely remove the Media from the database, + /// as well as associated media files from the file system. + /// + /// The to delete + /// Id of the User deleting the Media + public void Delete(IMedia media, int userId = 0) + { + ((IMediaServiceOperations)this).Delete(media, userId); + } + + + + /// + /// Permanently deletes versions from an object prior to a specific date. + /// This method will never delete the latest version of a content item. + /// + /// Id of the object to delete versions from + /// Latest version date + /// Optional Id of the User deleting versions of a Content object + public void DeleteVersions(int id, DateTime versionDate, int userId = 0) + { + if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, dateToRetain: versionDate), this)) + return; + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + repository.DeleteVersions(id, versionDate); + uow.Commit(); + } + + DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate), this); + + Audit(AuditType.Delete, "Delete Media by version date performed by user", userId, -1); + } + + /// + /// Permanently deletes specific version(s) from an object. + /// This method will never delete the latest version of a content item. + /// + /// Id of the object to delete a version from + /// Id of the version to delete + /// Boolean indicating whether to delete versions prior to the versionId + /// Optional Id of the User deleting versions of a Content object + public void DeleteVersion(int id, Guid versionId, bool deletePriorVersions, int userId = 0) + { + if (DeletingVersions.IsRaisedEventCancelled(new DeleteRevisionsEventArgs(id, specificVersion: versionId), this)) + return; + + if (deletePriorVersions) + { + var content = GetByVersion(versionId); + DeleteVersions(id, content.UpdateDate, userId); + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + repository.DeleteVersion(versionId); + uow.Commit(); + } + + DeletedVersions.RaiseEvent(new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), this); + + Audit(AuditType.Delete, "Delete Media by version performed by user", userId, -1); + } + + /// + /// Saves a single object + /// + /// The to save + /// Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + public void Save(IMedia media, int userId = 0, bool raiseEvents = true) + { + ((IMediaServiceOperations)this).Save(media, userId, raiseEvents); + } + + /// + /// Saves a collection of objects + /// + /// Collection of to save + /// Id of the User saving the Content + /// Optional boolean indicating whether or not to raise events. + public void Save(IEnumerable medias, int userId = 0, bool raiseEvents = true) + { + ((IMediaServiceOperations)this).Save(medias, userId, raiseEvents); + } + + /// + /// Sorts a collection of objects by updating the SortOrder according + /// to the ordering of items in the passed in . + /// + /// + /// + /// + /// True if sorting succeeded, otherwise False + public bool Sort(IEnumerable items, int userId = 0, bool raiseEvents = true) + { + var asArray = items.ToArray(); + + if (raiseEvents) + { + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(asArray), this)) + return false; + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + int i = 0; + foreach (var media in asArray) + { + //If the current sort order equals that of the media + //we don't need to update it, so just increment the sort order + //and continue. + if (media.SortOrder == i) + { + i++; + continue; + } + + media.SortOrder = i; + i++; + + repository.AddOrUpdate(media); + repository.AddOrUpdateContentXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + { + repository.AddOrUpdatePreviewXml(media, m => _entitySerializer.Serialize(this, _dataTypeService, _userService, m)); + } + } + + uow.Commit(); + } + + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(asArray, false), this); + + Audit(AuditType.Sort, "Sorting Media performed by user", userId, 0); + + return true; + } + + /// + /// Rebuilds all xml content in the cmsContentXml table for all media + /// + /// + /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures + /// for all media + /// + public void RebuildXmlStructures(params int[] contentTypeIds) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaRepository(uow)) + { + repository.RebuildXmlStructures( + media => _entitySerializer.Serialize(this, _dataTypeService, _userService, media), + contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds); + } + + Audit(AuditType.Publish, "MediaService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1); + } + + /// + /// Updates the Path and Level on a collection of objects + /// based on the Parent's Path and Level. Also change the trashed state if relevant. + /// + /// Collection of objects to update + /// Path of the Parent media + /// Level of the Parent media + /// Indicates whether the Parent is trashed or not + /// Used to track the objects to be used in the move event + /// Collection of updated objects + private IEnumerable UpdatePropertiesOnChildren(IEnumerable children, string parentPath, int parentLevel, bool parentTrashed, ICollection> eventInfo) + { + var list = new List(); + foreach (var child in children) + { + var originalPath = child.Path; + child.Path = string.Concat(parentPath, ",", child.Id); + child.Level = parentLevel + 1; + if (parentTrashed != child.Trashed) + { + child.ChangeTrashedState(parentTrashed, child.ParentId); + } + + eventInfo.Add(new MoveEventInfo(child, originalPath, child.ParentId)); + list.Add(child); + + var grandkids = GetChildren(child.Id).ToArray(); + if (grandkids.Any()) + { + list.AddRange(UpdatePropertiesOnChildren(grandkids, child.Path, child.Level, child.Trashed, eventInfo)); + } + } + return list; + } + + //private void CreateAndSaveMediaXml(XElement xml, int id, UmbracoDatabase db) + //{ + // var poco = new ContentXmlDto { NodeId = id, Xml = xml.ToDataString() }; + // var exists = db.FirstOrDefault("WHERE nodeId = @Id", new { Id = id }) != null; + // int result = exists ? db.Update(poco) : Convert.ToInt32(db.Insert(poco)); + //} + + private IMediaType FindMediaTypeByAlias(string mediaTypeAlias) + { + Mandate.ParameterNotNullOrEmpty(mediaTypeAlias, "mediaTypeAlias"); + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMediaTypeRepository(uow)) + { + var query = Query.Builder.Where(x => x.Alias == mediaTypeAlias); + var mediaTypes = repository.GetByQuery(query); + + if (mediaTypes.Any() == false) + throw new Exception(string.Format("No MediaType matching the passed in Alias: '{0}' was found", + mediaTypeAlias)); + + var mediaType = mediaTypes.First(); + + if (mediaType == null) + throw new Exception(string.Format("MediaType matching the passed in Alias: '{0}' was null", + mediaTypeAlias)); + + return mediaType; + } + } + + private void Audit(AuditType type, string message, int userId, int objectId) + { + var uow = UowProvider.GetUnitOfWork(); + using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) + { + auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); + uow.Commit(); + } + } + + #region Event Handlers + + /// + /// Occurs before Delete + /// + public static event TypedEventHandler DeletingVersions; + + /// + /// Occurs after Delete + /// + public static event TypedEventHandler DeletedVersions; + + /// + /// Occurs before Delete + /// + public static event TypedEventHandler> Deleting; + + /// + /// Occurs after Delete + /// + public static event TypedEventHandler> Deleted; + + /// + /// Occurs before Save + /// + public static event TypedEventHandler> Saving; + + /// + /// Occurs after Save + /// + public static event TypedEventHandler> Saved; + + /// + /// Occurs before Create + /// + [Obsolete("Use the Created event instead, the Creating and Created events both offer the same functionality, Creating event has been deprecated.")] + public static event TypedEventHandler> Creating; + + /// + /// Occurs after Create + /// + /// + /// Please note that the Media object has been created, but not saved + /// so it does not have an identity yet (meaning no Id has been set). + /// + public static event TypedEventHandler> Created; + + /// + /// Occurs before Content is moved to Recycle Bin + /// + public static event TypedEventHandler> Trashing; + + /// + /// Occurs after Content is moved to Recycle Bin + /// + public static event TypedEventHandler> Trashed; + + /// + /// Occurs before Move + /// + public static event TypedEventHandler> Moving; + + /// + /// Occurs after Move + /// + public static event TypedEventHandler> Moved; + + /// + /// Occurs before the Recycle Bin is emptied + /// + public static event TypedEventHandler EmptyingRecycleBin; + + /// + /// Occurs after the Recycle Bin has been Emptied + /// + public static event TypedEventHandler EmptiedRecycleBin; + #endregion + } } diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 6216bdfbe9..9991583719 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -21,1296 +21,1296 @@ using Umbraco.Core.Security; namespace Umbraco.Core.Services { - /// - /// Represents the MemberService. - /// - public class MemberService : RepositoryService, IMemberService - { - private readonly IMemberGroupService _memberGroupService; - private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); - private readonly IDataTypeService _dataTypeService; - private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); - - public MemberService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService, IDataTypeService dataTypeService) - : base(provider, repositoryFactory, logger, eventMessagesFactory) - { - if (memberGroupService == null) throw new ArgumentNullException("memberGroupService"); - if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); - _memberGroupService = memberGroupService; - _dataTypeService = dataTypeService; - } - - #region IMemberService Implementation - - /// - /// Gets the default MemberType alias - /// - /// By default we'll return the 'writer', but we need to check it exists. If it doesn't we'll - /// return the first type that is not an admin, otherwise if there's only one we will return that one. - /// Alias of the default MemberType - public string GetDefaultMemberType() - { - using (var repository = RepositoryFactory.CreateMemberTypeRepository(UowProvider.GetUnitOfWork())) - { - var types = repository.GetAll(new int[] { }).Select(x => x.Alias).ToArray(); - - if (types.Any() == false) - { - throw new InvalidOperationException("No member types could be resolved"); - } - - if (types.InvariantContains("Member")) - { - return types.First(x => x.InvariantEquals("Member")); - } - - return types.First(); - } - } - - /// - /// Checks if a Member with the username exists - /// - /// Username to check - /// True if the Member exists otherwise False - public bool Exists(string username) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - return repository.Exists(username); - } - } - - /// - /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method - /// - /// This method exists so that Umbraco developers can use one entry point to create/update - /// Members if they choose to. - /// The Member to save the password for - /// The password to encrypt and save - public void SavePassword(IMember member, string password) - { - if (member == null) throw new ArgumentNullException("member"); - - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - if (provider.IsUmbracoMembershipProvider()) - { - provider.ChangePassword(member.Username, "", password); - } - else - { - throw new NotSupportedException("When using a non-Umbraco membership provider you must change the member password by using the MembershipProvider.ChangePassword method"); - } - - //go re-fetch the member and update the properties that may have changed - var result = GetByUsername(member.Username); - - //should never be null but it could have been deleted by another thread. - if (result == null) - return; - - member.RawPasswordValue = result.RawPasswordValue; - member.LastPasswordChangeDate = result.LastPasswordChangeDate; - member.UpdateDate = result.UpdateDate; - } - - /// - /// Checks if a Member with the id exists - /// - /// Id of the Member - /// True if the Member exists otherwise False - public bool Exists(int id) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - return repository.Exists(id); - } - } - - /// - /// Gets a Member by its integer id - /// - /// Id - /// - public IMember GetById(int id) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - return repository.Get(id); - } - } - - /// - /// Gets a Member by the unique key - /// - /// The guid key corresponds to the unique id in the database - /// and the user id in the membership provider. - /// Id - /// - public IMember GetByKey(Guid id) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Key == id); - var member = repository.GetByQuery(query).FirstOrDefault(); - return member; - } - } - - /// - /// Gets all Members for the specified MemberType alias - /// - /// Alias of the MemberType - /// - public IEnumerable GetMembersByMemberType(string memberTypeAlias) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.ContentTypeAlias == memberTypeAlias); - var members = repository.GetByQuery(query); - return members; - } - } - - /// - /// Gets all Members for the MemberType id - /// - /// Id of the MemberType - /// - public IEnumerable GetMembersByMemberType(int memberTypeId) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - repository.Get(memberTypeId); - var query = Query.Builder.Where(x => x.ContentTypeId == memberTypeId); - var members = repository.GetByQuery(query); - return members; - } - } - - /// - /// Gets all Members within the specified MemberGroup name - /// - /// Name of the MemberGroup - /// - public IEnumerable GetMembersByGroup(string memberGroupName) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - return repository.GetByMemberGroup(memberGroupName); - } - } - - /// - /// Gets all Members with the ids specified - /// - /// If no Ids are specified all Members will be retrieved - /// Optional list of Member Ids - /// - public IEnumerable GetAllMembers(params int[] ids) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - return repository.GetAll(ids); - } - } - - /// - /// Delete Members of the specified MemberType id - /// - /// Id of the MemberType - public void DeleteMembersOfType(int memberTypeId) - { - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateMemberRepository(uow); - //TODO: What about content that has the contenttype as part of its composition? - var query = Query.Builder.Where(x => x.ContentTypeId == memberTypeId); - var members = repository.GetByQuery(query).ToArray(); - - if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(members), this)) - return; - - foreach (var member in members) - { - //Permantly delete the member - Delete(member); - } - } - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable FindMembersByDisplayName(string displayNameToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - long total; - var result = FindMembersByDisplayName(displayNameToMatch, Convert.ToInt64(pageIndex), pageSize, out total, matchType); - totalRecords = Convert.ToInt32(total); - return result; - } - - /// - /// Finds Members based on their display name - /// - /// Display name to match - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// The type of match to make as . Default is - /// - public IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - var query = new Query(); - - switch (matchType) - { - case StringPropertyMatchType.Exact: - query.Where(member => member.Name.Equals(displayNameToMatch)); - break; - case StringPropertyMatchType.Contains: - query.Where(member => member.Name.Contains(displayNameToMatch)); - break; - case StringPropertyMatchType.StartsWith: - query.Where(member => member.Name.StartsWith(displayNameToMatch)); - break; - case StringPropertyMatchType.EndsWith: - query.Where(member => member.Name.EndsWith(displayNameToMatch)); - break; - case StringPropertyMatchType.Wildcard: - query.Where(member => member.Name.SqlWildcard(displayNameToMatch, TextColumnType.NVarchar)); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); - } - - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable FindByEmail(string emailStringToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - long total; - var result = FindByEmail(emailStringToMatch, Convert.ToInt64(pageIndex), pageSize, out total, matchType); - totalRecords = Convert.ToInt32(total); - return result; - } - - /// - /// Finds a list of objects by a partial email string - /// - /// Partial email string to match - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// The type of match to make as . Default is - /// - public IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - var query = new Query(); - - switch (matchType) - { - case StringPropertyMatchType.Exact: - query.Where(member => member.Email.Equals(emailStringToMatch)); - break; - case StringPropertyMatchType.Contains: - query.Where(member => member.Email.Contains(emailStringToMatch)); - break; - case StringPropertyMatchType.StartsWith: - query.Where(member => member.Email.StartsWith(emailStringToMatch)); - break; - case StringPropertyMatchType.EndsWith: - query.Where(member => member.Email.EndsWith(emailStringToMatch)); - break; - case StringPropertyMatchType.Wildcard: - query.Where(member => member.Email.SqlWildcard(emailStringToMatch, TextColumnType.NVarchar)); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); - } - - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Email", Direction.Ascending, orderBySystemField: true); - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable FindByUsername(string login, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - long total; - var result = FindByUsername(login, Convert.ToInt64(pageIndex), pageSize, out total, matchType); - totalRecords = Convert.ToInt32(total); - return result; - } - - /// - /// Finds a list of objects by a partial username - /// - /// Partial username to match - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// The type of match to make as . Default is - /// - public IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - var query = new Query(); - - switch (matchType) - { - case StringPropertyMatchType.Exact: - query.Where(member => member.Username.Equals(login)); - break; - case StringPropertyMatchType.Contains: - query.Where(member => member.Username.Contains(login)); - break; - case StringPropertyMatchType.StartsWith: - query.Where(member => member.Username.StartsWith(login)); - break; - case StringPropertyMatchType.EndsWith: - query.Where(member => member.Username.EndsWith(login)); - break; - case StringPropertyMatchType.Wildcard: - query.Where(member => member.Email.SqlWildcard(login, TextColumnType.NVarchar)); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); - } - - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, orderBySystemField: true); - } - } - - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// The type of match to make as . Default is - /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - IQuery query; - - switch (matchType) - { - case StringPropertyMatchType.Exact: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - (((Member)x).LongStringPropertyValue.SqlEquals(value, TextColumnType.NText) || - ((Member)x).ShortStringPropertyValue.SqlEquals(value, TextColumnType.NVarchar))); - break; - case StringPropertyMatchType.Contains: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - (((Member)x).LongStringPropertyValue.SqlContains(value, TextColumnType.NText) || - ((Member)x).ShortStringPropertyValue.SqlContains(value, TextColumnType.NVarchar))); - break; - case StringPropertyMatchType.StartsWith: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - (((Member)x).LongStringPropertyValue.SqlStartsWith(value, TextColumnType.NText) || - ((Member)x).ShortStringPropertyValue.SqlStartsWith(value, TextColumnType.NVarchar))); - break; - case StringPropertyMatchType.EndsWith: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - (((Member)x).LongStringPropertyValue.SqlEndsWith(value, TextColumnType.NText) || - ((Member)x).ShortStringPropertyValue.SqlEndsWith(value, TextColumnType.NVarchar))); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); - } - - var members = repository.GetByQuery(query); - return members; - } - } - - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// The type of match to make as . Default is - /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - IQuery query; - - switch (matchType) - { - case ValuePropertyMatchType.Exact: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerPropertyValue == value); - break; - case ValuePropertyMatchType.GreaterThan: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerPropertyValue > value); - break; - case ValuePropertyMatchType.LessThan: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerPropertyValue < value); - break; - case ValuePropertyMatchType.GreaterThanOrEqualTo: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerPropertyValue >= value); - break; - case ValuePropertyMatchType.LessThanOrEqualTo: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerPropertyValue <= value); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); - } - - var members = repository.GetByQuery(query); - return members; - } - } - - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, bool value) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - var query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).BoolPropertyValue == value); - - var members = repository.GetByQuery(query); - return members; - } - } - - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// The type of match to make as . Default is - /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - IQuery query; - - switch (matchType) - { - case ValuePropertyMatchType.Exact: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue == value); - break; - case ValuePropertyMatchType.GreaterThan: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue > value); - break; - case ValuePropertyMatchType.LessThan: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue < value); - break; - case ValuePropertyMatchType.GreaterThanOrEqualTo: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue >= value); - break; - case ValuePropertyMatchType.LessThanOrEqualTo: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue <= value); - break; - default: - throw new ArgumentOutOfRangeException("matchType"); - } - - //TODO: Since this is by property value, we need a GetByPropertyQuery on the repo! - var members = repository.GetByQuery(query); - return members; - } - } - - /// - /// Rebuilds all xml content in the cmsContentXml table for all members - /// - /// - /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures - /// for all members = USE WITH CARE! - /// - /// True if publishing succeeded, otherwise False - public void RebuildXmlStructures(params int[] memberTypeIds) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - repository.RebuildXmlStructures( - member => _entitySerializer.Serialize(_dataTypeService, member), - contentTypeIds: memberTypeIds.Length == 0 ? null : memberTypeIds); - } - - Audit(AuditType.Publish, "MemberService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1); - } - - #endregion - - #region IMembershipMemberService Implementation - - /// - /// Gets the total number of Members based on the count type - /// - /// - /// The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members - /// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science - /// but that is how MS have made theirs so we'll follow that principal. - /// - /// to count by - /// with number of Members for passed in type - public int GetCount(MemberCountType countType) - { - using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) - { - IQuery query; - - switch (countType) - { - case MemberCountType.All: - query = new Query(); - return repository.Count(query); - case MemberCountType.Online: - var fromDate = DateTime.Now.AddMinutes(-Membership.UserIsOnlineTimeWindow); - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.LastLoginDate && - ((Member)x).DateTimePropertyValue > fromDate); - return repository.GetCountByQuery(query); - case MemberCountType.LockedOut: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.IsLockedOut && - ((Member)x).BoolPropertyValue == true); - return repository.GetCountByQuery(query); - case MemberCountType.Approved: - query = - Query.Builder.Where( - x => - ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.IsApproved && - ((Member)x).BoolPropertyValue == true); - return repository.GetCountByQuery(query); - default: - throw new ArgumentOutOfRangeException("countType"); - } - } - - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords) - { - long total; - var result = GetAll(Convert.ToInt64(pageIndex), pageSize, out total); - totalRecords = Convert.ToInt32(total); - return result; - } - - /// - /// Gets a list of paged objects - /// - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// - public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, orderBySystemField: true); - } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = "") - { - long total; - var result = GetAll(Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, orderBySystemField, memberTypeAlias, filter); - totalRecords = Convert.ToInt32(total); - return result; - } - - public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = "") - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - if (memberTypeAlias == null) - { - return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, filter); - } - var query = new Query().Where(x => x.ContentTypeAlias == memberTypeAlias); - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, filter); - } - } - - /// - /// Gets the count of Members by an optional MemberType alias - /// - /// If no alias is supplied then the count for all Member will be returned - /// Optional alias for the MemberType when counting number of Members - /// with number of Members - public int Count(string memberTypeAlias = null) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - return repository.Count(memberTypeAlias); - } - } - - /// - /// Creates an object without persisting it - /// - /// This method is convenient for when you need to add properties to a new Member - /// before persisting it in order to limit the amount of times its saved. - /// Also note that the returned will not have an Id until its saved. - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// Alias of the MemberType the Member should be based on - /// - public IMember CreateMember(string username, string email, string name, string memberTypeAlias) - { - var memberType = FindMemberTypeByAlias(memberTypeAlias); - return CreateMember(username, email, name, memberType); - } - - /// - /// Creates an object without persisting it - /// - /// This method is convenient for when you need to add properties to a new Member - /// before persisting it in order to limit the amount of times its saved. - /// Also note that the returned will not have an Id until its saved. - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// MemberType the Member should be based on - /// - public IMember CreateMember(string username, string email, string name, IMemberType memberType) - { - var member = new Member(name, email.ToLower().Trim(), username, memberType); - - Created.RaiseEvent(new NewEventArgs(member, false, memberType.Alias, -1), this); - - return member; - } - - /// - /// Creates and persists a Member - /// - /// Using this method will persist the Member object before its returned - /// meaning that it will have an Id available (unlike the CreateMember method) - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// Alias of the MemberType the Member should be based on - /// - public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias) - { - var memberType = FindMemberTypeByAlias(memberTypeAlias); - return CreateMemberWithIdentity(username, email, name, memberType); - } - - /// - /// Creates and persists a Member - /// - /// Using this method will persist the Member object before its returned - /// meaning that it will have an Id available (unlike the CreateMember method) - /// Username of the Member to create - /// Email of the Member to create - /// MemberType the Member should be based on - /// - public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType) - { - return CreateMemberWithIdentity(username, email, username, memberType); - } - - /// - /// Creates and persists a Member - /// - /// Using this method will persist the Member object before its returned - /// meaning that it will have an Id available (unlike the CreateMember method) - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// MemberType the Member should be based on - /// - public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType) - { - return CreateMemberWithIdentity(username, email, name, "", memberType); - } - - /// - /// Creates and persists a new - /// - /// An can be of type or - /// Username of the to create - /// Email of the to create - /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database - /// Alias of the Type - /// - IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias) - { - var memberType = FindMemberTypeByAlias(memberTypeAlias); - return CreateMemberWithIdentity(username, email, username, passwordValue, memberType); - } - - /// - /// Creates and persists a Member - /// - /// Using this method will persist the Member object before its returned - /// meaning that it will have an Id available (unlike the CreateMember method) - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database - /// MemberType the Member should be based on - /// - private IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, IMemberType memberType) - { - if (memberType == null) throw new ArgumentNullException("memberType"); - - var member = new Member(name, email.ToLower().Trim(), username, passwordValue, memberType); - - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(member), this)) - { - member.WasCancelled = true; - return member; - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - repository.AddOrUpdate(member); - //insert the xml - repository.AddOrUpdateContentXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); - } - - uow.Commit(); - } - - Saved.RaiseEvent(new SaveEventArgs(member, false), this); - Created.RaiseEvent(new NewEventArgs(member, false, memberType.Alias, -1), this); - - return member; - } - - /// - /// Gets an by its provider key - /// - /// Id to use for retrieval - /// - public IMember GetByProviderKey(object id) - { - var asGuid = id.TryConvertTo(); - if (asGuid.Success) - { - return GetByKey((Guid)id); - } - var asInt = id.TryConvertTo(); - if (asInt.Success) - { - return GetById((int)id); - } - - return null; - } - - /// - /// Get an by email - /// - /// Email to use for retrieval - /// - public IMember GetByEmail(string email) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - var query = Query.Builder.Where(x => x.Email.Equals(email)); - var member = repository.GetByQuery(query).FirstOrDefault(); - - return member; - } - } - - /// - /// Get an by username - /// - /// Username to use for retrieval - /// - public IMember GetByUsername(string username) - { - //TODO: Somewhere in here, whether at this level or the repository level, we need to add - // a caching mechanism since this method is used by all the membership providers and could be - // called quite a bit when dealing with members. - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - var query = Query.Builder.Where(x => x.Username.Equals(username)); - var member = repository.GetByQuery(query).FirstOrDefault(); - - return member; - } - } - - /// - /// Deletes an - /// - /// to Delete - public void Delete(IMember member) - { - if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(member), this)) - return; - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - repository.Delete(member); - uow.Commit(); - - var args = new DeleteEventArgs(member, false); - Deleted.RaiseEvent(args, this); - - //remove any flagged media files - repository.DeleteMediaFiles(args.MediaFilesToDelete); - } - } - - /// - /// Saves an - /// - /// to Save - /// Optional parameter to raise events. - /// Default is True otherwise set to False to not raise events - public void Save(IMember entity, bool raiseEvents = true) - { - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(entity), this)) - { - return; - } - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - repository.AddOrUpdate(entity); - repository.AddOrUpdateContentXml(entity, m => _entitySerializer.Serialize(_dataTypeService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(entity, m => _entitySerializer.Serialize(_dataTypeService, m)); - } - - uow.Commit(); - } - - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(entity, false), this); - } - - /// - /// Saves a list of objects - /// - /// to save - /// Optional parameter to raise events. - /// Default is True otherwise set to False to not raise events - public void Save(IEnumerable entities, bool raiseEvents = true) - { - var asArray = entities.ToArray(); - - if (raiseEvents) - { - if (Saving.IsRaisedEventCancelled(new SaveEventArgs(asArray), this)) - return; - } - using (new WriteLock(Locker)) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - foreach (var member in asArray) - { - repository.AddOrUpdate(member); - repository.AddOrUpdateContentXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); - // generate preview for blame history? - if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) - { - repository.AddOrUpdatePreviewXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); - } - } - - //commit the whole lot in one go - uow.Commit(); - } - - if (raiseEvents) - Saved.RaiseEvent(new SaveEventArgs(asArray, false), this); - } - } - - #endregion - - #region IMembershipRoleService Implementation - - public void AddRole(string roleName) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) - { - repository.CreateIfNotExists(roleName); - } - } - - public IEnumerable GetAllRoles() - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) - { - var result = repository.GetAll(); - return result.Select(x => x.Name).Distinct(); - } - } - - public IEnumerable GetAllRoles(int memberId) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) - { - var result = repository.GetMemberGroupsForMember(memberId); - return result.Select(x => x.Name).Distinct(); - } - } - - public IEnumerable GetAllRoles(string username) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) - { - var result = repository.GetMemberGroupsForMember(username); - return result.Select(x => x.Name).Distinct(); - } - } - - public IEnumerable GetMembersInRole(string roleName) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - return repository.GetByMemberGroup(roleName); - } - } - - public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberRepository(uow)) - { - return repository.FindMembersInRole(roleName, usernameToMatch, matchType); - } - } - - public bool DeleteRole(string roleName, bool throwIfBeingUsed) - { - using (new WriteLock(Locker)) - { - if (throwIfBeingUsed) - { - var inRole = GetMembersInRole(roleName); - if (inRole.Any()) - { - throw new InvalidOperationException("The role " + roleName + " is currently assigned to members"); - } - } - - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) - { - var qry = new Query().Where(g => g.Name == roleName); - var found = repository.GetByQuery(qry).ToArray(); - - foreach (var memberGroup in found) - { - _memberGroupService.Delete(memberGroup); - } - return found.Any(); - } - } - } - public void AssignRole(string username, string roleName) - { - AssignRoles(new[] { username }, new[] { roleName }); - } - - public void AssignRoles(string[] usernames, string[] roleNames) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) - { - repository.AssignRoles(usernames, roleNames); - } - } - - public void DissociateRole(string username, string roleName) - { - DissociateRoles(new[] { username }, new[] { roleName }); - } - - public void DissociateRoles(string[] usernames, string[] roleNames) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) - { - repository.DissociateRoles(usernames, roleNames); - } - } - - public void AssignRole(int memberId, string roleName) - { - AssignRoles(new[] { memberId }, new[] { roleName }); - } - - public void AssignRoles(int[] memberIds, string[] roleNames) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) - { - repository.AssignRoles(memberIds, roleNames); - } - } - - public void DissociateRole(int memberId, string roleName) - { - DissociateRoles(new[] { memberId }, new[] { roleName }); - } - - public void DissociateRoles(int[] memberIds, string[] roleNames) - { - var uow = UowProvider.GetUnitOfWork(); - using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) - { - repository.DissociateRoles(memberIds, roleNames); - } - } - - - - #endregion - - private IMemberType FindMemberTypeByAlias(string memberTypeAlias) - { - using (var repository = RepositoryFactory.CreateMemberTypeRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder.Where(x => x.Alias == memberTypeAlias); - var types = repository.GetByQuery(query); - - if (types.Any() == false) - throw new Exception( - string.Format("No MemberType matching the passed in Alias: '{0}' was found", - memberTypeAlias)); - - var contentType = types.First(); - - if (contentType == null) - throw new Exception(string.Format("MemberType matching the passed in Alias: '{0}' was null", - memberTypeAlias)); - - return contentType; - } - } - - private void Audit(AuditType type, string message, int userId, int objectId) - { - var uow = UowProvider.GetUnitOfWork(); - using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) - { - auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); - uow.Commit(); - } - } - - #region Event Handlers - - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> Deleting; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> Deleted; - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> Saving; - - /// - /// Occurs after Create - /// - /// - /// Please note that the Member object has been created, but might not have been saved - /// so it does not have an identity yet (meaning no Id has been set). - /// - public static event TypedEventHandler> Created; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> Saved; - - #endregion - - /// - /// A helper method that will create a basic/generic member for use with a generic membership provider - /// - /// - internal static IMember CreateGenericMembershipProviderMember(string name, string email, string username, string password) - { - var identity = int.MaxValue; - - var memType = new MemberType(-1); - var propGroup = new PropertyGroup - { - Name = "Membership", - Id = --identity - }; - propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext, Constants.Conventions.Member.Comments) - { - Name = Constants.Conventions.Member.CommentsLabel, - SortOrder = 0, - Id = --identity, - Key = identity.ToGuid() - }); - propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer, Constants.Conventions.Member.IsApproved) - { - Name = Constants.Conventions.Member.IsApprovedLabel, - SortOrder = 3, - Id = --identity, - Key = identity.ToGuid() - }); - propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer, Constants.Conventions.Member.IsLockedOut) - { - Name = Constants.Conventions.Member.IsLockedOutLabel, - SortOrder = 4, - Id = --identity, - Key = identity.ToGuid() - }); - propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastLockoutDate) - { - Name = Constants.Conventions.Member.LastLockoutDateLabel, - SortOrder = 5, - Id = --identity, - Key = identity.ToGuid() - }); - propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastLoginDate) - { - Name = Constants.Conventions.Member.LastLoginDateLabel, - SortOrder = 6, - Id = --identity, - Key = identity.ToGuid() - }); - propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastPasswordChangeDate) - { - Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, - SortOrder = 7, - Id = --identity, - Key = identity.ToGuid() - }); - - memType.PropertyGroups.Add(propGroup); - - var member = new Member(name, email, username, password, memType); - - //we've assigned ids to the property types and groups but we also need to assign fake ids to the properties themselves. - foreach (var property in member.Properties) - { - property.Id = --identity; - } - - return member; - } - } + /// + /// Represents the MemberService. + /// + public class MemberService : RepositoryService, IMemberService + { + private readonly IMemberGroupService _memberGroupService; + private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); + private readonly IDataTypeService _dataTypeService; + private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); + + public MemberService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService, IDataTypeService dataTypeService) + : base(provider, repositoryFactory, logger, eventMessagesFactory) + { + if (memberGroupService == null) throw new ArgumentNullException("memberGroupService"); + if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); + _memberGroupService = memberGroupService; + _dataTypeService = dataTypeService; + } + + #region IMemberService Implementation + + /// + /// Gets the default MemberType alias + /// + /// By default we'll return the 'writer', but we need to check it exists. If it doesn't we'll + /// return the first type that is not an admin, otherwise if there's only one we will return that one. + /// Alias of the default MemberType + public string GetDefaultMemberType() + { + using (var repository = RepositoryFactory.CreateMemberTypeRepository(UowProvider.GetUnitOfWork())) + { + var types = repository.GetAll(new int[] { }).Select(x => x.Alias).ToArray(); + + if (types.Any() == false) + { + throw new InvalidOperationException("No member types could be resolved"); + } + + if (types.InvariantContains("Member")) + { + return types.First(x => x.InvariantEquals("Member")); + } + + return types.First(); + } + } + + /// + /// Checks if a Member with the username exists + /// + /// Username to check + /// True if the Member exists otherwise False + public bool Exists(string username) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + return repository.Exists(username); + } + } + + /// + /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method + /// + /// This method exists so that Umbraco developers can use one entry point to create/update + /// Members if they choose to. + /// The Member to save the password for + /// The password to encrypt and save + public void SavePassword(IMember member, string password) + { + if (member == null) throw new ArgumentNullException("member"); + + var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); + if (provider.IsUmbracoMembershipProvider()) + { + provider.ChangePassword(member.Username, "", password); + } + else + { + throw new NotSupportedException("When using a non-Umbraco membership provider you must change the member password by using the MembershipProvider.ChangePassword method"); + } + + //go re-fetch the member and update the properties that may have changed + var result = GetByUsername(member.Username); + + //should never be null but it could have been deleted by another thread. + if (result == null) + return; + + member.RawPasswordValue = result.RawPasswordValue; + member.LastPasswordChangeDate = result.LastPasswordChangeDate; + member.UpdateDate = result.UpdateDate; + } + + /// + /// Checks if a Member with the id exists + /// + /// Id of the Member + /// True if the Member exists otherwise False + public bool Exists(int id) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + return repository.Exists(id); + } + } + + /// + /// Gets a Member by its integer id + /// + /// Id + /// + public IMember GetById(int id) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + return repository.Get(id); + } + } + + /// + /// Gets a Member by the unique key + /// + /// The guid key corresponds to the unique id in the database + /// and the user id in the membership provider. + /// Id + /// + public IMember GetByKey(Guid id) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Key == id); + var member = repository.GetByQuery(query).FirstOrDefault(); + return member; + } + } + + /// + /// Gets all Members for the specified MemberType alias + /// + /// Alias of the MemberType + /// + public IEnumerable GetMembersByMemberType(string memberTypeAlias) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ContentTypeAlias == memberTypeAlias); + var members = repository.GetByQuery(query); + return members; + } + } + + /// + /// Gets all Members for the MemberType id + /// + /// Id of the MemberType + /// + public IEnumerable GetMembersByMemberType(int memberTypeId) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + repository.Get(memberTypeId); + var query = Query.Builder.Where(x => x.ContentTypeId == memberTypeId); + var members = repository.GetByQuery(query); + return members; + } + } + + /// + /// Gets all Members within the specified MemberGroup name + /// + /// Name of the MemberGroup + /// + public IEnumerable GetMembersByGroup(string memberGroupName) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetByMemberGroup(memberGroupName); + } + } + + /// + /// Gets all Members with the ids specified + /// + /// If no Ids are specified all Members will be retrieved + /// Optional list of Member Ids + /// + public IEnumerable GetAllMembers(params int[] ids) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + return repository.GetAll(ids); + } + } + + /// + /// Delete Members of the specified MemberType id + /// + /// Id of the MemberType + public void DeleteMembersOfType(int memberTypeId) + { + using (new WriteLock(Locker)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateMemberRepository(uow); + //TODO: What about content that has the contenttype as part of its composition? + var query = Query.Builder.Where(x => x.ContentTypeId == memberTypeId); + var members = repository.GetByQuery(query).ToArray(); + + if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(members), this)) + return; + + foreach (var member in members) + { + //Permantly delete the member + Delete(member); + } + } + } + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable FindMembersByDisplayName(string displayNameToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + long total; + var result = FindMembersByDisplayName(displayNameToMatch, Convert.ToInt64(pageIndex), pageSize, out total, matchType); + totalRecords = Convert.ToInt32(total); + return result; + } + + /// + /// Finds Members based on their display name + /// + /// Display name to match + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// The type of match to make as . Default is + /// + public IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + var query = new Query(); + + switch (matchType) + { + case StringPropertyMatchType.Exact: + query.Where(member => member.Name.Equals(displayNameToMatch)); + break; + case StringPropertyMatchType.Contains: + query.Where(member => member.Name.Contains(displayNameToMatch)); + break; + case StringPropertyMatchType.StartsWith: + query.Where(member => member.Name.StartsWith(displayNameToMatch)); + break; + case StringPropertyMatchType.EndsWith: + query.Where(member => member.Name.EndsWith(displayNameToMatch)); + break; + case StringPropertyMatchType.Wildcard: + query.Where(member => member.Name.SqlWildcard(displayNameToMatch, TextColumnType.NVarchar)); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } + + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); + } + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable FindByEmail(string emailStringToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + long total; + var result = FindByEmail(emailStringToMatch, Convert.ToInt64(pageIndex), pageSize, out total, matchType); + totalRecords = Convert.ToInt32(total); + return result; + } + + /// + /// Finds a list of objects by a partial email string + /// + /// Partial email string to match + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// The type of match to make as . Default is + /// + public IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + var query = new Query(); + + switch (matchType) + { + case StringPropertyMatchType.Exact: + query.Where(member => member.Email.Equals(emailStringToMatch)); + break; + case StringPropertyMatchType.Contains: + query.Where(member => member.Email.Contains(emailStringToMatch)); + break; + case StringPropertyMatchType.StartsWith: + query.Where(member => member.Email.StartsWith(emailStringToMatch)); + break; + case StringPropertyMatchType.EndsWith: + query.Where(member => member.Email.EndsWith(emailStringToMatch)); + break; + case StringPropertyMatchType.Wildcard: + query.Where(member => member.Email.SqlWildcard(emailStringToMatch, TextColumnType.NVarchar)); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } + + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Email", Direction.Ascending, orderBySystemField: true); + } + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable FindByUsername(string login, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + long total; + var result = FindByUsername(login, Convert.ToInt64(pageIndex), pageSize, out total, matchType); + totalRecords = Convert.ToInt32(total); + return result; + } + + /// + /// Finds a list of objects by a partial username + /// + /// Partial username to match + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// The type of match to make as . Default is + /// + public IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + var query = new Query(); + + switch (matchType) + { + case StringPropertyMatchType.Exact: + query.Where(member => member.Username.Equals(login)); + break; + case StringPropertyMatchType.Contains: + query.Where(member => member.Username.Contains(login)); + break; + case StringPropertyMatchType.StartsWith: + query.Where(member => member.Username.StartsWith(login)); + break; + case StringPropertyMatchType.EndsWith: + query.Where(member => member.Username.EndsWith(login)); + break; + case StringPropertyMatchType.Wildcard: + query.Where(member => member.Email.SqlWildcard(login, TextColumnType.NVarchar)); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } + + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, orderBySystemField: true); + } + } + + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// The type of match to make as . Default is + /// + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + IQuery query; + + switch (matchType) + { + case StringPropertyMatchType.Exact: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + (((Member)x).LongStringPropertyValue.SqlEquals(value, TextColumnType.NText) || + ((Member)x).ShortStringPropertyValue.SqlEquals(value, TextColumnType.NVarchar))); + break; + case StringPropertyMatchType.Contains: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + (((Member)x).LongStringPropertyValue.SqlContains(value, TextColumnType.NText) || + ((Member)x).ShortStringPropertyValue.SqlContains(value, TextColumnType.NVarchar))); + break; + case StringPropertyMatchType.StartsWith: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + (((Member)x).LongStringPropertyValue.SqlStartsWith(value, TextColumnType.NText) || + ((Member)x).ShortStringPropertyValue.SqlStartsWith(value, TextColumnType.NVarchar))); + break; + case StringPropertyMatchType.EndsWith: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + (((Member)x).LongStringPropertyValue.SqlEndsWith(value, TextColumnType.NText) || + ((Member)x).ShortStringPropertyValue.SqlEndsWith(value, TextColumnType.NVarchar))); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } + + var members = repository.GetByQuery(query); + return members; + } + } + + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// The type of match to make as . Default is + /// + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + IQuery query; + + switch (matchType) + { + case ValuePropertyMatchType.Exact: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerPropertyValue == value); + break; + case ValuePropertyMatchType.GreaterThan: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerPropertyValue > value); + break; + case ValuePropertyMatchType.LessThan: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerPropertyValue < value); + break; + case ValuePropertyMatchType.GreaterThanOrEqualTo: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerPropertyValue >= value); + break; + case ValuePropertyMatchType.LessThanOrEqualTo: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).IntegerPropertyValue <= value); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } + + var members = repository.GetByQuery(query); + return members; + } + } + + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, bool value) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + var query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).BoolPropertyValue == value); + + var members = repository.GetByQuery(query); + return members; + } + } + + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// The type of match to make as . Default is + /// + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + IQuery query; + + switch (matchType) + { + case ValuePropertyMatchType.Exact: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue == value); + break; + case ValuePropertyMatchType.GreaterThan: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue > value); + break; + case ValuePropertyMatchType.LessThan: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue < value); + break; + case ValuePropertyMatchType.GreaterThanOrEqualTo: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue >= value); + break; + case ValuePropertyMatchType.LessThanOrEqualTo: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == propertyTypeAlias && + ((Member)x).DateTimePropertyValue <= value); + break; + default: + throw new ArgumentOutOfRangeException("matchType"); + } + + //TODO: Since this is by property value, we need a GetByPropertyQuery on the repo! + var members = repository.GetByQuery(query); + return members; + } + } + + /// + /// Rebuilds all xml content in the cmsContentXml table for all members + /// + /// + /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures + /// for all members = USE WITH CARE! + /// + /// True if publishing succeeded, otherwise False + public void RebuildXmlStructures(params int[] memberTypeIds) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + repository.RebuildXmlStructures( + member => _entitySerializer.Serialize(_dataTypeService, member), + contentTypeIds: memberTypeIds.Length == 0 ? null : memberTypeIds); + } + + Audit(AuditType.Publish, "MemberService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1); + } + + #endregion + + #region IMembershipMemberService Implementation + + /// + /// Gets the total number of Members based on the count type + /// + /// + /// The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members + /// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science + /// but that is how MS have made theirs so we'll follow that principal. + /// + /// to count by + /// with number of Members for passed in type + public int GetCount(MemberCountType countType) + { + using (var repository = RepositoryFactory.CreateMemberRepository(UowProvider.GetUnitOfWork())) + { + IQuery query; + + switch (countType) + { + case MemberCountType.All: + query = new Query(); + return repository.Count(query); + case MemberCountType.Online: + var fromDate = DateTime.Now.AddMinutes(-Membership.UserIsOnlineTimeWindow); + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.LastLoginDate && + ((Member)x).DateTimePropertyValue > fromDate); + return repository.GetCountByQuery(query); + case MemberCountType.LockedOut: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.IsLockedOut && + ((Member)x).BoolPropertyValue == true); + return repository.GetCountByQuery(query); + case MemberCountType.Approved: + query = + Query.Builder.Where( + x => + ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.IsApproved && + ((Member)x).BoolPropertyValue == true); + return repository.GetCountByQuery(query); + default: + throw new ArgumentOutOfRangeException("countType"); + } + } + + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords) + { + long total; + var result = GetAll(Convert.ToInt64(pageIndex), pageSize, out total); + totalRecords = Convert.ToInt32(total); + return result; + } + + /// + /// Gets a list of paged objects + /// + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// + public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, orderBySystemField: true); + } + } + + [Obsolete("Use the overload with 'long' parameter types instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = "") + { + long total; + var result = GetAll(Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, orderBySystemField, memberTypeAlias, filter); + totalRecords = Convert.ToInt32(total); + return result; + } + + public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = "") + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + if (memberTypeAlias == null) + { + return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, filter); + } + var query = new Query().Where(x => x.ContentTypeAlias == memberTypeAlias); + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, filter); + } + } + + /// + /// Gets the count of Members by an optional MemberType alias + /// + /// If no alias is supplied then the count for all Member will be returned + /// Optional alias for the MemberType when counting number of Members + /// with number of Members + public int Count(string memberTypeAlias = null) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + return repository.Count(memberTypeAlias); + } + } + + /// + /// Creates an object without persisting it + /// + /// This method is convenient for when you need to add properties to a new Member + /// before persisting it in order to limit the amount of times its saved. + /// Also note that the returned will not have an Id until its saved. + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// Alias of the MemberType the Member should be based on + /// + public IMember CreateMember(string username, string email, string name, string memberTypeAlias) + { + var memberType = FindMemberTypeByAlias(memberTypeAlias); + return CreateMember(username, email, name, memberType); + } + + /// + /// Creates an object without persisting it + /// + /// This method is convenient for when you need to add properties to a new Member + /// before persisting it in order to limit the amount of times its saved. + /// Also note that the returned will not have an Id until its saved. + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// MemberType the Member should be based on + /// + public IMember CreateMember(string username, string email, string name, IMemberType memberType) + { + var member = new Member(name, email.ToLower().Trim(), username, memberType); + + Created.RaiseEvent(new NewEventArgs(member, false, memberType.Alias, -1), this); + + return member; + } + + /// + /// Creates and persists a Member + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// Alias of the MemberType the Member should be based on + /// + public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias) + { + var memberType = FindMemberTypeByAlias(memberTypeAlias); + return CreateMemberWithIdentity(username, email, name, memberType); + } + + /// + /// Creates and persists a Member + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// Username of the Member to create + /// Email of the Member to create + /// MemberType the Member should be based on + /// + public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType) + { + return CreateMemberWithIdentity(username, email, username, memberType); + } + + /// + /// Creates and persists a Member + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// MemberType the Member should be based on + /// + public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType) + { + return CreateMemberWithIdentity(username, email, name, "", memberType); + } + + /// + /// Creates and persists a new + /// + /// An can be of type or + /// Username of the to create + /// Email of the to create + /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database + /// Alias of the Type + /// + IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias) + { + var memberType = FindMemberTypeByAlias(memberTypeAlias); + return CreateMemberWithIdentity(username, email, username, passwordValue, memberType); + } + + /// + /// Creates and persists a Member + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database + /// MemberType the Member should be based on + /// + private IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, IMemberType memberType) + { + if (memberType == null) throw new ArgumentNullException("memberType"); + + var member = new Member(name, email.ToLower().Trim(), username, passwordValue, memberType); + + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(member), this)) + { + member.WasCancelled = true; + return member; + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + repository.AddOrUpdate(member); + //insert the xml + repository.AddOrUpdateContentXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + { + repository.AddOrUpdatePreviewXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); + } + + uow.Commit(); + } + + Saved.RaiseEvent(new SaveEventArgs(member, false), this); + Created.RaiseEvent(new NewEventArgs(member, false, memberType.Alias, -1), this); + + return member; + } + + /// + /// Gets an by its provider key + /// + /// Id to use for retrieval + /// + public IMember GetByProviderKey(object id) + { + var asGuid = id.TryConvertTo(); + if (asGuid.Success) + { + return GetByKey((Guid)id); + } + var asInt = id.TryConvertTo(); + if (asInt.Success) + { + return GetById((int)id); + } + + return null; + } + + /// + /// Get an by email + /// + /// Email to use for retrieval + /// + public IMember GetByEmail(string email) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + var query = Query.Builder.Where(x => x.Email.Equals(email)); + var member = repository.GetByQuery(query).FirstOrDefault(); + + return member; + } + } + + /// + /// Get an by username + /// + /// Username to use for retrieval + /// + public IMember GetByUsername(string username) + { + //TODO: Somewhere in here, whether at this level or the repository level, we need to add + // a caching mechanism since this method is used by all the membership providers and could be + // called quite a bit when dealing with members. + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + var query = Query.Builder.Where(x => x.Username.Equals(username)); + var member = repository.GetByQuery(query).FirstOrDefault(); + + return member; + } + } + + /// + /// Deletes an + /// + /// to Delete + public void Delete(IMember member) + { + if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(member), this)) + return; + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + repository.Delete(member); + uow.Commit(); + + var args = new DeleteEventArgs(member, false); + Deleted.RaiseEvent(args, this); + + //remove any flagged media files + repository.DeleteMediaFiles(args.MediaFilesToDelete); + } + } + + /// + /// Saves an + /// + /// to Save + /// Optional parameter to raise events. + /// Default is True otherwise set to False to not raise events + public void Save(IMember entity, bool raiseEvents = true) + { + if (raiseEvents) + { + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(entity), this)) + { + return; + } + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + repository.AddOrUpdate(entity); + repository.AddOrUpdateContentXml(entity, m => _entitySerializer.Serialize(_dataTypeService, m)); + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + { + repository.AddOrUpdatePreviewXml(entity, m => _entitySerializer.Serialize(_dataTypeService, m)); + } + + uow.Commit(); + } + + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(entity, false), this); + } + + /// + /// Saves a list of objects + /// + /// to save + /// Optional parameter to raise events. + /// Default is True otherwise set to False to not raise events + public void Save(IEnumerable entities, bool raiseEvents = true) + { + var asArray = entities.ToArray(); + + if (raiseEvents) + { + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(asArray), this)) + return; + } + using (new WriteLock(Locker)) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + foreach (var member in asArray) + { + repository.AddOrUpdate(member); + repository.AddOrUpdateContentXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); + // generate preview for blame history? + if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) + { + repository.AddOrUpdatePreviewXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); + } + } + + //commit the whole lot in one go + uow.Commit(); + } + + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(asArray, false), this); + } + } + + #endregion + + #region IMembershipRoleService Implementation + + public void AddRole(string roleName) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) + { + repository.CreateIfNotExists(roleName); + } + } + + public IEnumerable GetAllRoles() + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) + { + var result = repository.GetAll(); + return result.Select(x => x.Name).Distinct(); + } + } + + public IEnumerable GetAllRoles(int memberId) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) + { + var result = repository.GetMemberGroupsForMember(memberId); + return result.Select(x => x.Name).Distinct(); + } + } + + public IEnumerable GetAllRoles(string username) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) + { + var result = repository.GetMemberGroupsForMember(username); + return result.Select(x => x.Name).Distinct(); + } + } + + public IEnumerable GetMembersInRole(string roleName) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + return repository.GetByMemberGroup(roleName); + } + } + + public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberRepository(uow)) + { + return repository.FindMembersInRole(roleName, usernameToMatch, matchType); + } + } + + public bool DeleteRole(string roleName, bool throwIfBeingUsed) + { + using (new WriteLock(Locker)) + { + if (throwIfBeingUsed) + { + var inRole = GetMembersInRole(roleName); + if (inRole.Any()) + { + throw new InvalidOperationException("The role " + roleName + " is currently assigned to members"); + } + } + + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) + { + var qry = new Query().Where(g => g.Name == roleName); + var found = repository.GetByQuery(qry).ToArray(); + + foreach (var memberGroup in found) + { + _memberGroupService.Delete(memberGroup); + } + return found.Any(); + } + } + } + public void AssignRole(string username, string roleName) + { + AssignRoles(new[] { username }, new[] { roleName }); + } + + public void AssignRoles(string[] usernames, string[] roleNames) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) + { + repository.AssignRoles(usernames, roleNames); + } + } + + public void DissociateRole(string username, string roleName) + { + DissociateRoles(new[] { username }, new[] { roleName }); + } + + public void DissociateRoles(string[] usernames, string[] roleNames) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) + { + repository.DissociateRoles(usernames, roleNames); + } + } + + public void AssignRole(int memberId, string roleName) + { + AssignRoles(new[] { memberId }, new[] { roleName }); + } + + public void AssignRoles(int[] memberIds, string[] roleNames) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) + { + repository.AssignRoles(memberIds, roleNames); + } + } + + public void DissociateRole(int memberId, string roleName) + { + DissociateRoles(new[] { memberId }, new[] { roleName }); + } + + public void DissociateRoles(int[] memberIds, string[] roleNames) + { + var uow = UowProvider.GetUnitOfWork(); + using (var repository = RepositoryFactory.CreateMemberGroupRepository(uow)) + { + repository.DissociateRoles(memberIds, roleNames); + } + } + + + + #endregion + + private IMemberType FindMemberTypeByAlias(string memberTypeAlias) + { + using (var repository = RepositoryFactory.CreateMemberTypeRepository(UowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Alias == memberTypeAlias); + var types = repository.GetByQuery(query); + + if (types.Any() == false) + throw new Exception( + string.Format("No MemberType matching the passed in Alias: '{0}' was found", + memberTypeAlias)); + + var contentType = types.First(); + + if (contentType == null) + throw new Exception(string.Format("MemberType matching the passed in Alias: '{0}' was null", + memberTypeAlias)); + + return contentType; + } + } + + private void Audit(AuditType type, string message, int userId, int objectId) + { + var uow = UowProvider.GetUnitOfWork(); + using (var auditRepo = RepositoryFactory.CreateAuditRepository(uow)) + { + auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); + uow.Commit(); + } + } + + #region Event Handlers + + /// + /// Occurs before Delete + /// + public static event TypedEventHandler> Deleting; + + /// + /// Occurs after Delete + /// + public static event TypedEventHandler> Deleted; + + /// + /// Occurs before Save + /// + public static event TypedEventHandler> Saving; + + /// + /// Occurs after Create + /// + /// + /// Please note that the Member object has been created, but might not have been saved + /// so it does not have an identity yet (meaning no Id has been set). + /// + public static event TypedEventHandler> Created; + + /// + /// Occurs after Save + /// + public static event TypedEventHandler> Saved; + + #endregion + + /// + /// A helper method that will create a basic/generic member for use with a generic membership provider + /// + /// + internal static IMember CreateGenericMembershipProviderMember(string name, string email, string username, string password) + { + var identity = int.MaxValue; + + var memType = new MemberType(-1); + var propGroup = new PropertyGroup + { + Name = "Membership", + Id = --identity + }; + propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext, Constants.Conventions.Member.Comments) + { + Name = Constants.Conventions.Member.CommentsLabel, + SortOrder = 0, + Id = --identity, + Key = identity.ToGuid() + }); + propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer, Constants.Conventions.Member.IsApproved) + { + Name = Constants.Conventions.Member.IsApprovedLabel, + SortOrder = 3, + Id = --identity, + Key = identity.ToGuid() + }); + propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer, Constants.Conventions.Member.IsLockedOut) + { + Name = Constants.Conventions.Member.IsLockedOutLabel, + SortOrder = 4, + Id = --identity, + Key = identity.ToGuid() + }); + propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastLockoutDate) + { + Name = Constants.Conventions.Member.LastLockoutDateLabel, + SortOrder = 5, + Id = --identity, + Key = identity.ToGuid() + }); + propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastLoginDate) + { + Name = Constants.Conventions.Member.LastLoginDateLabel, + SortOrder = 6, + Id = --identity, + Key = identity.ToGuid() + }); + propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastPasswordChangeDate) + { + Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, + SortOrder = 7, + Id = --identity, + Key = identity.ToGuid() + }); + + memType.PropertyGroups.Add(propGroup); + + var member = new Member(name, email, username, password, memType); + + //we've assigned ids to the property types and groups but we also need to assign fake ids to the properties themselves. + foreach (var property in member.Properties) + { + property.Id = --identity; + } + + return member; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index f21371ef2b..e256e4082a 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -24,795 +24,795 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Tests.Persistence.Repositories { - [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] - [TestFixture] - public class ContentRepositoryTest : BaseDatabaseFactoryTest - { - [SetUp] - public override void Initialize() - { - base.Initialize(); - - CreateTestData(); - } - - [TearDown] - public override void TearDown() - { - base.TearDown(); - } - - private ContentRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository) - { - TemplateRepository tr; - return CreateRepository(unitOfWork, out contentTypeRepository, out tr); - } - - private ContentRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository, out TemplateRepository templateRepository) - { - templateRepository = new TemplateRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepository = new TagRepository(unitOfWork, CacheHelper, Logger, SqlSyntax); - contentTypeRepository = new ContentTypeRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, templateRepository); - var repository = new ContentRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, contentTypeRepository, templateRepository, tagRepository, Mock.Of()); - return repository; - } - - [Test] - public void Rebuild_Xml_Structures_With_Non_Latest_Version() - { - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - var contentType1 = MockedContentTypes.CreateSimpleContentType("Textpage1", "Textpage1"); - contentTypeRepository.AddOrUpdate(contentType1); - - var allCreated = new List(); - - //create 100 non published - for (var i = 0; i < 100; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType1); - repository.AddOrUpdate(c1); - allCreated.Add(c1); - } - //create 100 published - for (var i = 0; i < 100; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType1); - c1.ChangePublishedState(PublishedState.Published); - repository.AddOrUpdate(c1); - allCreated.Add(c1); - } - unitOfWork.Commit(); - - //now create some versions of this content - this shouldn't affect the xml structures saved - for (int i = 0; i < allCreated.Count; i++) - { - allCreated[i].Name = "blah" + i; - //IMPORTANT testing note here: We need to changed the published state here so that - // it doesn't automatically think this is simply publishing again - this forces the latest - // version to be Saved and not published - allCreated[i].ChangePublishedState(PublishedState.Saved); - repository.AddOrUpdate(allCreated[i]); - } - unitOfWork.Commit(); - - //delete all xml - unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); - Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - - repository.RebuildXmlStructures(media => new XElement("test"), 10); - - Assert.AreEqual(100, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - } - } - - [Test] - public void Rebuild_All_Xml_Structures() - { - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - - var contentType1 = MockedContentTypes.CreateSimpleContentType("Textpage1", "Textpage1"); - contentTypeRepository.AddOrUpdate(contentType1); - var allCreated = new List(); - - for (var i = 0; i < 100; i++) - { - //These will be non-published so shouldn't show up - var c1 = MockedContent.CreateSimpleContent(contentType1); - repository.AddOrUpdate(c1); - allCreated.Add(c1); - } - for (var i = 0; i < 100; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType1); - c1.ChangePublishedState(PublishedState.Published); - repository.AddOrUpdate(c1); - allCreated.Add(c1); - } - unitOfWork.Commit(); - - //now create some versions of this content - this shouldn't affect the xml structures saved - for (int i = 0; i < allCreated.Count; i++) - { - allCreated[i].Name = "blah" + i; - repository.AddOrUpdate(allCreated[i]); - } - unitOfWork.Commit(); - - //delete all xml - unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); - Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - - repository.RebuildXmlStructures(media => new XElement("test"), 10); - - Assert.AreEqual(100, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - } - } - - [Test] - public void Rebuild_All_Xml_Structures_For_Content_Type() - { - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - var contentType1 = MockedContentTypes.CreateSimpleContentType("Textpage1", "Textpage1"); - var contentType2 = MockedContentTypes.CreateSimpleContentType("Textpage2", "Textpage2"); - var contentType3 = MockedContentTypes.CreateSimpleContentType("Textpage3", "Textpage3"); - contentTypeRepository.AddOrUpdate(contentType1); - contentTypeRepository.AddOrUpdate(contentType2); - contentTypeRepository.AddOrUpdate(contentType3); - - var allCreated = new List(); - - for (var i = 0; i < 30; i++) - { - //These will be non-published so shouldn't show up - var c1 = MockedContent.CreateSimpleContent(contentType1); - repository.AddOrUpdate(c1); - allCreated.Add(c1); - } - for (var i = 0; i < 30; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType1); - c1.ChangePublishedState(PublishedState.Published); - repository.AddOrUpdate(c1); - allCreated.Add(c1); - } - for (var i = 0; i < 30; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType2); - c1.ChangePublishedState(PublishedState.Published); - repository.AddOrUpdate(c1); - allCreated.Add(c1); - } - for (var i = 0; i < 30; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType3); - c1.ChangePublishedState(PublishedState.Published); - repository.AddOrUpdate(c1); - allCreated.Add(c1); - } - unitOfWork.Commit(); - - //now create some versions of this content - this shouldn't affect the xml structures saved - for (int i = 0; i < allCreated.Count; i++) - { - allCreated[i].Name = "blah" + i; - repository.AddOrUpdate(allCreated[i]); - } - unitOfWork.Commit(); - - //delete all xml - unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); - Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - - repository.RebuildXmlStructures(media => new XElement("test"), 10, contentTypeIds: new[] { contentType1.Id, contentType2.Id }); - - Assert.AreEqual(60, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - } - } - - [Test] - public void Ensures_Permissions_Are_Set_If_Parent_Entity_Permissions_Exist() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); - contentType.AllowedContentTypes = new List - { - new ContentTypeSort(new Lazy(() => contentType.Id), 0, contentType.Alias) - }; - var parentPage = MockedContent.CreateSimpleContent(contentType); - contentTypeRepository.AddOrUpdate(contentType); - repository.AddOrUpdate(parentPage); - unitOfWork.Commit(); - - // Act - repository.AssignEntityPermission(parentPage, 'A', new int[] { 0 }); - var childPage = MockedContent.CreateSimpleContent(contentType, "child", parentPage); - repository.AddOrUpdate(childPage); - unitOfWork.Commit(); - - // Assert - var permissions = repository.GetPermissionsForEntity(childPage.Id); - Assert.AreEqual(1, permissions.Count()); - Assert.AreEqual("A", permissions.Single().AssignedPermissions.First()); - } - - } - - [Test] - public void Can_Perform_Add_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage"); - Content textpage = MockedContent.CreateSimpleContent(contentType); - - // Act - contentTypeRepository.AddOrUpdate(contentType); - repository.AddOrUpdate(textpage); - unitOfWork.Commit(); - - // Assert - Assert.That(contentType.HasIdentity, Is.True); - Assert.That(textpage.HasIdentity, Is.True); - - } - } - - [Test] - public void Can_Perform_Add_With_Default_Template() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - TemplateRepository templateRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository, out templateRepository)) - { - var template = new Template("hello", "hello"); - templateRepository.AddOrUpdate(template); - unitOfWork.Commit(); - - ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage"); - contentType.AllowedTemplates = Enumerable.Empty(); // because CreateSimple... assigns one - contentType.SetDefaultTemplate(template); - Content textpage = MockedContent.CreateSimpleContent(contentType); - - // Act - - contentTypeRepository.AddOrUpdate(contentType); - repository.AddOrUpdate(textpage); - unitOfWork.Commit(); - - // Assert - Assert.That(textpage.Template, Is.Not.Null); - Assert.That(textpage.Template, Is.EqualTo(contentType.DefaultTemplate)); - } - } - - //Covers issue U4-2791 and U4-2607 - [Test] - public void Can_Save_Content_With_AtSign_In_Name_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); - contentTypeRepository.AddOrUpdate(contentType); - unitOfWork.Commit(); - - var textpage = MockedContent.CreateSimpleContent(contentType, "test@umbraco.org", -1); - var anotherTextpage = MockedContent.CreateSimpleContent(contentType, "@lightgiants", -1); - - // Act - - repository.AddOrUpdate(textpage); - repository.AddOrUpdate(anotherTextpage); - unitOfWork.Commit(); - - // Assert - Assert.That(contentType.HasIdentity, Is.True); - Assert.That(textpage.HasIdentity, Is.True); - - var content = repository.Get(textpage.Id); - Assert.That(content.Name, Is.EqualTo(textpage.Name)); - - var content2 = repository.Get(anotherTextpage.Id); - Assert.That(content2.Name, Is.EqualTo(anotherTextpage.Name)); - } - } - - [Test] - public void Can_Perform_Multiple_Adds_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); - Content textpage = MockedContent.CreateSimpleContent(contentType); - - // Act - contentTypeRepository.AddOrUpdate(contentType); - repository.AddOrUpdate(textpage); - unitOfWork.Commit(); - - Content subpage = MockedContent.CreateSimpleContent(contentType, "Text Page 1", textpage.Id); - repository.AddOrUpdate(subpage); - unitOfWork.Commit(); - - // Assert - Assert.That(contentType.HasIdentity, Is.True); - Assert.That(textpage.HasIdentity, Is.True); - Assert.That(subpage.HasIdentity, Is.True); - Assert.That(textpage.Id, Is.EqualTo(subpage.ParentId)); - } - - } - - - [Test] - public void Can_Verify_Fresh_Entity_Is_Not_Dirty() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var content = repository.Get(NodeDto.NodeIdSeed + 3); - bool dirty = ((Content)content).IsDirty(); - - // Assert - Assert.That(dirty, Is.False); - } - } - - [Test] - public void Can_Perform_Update_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var content = repository.Get(NodeDto.NodeIdSeed + 2); - content.Name = "About 2"; - repository.AddOrUpdate(content); - unitOfWork.Commit(); - var updatedContent = repository.Get(NodeDto.NodeIdSeed + 2); - - // Assert - Assert.That(updatedContent.Id, Is.EqualTo(content.Id)); - Assert.That(updatedContent.Name, Is.EqualTo(content.Name)); - } - - } - - [Test] - public void Can_Update_With_Null_Template() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var content = repository.Get(NodeDto.NodeIdSeed + 2); - content.Template = null; - repository.AddOrUpdate(content); - unitOfWork.Commit(); - var updatedContent = repository.Get(NodeDto.NodeIdSeed + 2); - - // Assert - Assert.That(updatedContent.Template, Is.Null); - } - - } - - [Test] - public void Can_Perform_Delete_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - var contentType = contentTypeRepository.Get(NodeDto.NodeIdSeed); - var content = new Content("Textpage 2 Child Node", NodeDto.NodeIdSeed + 3, contentType); - content.CreatorId = 0; - content.WriterId = 0; - - // Act - repository.AddOrUpdate(content); - unitOfWork.Commit(); - var id = content.Id; - - repository.Delete(content); - unitOfWork.Commit(); - - var content1 = repository.Get(id); - - // Assert - Assert.That(content1, Is.Null); - } - } - - [Test] - public void Can_Perform_Get_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var content = repository.Get(NodeDto.NodeIdSeed + 3); - - // Assert - Assert.That(content.Id, Is.EqualTo(NodeDto.NodeIdSeed + 3)); - Assert.That(content.CreateDate, Is.GreaterThan(DateTime.MinValue)); - Assert.That(content.UpdateDate, Is.GreaterThan(DateTime.MinValue)); - Assert.That(content.ParentId, Is.Not.EqualTo(0)); - Assert.That(content.Name, Is.EqualTo("Text Page 2")); - //Assert.That(content.SortOrder, Is.EqualTo(1)); - Assert.That(content.Version, Is.Not.EqualTo(Guid.Empty)); - Assert.That(content.ContentTypeId, Is.EqualTo(NodeDto.NodeIdSeed)); - Assert.That(content.Path, Is.Not.Empty); - Assert.That(content.Properties.Any(), Is.True); - } - } - - [Test] - public void Can_Perform_GetByQuery_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - var result = repository.GetByQuery(query); - - // Assert - Assert.That(result.Count(), Is.GreaterThanOrEqualTo(2)); - } - } - - [Test] - public void Can_Perform_Get_All_With_Many_Version() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - var result = repository.GetAll().ToArray(); - foreach (var content in result) - { - content.ChangePublishedState(PublishedState.Saved); - repository.AddOrUpdate(content); - } - unitOfWork.Commit(); - foreach (var content in result) - { - content.ChangePublishedState(PublishedState.Published); - repository.AddOrUpdate(content); - } - unitOfWork.Commit(); - - //re-get - - var result2 = repository.GetAll().ToArray(); - - Assert.AreEqual(result.Count(), result2.Count()); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_ForFirstPage_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); - - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_ForSecondPage_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); - - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Text Page 2")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_WithSinglePage_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); - - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(2)); - Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_WithDescendingOrder_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Descending, orderBySystemField: true); - - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Text Page 2")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingSome_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, "Page 2"); - - // Assert - Assert.That(totalRecords, Is.EqualTo(1)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Text Page 2")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingAll_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, "Page"); - - // Assert - Assert.That(totalRecords, Is.EqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); - } - } - - [Test] - public void Can_Perform_GetAll_By_Param_Ids_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var contents = repository.GetAll(NodeDto.NodeIdSeed + 2, NodeDto.NodeIdSeed + 3); - - // Assert - Assert.That(contents, Is.Not.Null); - Assert.That(contents.Any(), Is.True); - Assert.That(contents.Count(), Is.EqualTo(2)); - } - } - - [Test] - public void Can_Perform_GetAll_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var contents = repository.GetAll(); - - // Assert - Assert.That(contents, Is.Not.Null); - Assert.That(contents.Any(), Is.True); - Assert.That(contents.Count(), Is.GreaterThanOrEqualTo(4)); - } - - - } - - [Test] - public void Can_Perform_Exists_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var exists = repository.Exists(NodeDto.NodeIdSeed + 1); - - // Assert - Assert.That(exists, Is.True); - } - - - } - - [Test] - public void Can_Perform_Count_On_ContentRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - int level = 2; - var query = Query.Builder.Where(x => x.Level == level); - var result = repository.Count(query); - - // Assert - Assert.That(result, Is.GreaterThanOrEqualTo(2)); - } - } - - [Test] - public void Can_Verify_Keys_Set() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var textpage = repository.Get(NodeDto.NodeIdSeed + 1); - var subpage = repository.Get(NodeDto.NodeIdSeed + 2); - var trashed = repository.Get(NodeDto.NodeIdSeed + 4); - - // Assert - Assert.That(textpage.Key.ToString().ToUpper(), Is.EqualTo("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0")); - Assert.That(subpage.Key.ToString().ToUpper(), Is.EqualTo("FF11402B-7E53-4654-81A7-462AC2108059")); - Assert.That(trashed.Key, Is.Not.EqualTo(Guid.Empty)); - } - } - - [Test] - public void Can_Get_Content_By_Guid_Key() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - ContentTypeRepository contentTypeRepository; - using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Key == new Guid("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0")); - var content = repository.GetByQuery(query).SingleOrDefault(); - - // Assert - Assert.That(content, Is.Not.Null); - Assert.That(content.Id, Is.EqualTo(NodeDto.NodeIdSeed + 1)); - } - - } - - public void CreateTestData() - { - //Create and Save ContentType "umbTextpage" -> (NodeDto.NodeIdSeed) - ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage", "Textpage"); - contentType.Key = new Guid("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"); - ServiceContext.ContentTypeService.Save(contentType); - - //Create and Save Content "Homepage" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 1) - Content textpage = MockedContent.CreateSimpleContent(contentType); - textpage.Key = new Guid("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0"); - ServiceContext.ContentService.Save(textpage, 0); - - //Create and Save Content "Text Page 1" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 2) - Content subpage = MockedContent.CreateSimpleContent(contentType, "Text Page 1", textpage.Id); - subpage.Key = new Guid("FF11402B-7E53-4654-81A7-462AC2108059"); - ServiceContext.ContentService.Save(subpage, 0); - - //Create and Save Content "Text Page 1" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 3) - Content subpage2 = MockedContent.CreateSimpleContent(contentType, "Text Page 2", textpage.Id); - ServiceContext.ContentService.Save(subpage2, 0); - - //Create and Save Content "Text Page Deleted" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 4) - Content trashed = MockedContent.CreateSimpleContent(contentType, "Text Page Deleted", -20); - trashed.Trashed = true; - ServiceContext.ContentService.Save(trashed, 0); - } - } + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] + [TestFixture] + public class ContentRepositoryTest : BaseDatabaseFactoryTest + { + [SetUp] + public override void Initialize() + { + base.Initialize(); + + CreateTestData(); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + private ContentRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository) + { + TemplateRepository tr; + return CreateRepository(unitOfWork, out contentTypeRepository, out tr); + } + + private ContentRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository, out TemplateRepository templateRepository) + { + templateRepository = new TemplateRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, Mock.Of(), Mock.Of(), Mock.Of()); + var tagRepository = new TagRepository(unitOfWork, CacheHelper, Logger, SqlSyntax); + contentTypeRepository = new ContentTypeRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, templateRepository); + var repository = new ContentRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, contentTypeRepository, templateRepository, tagRepository, Mock.Of()); + return repository; + } + + [Test] + public void Rebuild_Xml_Structures_With_Non_Latest_Version() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + var contentType1 = MockedContentTypes.CreateSimpleContentType("Textpage1", "Textpage1"); + contentTypeRepository.AddOrUpdate(contentType1); + + var allCreated = new List(); + + //create 100 non published + for (var i = 0; i < 100; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType1); + repository.AddOrUpdate(c1); + allCreated.Add(c1); + } + //create 100 published + for (var i = 0; i < 100; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType1); + c1.ChangePublishedState(PublishedState.Published); + repository.AddOrUpdate(c1); + allCreated.Add(c1); + } + unitOfWork.Commit(); + + //now create some versions of this content - this shouldn't affect the xml structures saved + for (int i = 0; i < allCreated.Count; i++) + { + allCreated[i].Name = "blah" + i; + //IMPORTANT testing note here: We need to changed the published state here so that + // it doesn't automatically think this is simply publishing again - this forces the latest + // version to be Saved and not published + allCreated[i].ChangePublishedState(PublishedState.Saved); + repository.AddOrUpdate(allCreated[i]); + } + unitOfWork.Commit(); + + //delete all xml + unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); + Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + + repository.RebuildXmlStructures(media => new XElement("test"), 10); + + Assert.AreEqual(100, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + } + } + + [Test] + public void Rebuild_All_Xml_Structures() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + + var contentType1 = MockedContentTypes.CreateSimpleContentType("Textpage1", "Textpage1"); + contentTypeRepository.AddOrUpdate(contentType1); + var allCreated = new List(); + + for (var i = 0; i < 100; i++) + { + //These will be non-published so shouldn't show up + var c1 = MockedContent.CreateSimpleContent(contentType1); + repository.AddOrUpdate(c1); + allCreated.Add(c1); + } + for (var i = 0; i < 100; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType1); + c1.ChangePublishedState(PublishedState.Published); + repository.AddOrUpdate(c1); + allCreated.Add(c1); + } + unitOfWork.Commit(); + + //now create some versions of this content - this shouldn't affect the xml structures saved + for (int i = 0; i < allCreated.Count; i++) + { + allCreated[i].Name = "blah" + i; + repository.AddOrUpdate(allCreated[i]); + } + unitOfWork.Commit(); + + //delete all xml + unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); + Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + + repository.RebuildXmlStructures(media => new XElement("test"), 10); + + Assert.AreEqual(100, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + } + } + + [Test] + public void Rebuild_All_Xml_Structures_For_Content_Type() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + var contentType1 = MockedContentTypes.CreateSimpleContentType("Textpage1", "Textpage1"); + var contentType2 = MockedContentTypes.CreateSimpleContentType("Textpage2", "Textpage2"); + var contentType3 = MockedContentTypes.CreateSimpleContentType("Textpage3", "Textpage3"); + contentTypeRepository.AddOrUpdate(contentType1); + contentTypeRepository.AddOrUpdate(contentType2); + contentTypeRepository.AddOrUpdate(contentType3); + + var allCreated = new List(); + + for (var i = 0; i < 30; i++) + { + //These will be non-published so shouldn't show up + var c1 = MockedContent.CreateSimpleContent(contentType1); + repository.AddOrUpdate(c1); + allCreated.Add(c1); + } + for (var i = 0; i < 30; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType1); + c1.ChangePublishedState(PublishedState.Published); + repository.AddOrUpdate(c1); + allCreated.Add(c1); + } + for (var i = 0; i < 30; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType2); + c1.ChangePublishedState(PublishedState.Published); + repository.AddOrUpdate(c1); + allCreated.Add(c1); + } + for (var i = 0; i < 30; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType3); + c1.ChangePublishedState(PublishedState.Published); + repository.AddOrUpdate(c1); + allCreated.Add(c1); + } + unitOfWork.Commit(); + + //now create some versions of this content - this shouldn't affect the xml structures saved + for (int i = 0; i < allCreated.Count; i++) + { + allCreated[i].Name = "blah" + i; + repository.AddOrUpdate(allCreated[i]); + } + unitOfWork.Commit(); + + //delete all xml + unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); + Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + + repository.RebuildXmlStructures(media => new XElement("test"), 10, contentTypeIds: new[] { contentType1.Id, contentType2.Id }); + + Assert.AreEqual(60, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + } + } + + [Test] + public void Ensures_Permissions_Are_Set_If_Parent_Entity_Permissions_Exist() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); + contentType.AllowedContentTypes = new List + { + new ContentTypeSort(new Lazy(() => contentType.Id), 0, contentType.Alias) + }; + var parentPage = MockedContent.CreateSimpleContent(contentType); + contentTypeRepository.AddOrUpdate(contentType); + repository.AddOrUpdate(parentPage); + unitOfWork.Commit(); + + // Act + repository.AssignEntityPermission(parentPage, 'A', new int[] { 0 }); + var childPage = MockedContent.CreateSimpleContent(contentType, "child", parentPage); + repository.AddOrUpdate(childPage); + unitOfWork.Commit(); + + // Assert + var permissions = repository.GetPermissionsForEntity(childPage.Id); + Assert.AreEqual(1, permissions.Count()); + Assert.AreEqual("A", permissions.Single().AssignedPermissions.First()); + } + + } + + [Test] + public void Can_Perform_Add_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage"); + Content textpage = MockedContent.CreateSimpleContent(contentType); + + // Act + contentTypeRepository.AddOrUpdate(contentType); + repository.AddOrUpdate(textpage); + unitOfWork.Commit(); + + // Assert + Assert.That(contentType.HasIdentity, Is.True); + Assert.That(textpage.HasIdentity, Is.True); + + } + } + + [Test] + public void Can_Perform_Add_With_Default_Template() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + TemplateRepository templateRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository, out templateRepository)) + { + var template = new Template("hello", "hello"); + templateRepository.AddOrUpdate(template); + unitOfWork.Commit(); + + ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage"); + contentType.AllowedTemplates = Enumerable.Empty(); // because CreateSimple... assigns one + contentType.SetDefaultTemplate(template); + Content textpage = MockedContent.CreateSimpleContent(contentType); + + // Act + + contentTypeRepository.AddOrUpdate(contentType); + repository.AddOrUpdate(textpage); + unitOfWork.Commit(); + + // Assert + Assert.That(textpage.Template, Is.Not.Null); + Assert.That(textpage.Template, Is.EqualTo(contentType.DefaultTemplate)); + } + } + + //Covers issue U4-2791 and U4-2607 + [Test] + public void Can_Save_Content_With_AtSign_In_Name_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); + contentTypeRepository.AddOrUpdate(contentType); + unitOfWork.Commit(); + + var textpage = MockedContent.CreateSimpleContent(contentType, "test@umbraco.org", -1); + var anotherTextpage = MockedContent.CreateSimpleContent(contentType, "@lightgiants", -1); + + // Act + + repository.AddOrUpdate(textpage); + repository.AddOrUpdate(anotherTextpage); + unitOfWork.Commit(); + + // Assert + Assert.That(contentType.HasIdentity, Is.True); + Assert.That(textpage.HasIdentity, Is.True); + + var content = repository.Get(textpage.Id); + Assert.That(content.Name, Is.EqualTo(textpage.Name)); + + var content2 = repository.Get(anotherTextpage.Id); + Assert.That(content2.Name, Is.EqualTo(anotherTextpage.Name)); + } + } + + [Test] + public void Can_Perform_Multiple_Adds_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); + Content textpage = MockedContent.CreateSimpleContent(contentType); + + // Act + contentTypeRepository.AddOrUpdate(contentType); + repository.AddOrUpdate(textpage); + unitOfWork.Commit(); + + Content subpage = MockedContent.CreateSimpleContent(contentType, "Text Page 1", textpage.Id); + repository.AddOrUpdate(subpage); + unitOfWork.Commit(); + + // Assert + Assert.That(contentType.HasIdentity, Is.True); + Assert.That(textpage.HasIdentity, Is.True); + Assert.That(subpage.HasIdentity, Is.True); + Assert.That(textpage.Id, Is.EqualTo(subpage.ParentId)); + } + + } + + + [Test] + public void Can_Verify_Fresh_Entity_Is_Not_Dirty() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var content = repository.Get(NodeDto.NodeIdSeed + 3); + bool dirty = ((Content)content).IsDirty(); + + // Assert + Assert.That(dirty, Is.False); + } + } + + [Test] + public void Can_Perform_Update_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var content = repository.Get(NodeDto.NodeIdSeed + 2); + content.Name = "About 2"; + repository.AddOrUpdate(content); + unitOfWork.Commit(); + var updatedContent = repository.Get(NodeDto.NodeIdSeed + 2); + + // Assert + Assert.That(updatedContent.Id, Is.EqualTo(content.Id)); + Assert.That(updatedContent.Name, Is.EqualTo(content.Name)); + } + + } + + [Test] + public void Can_Update_With_Null_Template() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var content = repository.Get(NodeDto.NodeIdSeed + 2); + content.Template = null; + repository.AddOrUpdate(content); + unitOfWork.Commit(); + var updatedContent = repository.Get(NodeDto.NodeIdSeed + 2); + + // Assert + Assert.That(updatedContent.Template, Is.Null); + } + + } + + [Test] + public void Can_Perform_Delete_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + var contentType = contentTypeRepository.Get(NodeDto.NodeIdSeed); + var content = new Content("Textpage 2 Child Node", NodeDto.NodeIdSeed + 3, contentType); + content.CreatorId = 0; + content.WriterId = 0; + + // Act + repository.AddOrUpdate(content); + unitOfWork.Commit(); + var id = content.Id; + + repository.Delete(content); + unitOfWork.Commit(); + + var content1 = repository.Get(id); + + // Assert + Assert.That(content1, Is.Null); + } + } + + [Test] + public void Can_Perform_Get_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var content = repository.Get(NodeDto.NodeIdSeed + 3); + + // Assert + Assert.That(content.Id, Is.EqualTo(NodeDto.NodeIdSeed + 3)); + Assert.That(content.CreateDate, Is.GreaterThan(DateTime.MinValue)); + Assert.That(content.UpdateDate, Is.GreaterThan(DateTime.MinValue)); + Assert.That(content.ParentId, Is.Not.EqualTo(0)); + Assert.That(content.Name, Is.EqualTo("Text Page 2")); + //Assert.That(content.SortOrder, Is.EqualTo(1)); + Assert.That(content.Version, Is.Not.EqualTo(Guid.Empty)); + Assert.That(content.ContentTypeId, Is.EqualTo(NodeDto.NodeIdSeed)); + Assert.That(content.Path, Is.Not.Empty); + Assert.That(content.Properties.Any(), Is.True); + } + } + + [Test] + public void Can_Perform_GetByQuery_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + var result = repository.GetByQuery(query); + + // Assert + Assert.That(result.Count(), Is.GreaterThanOrEqualTo(2)); + } + } + + [Test] + public void Can_Perform_Get_All_With_Many_Version() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + var result = repository.GetAll().ToArray(); + foreach (var content in result) + { + content.ChangePublishedState(PublishedState.Saved); + repository.AddOrUpdate(content); + } + unitOfWork.Commit(); + foreach (var content in result) + { + content.ChangePublishedState(PublishedState.Published); + repository.AddOrUpdate(content); + } + unitOfWork.Commit(); + + //re-get + + var result2 = repository.GetAll().ToArray(); + + Assert.AreEqual(result.Count(), result2.Count()); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_ForFirstPage_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_ForSecondPage_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 2")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithSinglePage_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(2)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithDescendingOrder_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Descending, orderBySystemField: true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 2")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingSome_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, "Page 2"); + + // Assert + Assert.That(totalRecords, Is.EqualTo(1)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 2")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingAll_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, "Page"); + + // Assert + Assert.That(totalRecords, Is.EqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); + } + } + + [Test] + public void Can_Perform_GetAll_By_Param_Ids_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var contents = repository.GetAll(NodeDto.NodeIdSeed + 2, NodeDto.NodeIdSeed + 3); + + // Assert + Assert.That(contents, Is.Not.Null); + Assert.That(contents.Any(), Is.True); + Assert.That(contents.Count(), Is.EqualTo(2)); + } + } + + [Test] + public void Can_Perform_GetAll_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var contents = repository.GetAll(); + + // Assert + Assert.That(contents, Is.Not.Null); + Assert.That(contents.Any(), Is.True); + Assert.That(contents.Count(), Is.GreaterThanOrEqualTo(4)); + } + + + } + + [Test] + public void Can_Perform_Exists_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var exists = repository.Exists(NodeDto.NodeIdSeed + 1); + + // Assert + Assert.That(exists, Is.True); + } + + + } + + [Test] + public void Can_Perform_Count_On_ContentRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + int level = 2; + var query = Query.Builder.Where(x => x.Level == level); + var result = repository.Count(query); + + // Assert + Assert.That(result, Is.GreaterThanOrEqualTo(2)); + } + } + + [Test] + public void Can_Verify_Keys_Set() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var textpage = repository.Get(NodeDto.NodeIdSeed + 1); + var subpage = repository.Get(NodeDto.NodeIdSeed + 2); + var trashed = repository.Get(NodeDto.NodeIdSeed + 4); + + // Assert + Assert.That(textpage.Key.ToString().ToUpper(), Is.EqualTo("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0")); + Assert.That(subpage.Key.ToString().ToUpper(), Is.EqualTo("FF11402B-7E53-4654-81A7-462AC2108059")); + Assert.That(trashed.Key, Is.Not.EqualTo(Guid.Empty)); + } + } + + [Test] + public void Can_Get_Content_By_Guid_Key() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Key == new Guid("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0")); + var content = repository.GetByQuery(query).SingleOrDefault(); + + // Assert + Assert.That(content, Is.Not.Null); + Assert.That(content.Id, Is.EqualTo(NodeDto.NodeIdSeed + 1)); + } + + } + + public void CreateTestData() + { + //Create and Save ContentType "umbTextpage" -> (NodeDto.NodeIdSeed) + ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage", "Textpage"); + contentType.Key = new Guid("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"); + ServiceContext.ContentTypeService.Save(contentType); + + //Create and Save Content "Homepage" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 1) + Content textpage = MockedContent.CreateSimpleContent(contentType); + textpage.Key = new Guid("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0"); + ServiceContext.ContentService.Save(textpage, 0); + + //Create and Save Content "Text Page 1" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 2) + Content subpage = MockedContent.CreateSimpleContent(contentType, "Text Page 1", textpage.Id); + subpage.Key = new Guid("FF11402B-7E53-4654-81A7-462AC2108059"); + ServiceContext.ContentService.Save(subpage, 0); + + //Create and Save Content "Text Page 1" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 3) + Content subpage2 = MockedContent.CreateSimpleContent(contentType, "Text Page 2", textpage.Id); + ServiceContext.ContentService.Save(subpage2, 0); + + //Create and Save Content "Text Page Deleted" based on "umbTextpage" -> (NodeDto.NodeIdSeed + 4) + Content trashed = MockedContent.CreateSimpleContent(contentType, "Text Page Deleted", -20); + trashed.Trashed = true; + ServiceContext.ContentService.Save(trashed, 0); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index 4ca987fe26..32258a9961 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -17,547 +17,547 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Tests.Persistence.Repositories { - [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] - [TestFixture] - public class MediaRepositoryTest : BaseDatabaseFactoryTest - { - public MediaRepositoryTest() - { - } - - [SetUp] - public override void Initialize() - { - base.Initialize(); - - CreateTestData(); - } - - private MediaRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out MediaTypeRepository mediaTypeRepository) - { - mediaTypeRepository = new MediaTypeRepository(unitOfWork, CacheHelper, Mock.Of(), SqlSyntax); - var tagRepository = new TagRepository(unitOfWork, CacheHelper, Mock.Of(), SqlSyntax); - var repository = new MediaRepository(unitOfWork, CacheHelper, Mock.Of(), SqlSyntax, mediaTypeRepository, tagRepository, Mock.Of()); - return repository; - } - - [Test] - public void Rebuild_All_Xml_Structures() - { - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - var mediaType = mediaTypeRepository.Get(1032); - - for (var i = 0; i < 100; i++) - { - var image = MockedMedia.CreateMediaImage(mediaType, -1); - repository.AddOrUpdate(image); - } - unitOfWork.Commit(); - - //delete all xml - unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); - Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - - repository.RebuildXmlStructures(media => new XElement("test"), 10); - - Assert.AreEqual(103, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - } - } - - [Test] - public void Rebuild_All_Xml_Structures_For_Content_Type() - { - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - var imageMediaType = mediaTypeRepository.Get(1032); - var fileMediaType = mediaTypeRepository.Get(1033); - var folderMediaType = mediaTypeRepository.Get(1031); - - for (var i = 0; i < 30; i++) - { - var image = MockedMedia.CreateMediaImage(imageMediaType, -1); - repository.AddOrUpdate(image); - } - for (var i = 0; i < 30; i++) - { - var file = MockedMedia.CreateMediaFile(fileMediaType, -1); - repository.AddOrUpdate(file); - } - for (var i = 0; i < 30; i++) - { - var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); - repository.AddOrUpdate(folder); - } - unitOfWork.Commit(); - - //delete all xml - unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); - Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - - repository.RebuildXmlStructures(media => new XElement("test"), 10, contentTypeIds: new[] { 1032, 1033 }); - - Assert.AreEqual(62, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - } - } - - [Test] - public void Can_Perform_Add_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - var mediaType = mediaTypeRepository.Get(1032); - var image = MockedMedia.CreateMediaImage(mediaType, -1); - - // Act - mediaTypeRepository.AddOrUpdate(mediaType); - repository.AddOrUpdate(image); - unitOfWork.Commit(); - - // Assert - Assert.That(mediaType.HasIdentity, Is.True); - Assert.That(image.HasIdentity, Is.True); - } - } - - [Test] - public void Can_Perform_Multiple_Adds_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - var mediaType = mediaTypeRepository.Get(1032); - var file = MockedMedia.CreateMediaFile(mediaType, -1); - - // Act - repository.AddOrUpdate(file); - unitOfWork.Commit(); - - var image = MockedMedia.CreateMediaImage(mediaType, -1); - repository.AddOrUpdate(image); - unitOfWork.Commit(); - - // Assert - Assert.That(file.HasIdentity, Is.True); - Assert.That(image.HasIdentity, Is.True); - Assert.That(file.Name, Is.EqualTo("Test File")); - Assert.That(image.Name, Is.EqualTo("Test Image")); - Assert.That(file.ContentTypeId, Is.EqualTo(mediaType.Id)); - Assert.That(image.ContentTypeId, Is.EqualTo(mediaType.Id)); - } - } - - [Test] - public void Can_Perform_Multiple_Adds_On_MediaRepository_With_RepositoryResolver() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - var mediaType = mediaTypeRepository.Get(1032); - var file = MockedMedia.CreateMediaFile(mediaType, -1); - - // Act - repository.AddOrUpdate(file); - unitOfWork.Commit(); - - var image = MockedMedia.CreateMediaImage(mediaType, -1); - repository.AddOrUpdate(image); - unitOfWork.Commit(); - - // Assert - Assert.That(file.HasIdentity, Is.True); - Assert.That(image.HasIdentity, Is.True); - Assert.That(file.Name, Is.EqualTo("Test File")); - Assert.That(image.Name, Is.EqualTo("Test Image")); - Assert.That(file.ContentTypeId, Is.EqualTo(mediaType.Id)); - Assert.That(image.ContentTypeId, Is.EqualTo(mediaType.Id)); - } - } - - [Test] - public void Can_Verify_Fresh_Entity_Is_Not_Dirty() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - // Act - var media = repository.Get(NodeDto.NodeIdSeed + 1); - bool dirty = ((ICanBeDirty)media).IsDirty(); - - // Assert - Assert.That(dirty, Is.False); - } - } - - [Test] - public void Can_Perform_Update_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - // Act - var content = repository.Get(NodeDto.NodeIdSeed + 2); - content.Name = "Test File Updated"; - repository.AddOrUpdate(content); - unitOfWork.Commit(); - - var updatedContent = repository.Get(NodeDto.NodeIdSeed + 2); - - // Assert - Assert.That(updatedContent.Id, Is.EqualTo(content.Id)); - Assert.That(updatedContent.Name, Is.EqualTo(content.Name)); - } - } - - [Test] - public void Can_Perform_Delete_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - // Act - var media = repository.Get(NodeDto.NodeIdSeed + 2); - repository.Delete(media); - unitOfWork.Commit(); - - var deleted = repository.Get(NodeDto.NodeIdSeed + 2); - var exists = repository.Exists(NodeDto.NodeIdSeed + 2); - - // Assert - Assert.That(deleted, Is.Null); - Assert.That(exists, Is.False); - } - } - - [Test] - public void Can_Perform_Get_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - // Act - var media = repository.Get(NodeDto.NodeIdSeed + 1); - - // Assert - Assert.That(media.Id, Is.EqualTo(NodeDto.NodeIdSeed + 1)); - Assert.That(media.CreateDate, Is.GreaterThan(DateTime.MinValue)); - Assert.That(media.UpdateDate, Is.GreaterThan(DateTime.MinValue)); - Assert.That(media.ParentId, Is.Not.EqualTo(0)); - Assert.That(media.Name, Is.EqualTo("Test Image")); - Assert.That(media.SortOrder, Is.EqualTo(0)); - Assert.That(media.Version, Is.Not.EqualTo(Guid.Empty)); - Assert.That(media.ContentTypeId, Is.EqualTo(1032)); - Assert.That(media.Path, Is.Not.Empty); - Assert.That(media.Properties.Any(), Is.True); - } - } - - [Test] - public void Can_Perform_GetByQuery_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - var result = repository.GetByQuery(query); - - // Assert - Assert.That(result.Count(), Is.GreaterThanOrEqualTo(2)); //There should be two entities on level 2: File and Media - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_ForFirstPage_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, orderBySystemField: true); - - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Test Image")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_ForSecondPage_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "SortOrder", Direction.Ascending, orderBySystemField: true); - - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Test File")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_WithSinglePage_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "SortOrder", Direction.Ascending, orderBySystemField: true); - - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(2)); - Assert.That(result.First().Name, Is.EqualTo("Test Image")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_WithDescendingOrder_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Descending, orderBySystemField: true); - - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Test File")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_WitAlternateOrder_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); - - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Test File")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingSome_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true, "File"); - - // Assert - Assert.That(totalRecords, Is.EqualTo(1)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Test File")); - } - } - - [Test] - public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingAll_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - // Act - var query = Query.Builder.Where(x => x.Level == 2); - long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true, "Test"); - - // Assert - Assert.That(totalRecords, Is.EqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Test Image")); - } - } - - [Test] - public void Can_Perform_GetAll_By_Param_Ids_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - // Act - var medias = repository.GetAll(NodeDto.NodeIdSeed + 1, NodeDto.NodeIdSeed + 2); - - // Assert - Assert.That(medias, Is.Not.Null); - Assert.That(medias.Any(), Is.True); - Assert.That(medias.Count(), Is.EqualTo(2)); - } - } - - [Test] - public void Can_Perform_GetAll_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - // Act - var medias = repository.GetAll(); - - // Assert - Assert.That(medias, Is.Not.Null); - Assert.That(medias.Any(), Is.True); - Assert.That(medias.Count(), Is.GreaterThanOrEqualTo(3)); - } - } - - [Test] - public void Can_Perform_Exists_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - // Act - var exists = repository.Exists(NodeDto.NodeIdSeed + 1); - var existsToo = repository.Exists(NodeDto.NodeIdSeed + 1); - var doesntExists = repository.Exists(NodeDto.NodeIdSeed + 5); - - // Assert - Assert.That(exists, Is.True); - Assert.That(existsToo, Is.True); - Assert.That(doesntExists, Is.False); - } - } - - [Test] - public void Can_Perform_Count_On_MediaRepository() - { - // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); - var unitOfWork = provider.GetUnitOfWork(); - MediaTypeRepository mediaTypeRepository; - using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) - { - - // Act - int level = 2; - var query = Query.Builder.Where(x => x.Level == level); - var result = repository.Count(query); - - // Assert - Assert.That(result, Is.GreaterThanOrEqualTo(2)); - } - } - - [TearDown] - public override void TearDown() - { - base.TearDown(); - } - - public void CreateTestData() - { - //Create and Save folder-Media -> (NodeDto.NodeIdSeed) - var folderMediaType = ServiceContext.ContentTypeService.GetMediaType(1031); - var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); - ServiceContext.MediaService.Save(folder, 0); - - //Create and Save image-Media -> (NodeDto.NodeIdSeed + 1) - var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); - var image = MockedMedia.CreateMediaImage(imageMediaType, folder.Id); - ServiceContext.MediaService.Save(image, 0); - - //Create and Save file-Media -> (NodeDto.NodeIdSeed + 2) - var fileMediaType = ServiceContext.ContentTypeService.GetMediaType(1033); - var file = MockedMedia.CreateMediaFile(fileMediaType, folder.Id); - ServiceContext.MediaService.Save(file, 0); - } - } + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] + [TestFixture] + public class MediaRepositoryTest : BaseDatabaseFactoryTest + { + public MediaRepositoryTest() + { + } + + [SetUp] + public override void Initialize() + { + base.Initialize(); + + CreateTestData(); + } + + private MediaRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out MediaTypeRepository mediaTypeRepository) + { + mediaTypeRepository = new MediaTypeRepository(unitOfWork, CacheHelper, Mock.Of(), SqlSyntax); + var tagRepository = new TagRepository(unitOfWork, CacheHelper, Mock.Of(), SqlSyntax); + var repository = new MediaRepository(unitOfWork, CacheHelper, Mock.Of(), SqlSyntax, mediaTypeRepository, tagRepository, Mock.Of()); + return repository; + } + + [Test] + public void Rebuild_All_Xml_Structures() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + var mediaType = mediaTypeRepository.Get(1032); + + for (var i = 0; i < 100; i++) + { + var image = MockedMedia.CreateMediaImage(mediaType, -1); + repository.AddOrUpdate(image); + } + unitOfWork.Commit(); + + //delete all xml + unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); + Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + + repository.RebuildXmlStructures(media => new XElement("test"), 10); + + Assert.AreEqual(103, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + } + } + + [Test] + public void Rebuild_All_Xml_Structures_For_Content_Type() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + var imageMediaType = mediaTypeRepository.Get(1032); + var fileMediaType = mediaTypeRepository.Get(1033); + var folderMediaType = mediaTypeRepository.Get(1031); + + for (var i = 0; i < 30; i++) + { + var image = MockedMedia.CreateMediaImage(imageMediaType, -1); + repository.AddOrUpdate(image); + } + for (var i = 0; i < 30; i++) + { + var file = MockedMedia.CreateMediaFile(fileMediaType, -1); + repository.AddOrUpdate(file); + } + for (var i = 0; i < 30; i++) + { + var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); + repository.AddOrUpdate(folder); + } + unitOfWork.Commit(); + + //delete all xml + unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); + Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + + repository.RebuildXmlStructures(media => new XElement("test"), 10, contentTypeIds: new[] { 1032, 1033 }); + + Assert.AreEqual(62, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); + } + } + + [Test] + public void Can_Perform_Add_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + var mediaType = mediaTypeRepository.Get(1032); + var image = MockedMedia.CreateMediaImage(mediaType, -1); + + // Act + mediaTypeRepository.AddOrUpdate(mediaType); + repository.AddOrUpdate(image); + unitOfWork.Commit(); + + // Assert + Assert.That(mediaType.HasIdentity, Is.True); + Assert.That(image.HasIdentity, Is.True); + } + } + + [Test] + public void Can_Perform_Multiple_Adds_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + var mediaType = mediaTypeRepository.Get(1032); + var file = MockedMedia.CreateMediaFile(mediaType, -1); + + // Act + repository.AddOrUpdate(file); + unitOfWork.Commit(); + + var image = MockedMedia.CreateMediaImage(mediaType, -1); + repository.AddOrUpdate(image); + unitOfWork.Commit(); + + // Assert + Assert.That(file.HasIdentity, Is.True); + Assert.That(image.HasIdentity, Is.True); + Assert.That(file.Name, Is.EqualTo("Test File")); + Assert.That(image.Name, Is.EqualTo("Test Image")); + Assert.That(file.ContentTypeId, Is.EqualTo(mediaType.Id)); + Assert.That(image.ContentTypeId, Is.EqualTo(mediaType.Id)); + } + } + + [Test] + public void Can_Perform_Multiple_Adds_On_MediaRepository_With_RepositoryResolver() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + var mediaType = mediaTypeRepository.Get(1032); + var file = MockedMedia.CreateMediaFile(mediaType, -1); + + // Act + repository.AddOrUpdate(file); + unitOfWork.Commit(); + + var image = MockedMedia.CreateMediaImage(mediaType, -1); + repository.AddOrUpdate(image); + unitOfWork.Commit(); + + // Assert + Assert.That(file.HasIdentity, Is.True); + Assert.That(image.HasIdentity, Is.True); + Assert.That(file.Name, Is.EqualTo("Test File")); + Assert.That(image.Name, Is.EqualTo("Test Image")); + Assert.That(file.ContentTypeId, Is.EqualTo(mediaType.Id)); + Assert.That(image.ContentTypeId, Is.EqualTo(mediaType.Id)); + } + } + + [Test] + public void Can_Verify_Fresh_Entity_Is_Not_Dirty() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + // Act + var media = repository.Get(NodeDto.NodeIdSeed + 1); + bool dirty = ((ICanBeDirty)media).IsDirty(); + + // Assert + Assert.That(dirty, Is.False); + } + } + + [Test] + public void Can_Perform_Update_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + // Act + var content = repository.Get(NodeDto.NodeIdSeed + 2); + content.Name = "Test File Updated"; + repository.AddOrUpdate(content); + unitOfWork.Commit(); + + var updatedContent = repository.Get(NodeDto.NodeIdSeed + 2); + + // Assert + Assert.That(updatedContent.Id, Is.EqualTo(content.Id)); + Assert.That(updatedContent.Name, Is.EqualTo(content.Name)); + } + } + + [Test] + public void Can_Perform_Delete_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + // Act + var media = repository.Get(NodeDto.NodeIdSeed + 2); + repository.Delete(media); + unitOfWork.Commit(); + + var deleted = repository.Get(NodeDto.NodeIdSeed + 2); + var exists = repository.Exists(NodeDto.NodeIdSeed + 2); + + // Assert + Assert.That(deleted, Is.Null); + Assert.That(exists, Is.False); + } + } + + [Test] + public void Can_Perform_Get_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + // Act + var media = repository.Get(NodeDto.NodeIdSeed + 1); + + // Assert + Assert.That(media.Id, Is.EqualTo(NodeDto.NodeIdSeed + 1)); + Assert.That(media.CreateDate, Is.GreaterThan(DateTime.MinValue)); + Assert.That(media.UpdateDate, Is.GreaterThan(DateTime.MinValue)); + Assert.That(media.ParentId, Is.Not.EqualTo(0)); + Assert.That(media.Name, Is.EqualTo("Test Image")); + Assert.That(media.SortOrder, Is.EqualTo(0)); + Assert.That(media.Version, Is.Not.EqualTo(Guid.Empty)); + Assert.That(media.ContentTypeId, Is.EqualTo(1032)); + Assert.That(media.Path, Is.Not.Empty); + Assert.That(media.Properties.Any(), Is.True); + } + } + + [Test] + public void Can_Perform_GetByQuery_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + var result = repository.GetByQuery(query); + + // Assert + Assert.That(result.Count(), Is.GreaterThanOrEqualTo(2)); //There should be two entities on level 2: File and Media + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_ForFirstPage_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, orderBySystemField: true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Test Image")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_ForSecondPage_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "SortOrder", Direction.Ascending, orderBySystemField: true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Test File")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithSinglePage_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "SortOrder", Direction.Ascending, orderBySystemField: true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(2)); + Assert.That(result.First().Name, Is.EqualTo("Test Image")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithDescendingOrder_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Descending, orderBySystemField: true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Test File")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WitAlternateOrder_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Test File")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingSome_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true, "File"); + + // Assert + Assert.That(totalRecords, Is.EqualTo(1)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Test File")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingAll_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + long totalRecords; + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true, "Test"); + + // Assert + Assert.That(totalRecords, Is.EqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Test Image")); + } + } + + [Test] + public void Can_Perform_GetAll_By_Param_Ids_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + // Act + var medias = repository.GetAll(NodeDto.NodeIdSeed + 1, NodeDto.NodeIdSeed + 2); + + // Assert + Assert.That(medias, Is.Not.Null); + Assert.That(medias.Any(), Is.True); + Assert.That(medias.Count(), Is.EqualTo(2)); + } + } + + [Test] + public void Can_Perform_GetAll_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + // Act + var medias = repository.GetAll(); + + // Assert + Assert.That(medias, Is.Not.Null); + Assert.That(medias.Any(), Is.True); + Assert.That(medias.Count(), Is.GreaterThanOrEqualTo(3)); + } + } + + [Test] + public void Can_Perform_Exists_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + // Act + var exists = repository.Exists(NodeDto.NodeIdSeed + 1); + var existsToo = repository.Exists(NodeDto.NodeIdSeed + 1); + var doesntExists = repository.Exists(NodeDto.NodeIdSeed + 5); + + // Assert + Assert.That(exists, Is.True); + Assert.That(existsToo, Is.True); + Assert.That(doesntExists, Is.False); + } + } + + [Test] + public void Can_Perform_Count_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + + // Act + int level = 2; + var query = Query.Builder.Where(x => x.Level == level); + var result = repository.Count(query); + + // Assert + Assert.That(result, Is.GreaterThanOrEqualTo(2)); + } + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + public void CreateTestData() + { + //Create and Save folder-Media -> (NodeDto.NodeIdSeed) + var folderMediaType = ServiceContext.ContentTypeService.GetMediaType(1031); + var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); + ServiceContext.MediaService.Save(folder, 0); + + //Create and Save image-Media -> (NodeDto.NodeIdSeed + 1) + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + var image = MockedMedia.CreateMediaImage(imageMediaType, folder.Id); + ServiceContext.MediaService.Save(image, 0); + + //Create and Save file-Media -> (NodeDto.NodeIdSeed + 2) + var fileMediaType = ServiceContext.ContentTypeService.GetMediaType(1033); + var file = MockedMedia.CreateMediaFile(fileMediaType, folder.Id); + ServiceContext.MediaService.Save(file, 0); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index 2f9ecce9b2..26f29b581b 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -20,124 +20,124 @@ using Version = Lucene.Net.Util.Version; namespace Umbraco.Tests.UmbracoExamine { - /// - /// Used internally by test classes to initialize a new index from the template - /// - internal static class IndexInitializer - { - public static UmbracoContentIndexer GetUmbracoIndexer( - Directory luceneDir, - Analyzer analyzer = null, - IDataService dataService = null, - IContentService contentService = null, - IMediaService mediaService = null, - IDataTypeService dataTypeService = null, - IMemberService memberService = null, - IUserService userService = null) - { - if (dataService == null) - { - dataService = new TestDataService(); - } - if (contentService == null) - { - contentService = Mock.Of(); - } - if (userService == null) - { - userService = Mock.Of(x => x.GetProfileById(It.IsAny()) == Mock.Of(p => p.Id == (object)0 && p.Name == "admin")); - } - if (mediaService == null) - { - long totalRecs; + /// + /// Used internally by test classes to initialize a new index from the template + /// + internal static class IndexInitializer + { + public static UmbracoContentIndexer GetUmbracoIndexer( + Directory luceneDir, + Analyzer analyzer = null, + IDataService dataService = null, + IContentService contentService = null, + IMediaService mediaService = null, + IDataTypeService dataTypeService = null, + IMemberService memberService = null, + IUserService userService = null) + { + if (dataService == null) + { + dataService = new TestDataService(); + } + if (contentService == null) + { + contentService = Mock.Of(); + } + if (userService == null) + { + userService = Mock.Of(x => x.GetProfileById(It.IsAny()) == Mock.Of(p => p.Id == (object)0 && p.Name == "admin")); + } + if (mediaService == null) + { + long totalRecs; - var allRecs = dataService.MediaService.GetLatestMediaByXpath("//node") - .Root - .Elements() - .Select(x => Mock.Of( - m => - m.Id == (int)x.Attribute("id") && - m.ParentId == (int)x.Attribute("parentID") && - m.Level == (int)x.Attribute("level") && - m.CreatorId == 0 && - m.SortOrder == (int)x.Attribute("sortOrder") && - m.CreateDate == (DateTime)x.Attribute("createDate") && - m.UpdateDate == (DateTime)x.Attribute("updateDate") && - m.Name == (string)x.Attribute("nodeName") && - m.Path == (string)x.Attribute("path") && - m.Properties == new PropertyCollection() && - m.ContentType == Mock.Of(mt => - mt.Alias == (string)x.Attribute("nodeTypeAlias") && - mt.Id == (int)x.Attribute("nodeType")))) - .ToArray(); + var allRecs = dataService.MediaService.GetLatestMediaByXpath("//node") + .Root + .Elements() + .Select(x => Mock.Of( + m => + m.Id == (int)x.Attribute("id") && + m.ParentId == (int)x.Attribute("parentID") && + m.Level == (int)x.Attribute("level") && + m.CreatorId == 0 && + m.SortOrder == (int)x.Attribute("sortOrder") && + m.CreateDate == (DateTime)x.Attribute("createDate") && + m.UpdateDate == (DateTime)x.Attribute("updateDate") && + m.Name == (string)x.Attribute("nodeName") && + m.Path == (string)x.Attribute("path") && + m.Properties == new PropertyCollection() && + m.ContentType == Mock.Of(mt => + mt.Alias == (string)x.Attribute("nodeTypeAlias") && + mt.Id == (int)x.Attribute("nodeType")))) + .ToArray(); - mediaService = Mock.Of( - x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()) - == - allRecs); - } - if (dataTypeService == null) - { - dataTypeService = Mock.Of(); - } + mediaService = Mock.Of( + x => x.GetPagedDescendants( + It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()) + == + allRecs); + } + if (dataTypeService == null) + { + dataTypeService = Mock.Of(); + } - if (memberService == null) - { - memberService = Mock.Of(); - } + if (memberService == null) + { + memberService = Mock.Of(); + } - if (analyzer == null) - { - analyzer = new StandardAnalyzer(Version.LUCENE_29); - } + if (analyzer == null) + { + analyzer = new StandardAnalyzer(Version.LUCENE_29); + } - var indexSet = new IndexSet(); - var indexCriteria = indexSet.ToIndexCriteria(dataService, UmbracoContentIndexer.IndexFieldPolicies); + var indexSet = new IndexSet(); + var indexCriteria = indexSet.ToIndexCriteria(dataService, UmbracoContentIndexer.IndexFieldPolicies); - var i = new UmbracoContentIndexer(indexCriteria, - luceneDir, //custom lucene directory - dataService, - contentService, - mediaService, - dataTypeService, - userService, - analyzer, - false); + var i = new UmbracoContentIndexer(indexCriteria, + luceneDir, //custom lucene directory + dataService, + contentService, + mediaService, + dataTypeService, + userService, + analyzer, + false); - //i.IndexSecondsInterval = 1; + //i.IndexSecondsInterval = 1; - i.IndexingError += IndexingError; + i.IndexingError += IndexingError; - return i; - } - public static UmbracoExamineSearcher GetUmbracoSearcher(Directory luceneDir, Analyzer analyzer = null) - { - if (analyzer == null) - { - analyzer = new StandardAnalyzer(Version.LUCENE_29); - } - return new UmbracoExamineSearcher(luceneDir, analyzer); - } + return i; + } + public static UmbracoExamineSearcher GetUmbracoSearcher(Directory luceneDir, Analyzer analyzer = null) + { + if (analyzer == null) + { + analyzer = new StandardAnalyzer(Version.LUCENE_29); + } + return new UmbracoExamineSearcher(luceneDir, analyzer); + } - public static LuceneSearcher GetLuceneSearcher(Directory luceneDir) - { - return new LuceneSearcher(luceneDir, new StandardAnalyzer(Version.LUCENE_29)); - } + public static LuceneSearcher GetLuceneSearcher(Directory luceneDir) + { + return new LuceneSearcher(luceneDir, new StandardAnalyzer(Version.LUCENE_29)); + } - public static MultiIndexSearcher GetMultiSearcher(Directory pdfDir, Directory simpleDir, Directory conventionDir, Directory cwsDir) - { - var i = new MultiIndexSearcher(new[] { pdfDir, simpleDir, conventionDir, cwsDir }, new StandardAnalyzer(Version.LUCENE_29)); - return i; - } + public static MultiIndexSearcher GetMultiSearcher(Directory pdfDir, Directory simpleDir, Directory conventionDir, Directory cwsDir) + { + var i = new MultiIndexSearcher(new[] { pdfDir, simpleDir, conventionDir, cwsDir }, new StandardAnalyzer(Version.LUCENE_29)); + return i; + } - internal static void IndexingError(object sender, IndexingErrorEventArgs e) - { - throw new ApplicationException(e.Message, e.InnerException); - } + internal static void IndexingError(object sender, IndexingErrorEventArgs e) + { + throw new ApplicationException(e.Message, e.InnerException); + } - } + } } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js index 8c74abf590..bf593397b6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js @@ -1,71 +1,71 @@ -(function () { - 'use strict'; - - function TableDirective() { - - function link(scope, el, attr, ctrl) { - - scope.clickItem = function (item, $event) { - if (scope.onClick) { - scope.onClick(item); - $event.stopPropagation(); - } - }; - - scope.selectItem = function (item, $index, $event) { - if (scope.onSelect) { - scope.onSelect(item, $index, $event); - $event.stopPropagation(); - } - }; - - scope.selectAll = function ($event) { - if (scope.onSelectAll) { - scope.onSelectAll($event); - } - }; - - scope.isSelectedAll = function () { - if (scope.onSelectedAll && scope.items && scope.items.length > 0) { - return scope.onSelectedAll(); - } - }; - - scope.isSortDirection = function (col, direction) { - if (scope.onSortingDirection) { - return scope.onSortingDirection(col, direction); - } - }; - - scope.sort = function (field, allow, isSystem) { - if (scope.onSort) { - scope.onSort(field, allow, isSystem); - } - }; - - } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-table.html', - scope: { - items: '=', - itemProperties: '=', - allowSelectAll: '=', - onSelect: '=', - onClick: '=', - onSelectAll: '=', - onSelectedAll: '=', - onSortingDirection: '=', - onSort: '=' - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbTable', TableDirective); - -})(); +(function () { + 'use strict'; + + function TableDirective() { + + function link(scope, el, attr, ctrl) { + + scope.clickItem = function (item, $event) { + if (scope.onClick) { + scope.onClick(item); + $event.stopPropagation(); + } + }; + + scope.selectItem = function (item, $index, $event) { + if (scope.onSelect) { + scope.onSelect(item, $index, $event); + $event.stopPropagation(); + } + }; + + scope.selectAll = function ($event) { + if (scope.onSelectAll) { + scope.onSelectAll($event); + } + }; + + scope.isSelectedAll = function () { + if (scope.onSelectedAll && scope.items && scope.items.length > 0) { + return scope.onSelectedAll(); + } + }; + + scope.isSortDirection = function (col, direction) { + if (scope.onSortingDirection) { + return scope.onSortingDirection(col, direction); + } + }; + + scope.sort = function (field, allow, isSystem) { + if (scope.onSort) { + scope.onSort(field, allow, isSystem); + } + }; + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-table.html', + scope: { + items: '=', + itemProperties: '=', + allowSelectAll: '=', + onSelect: '=', + onClick: '=', + onSelectAll: '=', + onSelectedAll: '=', + onSortingDirection: '=', + onSort: '=' + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbTable', TableDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 2688dec37a..60ecd16f19 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -25,636 +25,636 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { - /** internal method process the saving of data and post processing the result */ - function saveContentItem(content, action, files) { - return umbRequestHelper.postSaveContent({ - restApiUrl: umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"), - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatContentPostData(c, a); - } - }); - } - - return { - - getRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for content recycle bin'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#sort - * @methodOf umbraco.resources.contentResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
      -	 * var ids = [123,34533,2334,23434];
      -	 * contentResource.sort({ parentId: 1244, sortedIds: ids })
      -	 *    .then(function() {
      -	 *        $scope.complete = true;
      -	 *    });
      -	 * 
      - * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ - sort: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.sortedIds) { - throw "args.sortedIds cannot be null"; + /** internal method process the saving of data and post processing the result */ + function saveContentItem(content, action, files) { + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"), + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatContentPostData(c, a); + } + }); } - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort content'); - }, + return { - /** - * @ngdoc method - * @name umbraco.resources.contentResource#move - * @methodOf umbraco.resources.contentResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
      -	 * contentResource.move({ parentId: 1244, id: 123 })
      -	 *    .then(function() {
      -	 *        alert("node was moved");
      -	 *    }, function(err){
      -	 *      alert("node didnt move:" + err.data.Message); 
      -	 *    });
      -	 * 
      - * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } + getRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetRecycleBin")), + 'Failed to retrieve data for content recycle bin'); + }, - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move content'); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#sort + * @methodOf umbraco.resources.contentResource + * + * @description + * Sorts all children below a given parent node id, based on a collection of node-ids + * + * ##usage + *
      +        * var ids = [123,34533,2334,23434];
      +        * contentResource.sort({ parentId: 1244, sortedIds: ids })
      +        *    .then(function() {
      +        *        $scope.complete = true;
      +        *    });
      +        * 
      + * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ + sort: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.sortedIds) { + throw "args.sortedIds cannot be null"; + } - /** - * @ngdoc method - * @name umbraco.resources.contentResource#copy - * @methodOf umbraco.resources.contentResource - * - * @description - * Copies a node underneath a new parentId - * - * ##usage - *
      -	 * contentResource.copy({ parentId: 1244, id: 123 })
      -	 *    .then(function() {
      -	 *        alert("node was copied");
      -	 *    }, function(err){
      -	 *      alert("node wasnt copy:" + err.data.Message); 
      -	 *    });
      -	 * 
      - * @param {Object} args arguments object - * @param {Int} args.id the ID of the node to copy - * @param {Int} args.parentId the ID of the parent node to copy to - * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api - * @returns {Promise} resourcePromise object. - * - */ - copy: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"), + { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), + 'Failed to sort content'); + }, - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"), - args), - 'Failed to copy content'); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#move + * @methodOf umbraco.resources.contentResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
      +        * contentResource.move({ parentId: 1244, id: 123 })
      +        *    .then(function() {
      +        *        alert("node was moved");
      +        *    }, function(err){
      +        *      alert("node didnt move:" + err.data.Message); 
      +        *    });
      +        * 
      + * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } - /** - * @ngdoc method - * @name umbraco.resources.contentResource#unPublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Unpublishes a content item with a given Id - * - * ##usage - *
      -	 * contentResource.unPublish(1234)
      -	 *    .then(function() {
      -	 *        alert("node was unpulished");
      -	 *    }, function(err){
      -	 *      alert("node wasnt unpublished:" + err.data.Message); 
      -	 *    });
      -	 * 
      - * @param {Int} id the ID of the node to unpublish - * @returns {Promise} resourcePromise object. - * - */ - unPublish: function (id) { - if (!id) { - throw "id cannot be null"; - } + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move content'); + }, - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostUnPublish", - [{ id: id }])), - 'Failed to publish content with id ' + id); - }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#emptyRecycleBin - * @methodOf umbraco.resources.contentResource - * - * @description - * Empties the content recycle bin - * - * ##usage - *
      -	 * contentResource.emptyRecycleBin()
      -	 *    .then(function() {
      -	 *        alert('its empty!');
      -	 *    });
      -	 * 
      - * - * @returns {Promise} resourcePromise object. - * - */ - emptyRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "EmptyRecycleBin")), - 'Failed to empty the recycle bin'); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#copy + * @methodOf umbraco.resources.contentResource + * + * @description + * Copies a node underneath a new parentId + * + * ##usage + *
      +        * contentResource.copy({ parentId: 1244, id: 123 })
      +        *    .then(function() {
      +        *        alert("node was copied");
      +        *    }, function(err){
      +        *      alert("node wasnt copy:" + err.data.Message); 
      +        *    });
      +        * 
      + * @param {Object} args arguments object + * @param {Int} args.id the ID of the node to copy + * @param {Int} args.parentId the ID of the parent node to copy to + * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api + * @returns {Promise} resourcePromise object. + * + */ + copy: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } - /** - * @ngdoc method - * @name umbraco.resources.contentResource#deleteById - * @methodOf umbraco.resources.contentResource - * - * @description - * Deletes a content item with a given id - * - * ##usage - *
      -	 * contentResource.deleteById(1234)
      -	 *    .then(function() {
      -	 *        alert('its gone!');
      -	 *    });
      -	 * 
      - * - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteById: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); - }, + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"), + args), + 'Failed to copy content'); + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getById - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets a content item with a given id - * - * ##usage - *
      -	 * contentResource.getById(1234)
      -	 *    .then(function(content) {
      -	 *        var myDoc = content; 
      -	 *        alert('its here!');
      -	 *    });
      -	 * 
      - * - * @param {Int} id id of content item to return - * @returns {Promise} resourcePromise object containing the content item. - * - */ - getById: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve data for content id ' + id); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#unPublish + * @methodOf umbraco.resources.contentResource + * + * @description + * Unpublishes a content item with a given Id + * + * ##usage + *
      +        * contentResource.unPublish(1234)
      +        *    .then(function() {
      +        *        alert("node was unpulished");
      +        *    }, function(err){
      +        *      alert("node wasnt unpublished:" + err.data.Message); 
      +        *    });
      +        * 
      + * @param {Int} id the ID of the node to unpublish + * @returns {Promise} resourcePromise object. + * + */ + unPublish: function (id) { + if (!id) { + throw "id cannot be null"; + } - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getByIds - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets an array of content items, given a collection of ids - * - * ##usage - *
      -	 * contentResource.getByIds( [1234,2526,28262])
      -	 *    .then(function(contentArray) {
      -	 *        var myDoc = contentArray; 
      -	 *        alert('they are here!');
      -	 *    });
      -	 * 
      - * - * @param {Array} ids ids of content items to return as an array - * @returns {Promise} resourcePromise object containing the content items array. - * - */ - getByIds: function (ids) { - - var idQuery = ""; - _.each(ids, function (item) { - idQuery += "ids=" + item + "&"; - }); - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for content with multiple ids'); - }, - - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getScaffold - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. - * - * - Parent Id must be provided so umbraco knows where to store the content - * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold - * - * The scaffold is used to build editors for content that has not yet been populated with data. - * - * ##usage - *
      -	 * contentResource.getScaffold(1234, 'homepage')
      -	 *    .then(function(scaffold) {
      -	 *        var myDoc = scaffold;
      -	 *        myDoc.name = "My new document"; 
      -	 *
      -	 *        contentResource.publish(myDoc, true)
      -	 *            .then(function(content){
      -	 *                alert("Retrieved, updated and published again");
      -	 *            });
      -	 *    });
      -	 * 
      - * - * @param {Int} parentId id of content item to return - * @param {String} alias contenttype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the content scaffold. - * - */ - getScaffold: function (parentId, alias) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty content item type ' + alias); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getNiceUrl - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a url, given a node ID - * - * ##usage - *
      -	 * contentResource.getNiceUrl(id)
      -	 *    .then(function(url) {
      -	 *        alert('its here!');
      -	 *    });
      -	 * 
      - * - * @param {Int} id Id of node to return the public url to - * @returns {Promise} resourcePromise object containing the url. - * - */ - getNiceUrl: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetNiceUrl", [{ id: id }])), - 'Failed to retrieve url for id:' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getChildren - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets children of a content item with a given id - * - * ##usage - *
      -	 * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
      -	 *    .then(function(contentArray) {
      -	 *        var children = contentArray; 
      -	 *        alert('they are here!');
      -	 *    });
      -	 * 
      - * - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getChildren: function (parentId, options) { - - var defaults = { - pageSize: 0, - pageNumber: 0, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder", - orderBySystemField: true - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetChildren", - [ - { id: parentId }, - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { orderBySystemField: options.orderBySystemField }, - { filter: options.filter } - ])), - 'Failed to retrieve children for content item ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#hasPermission - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns true/false given a permission char to check against a nodeID - * for the current user - * - * ##usage - *
      -	 * contentResource.hasPermission('p',1234)
      -	 *    .then(function() {
      -	 *        alert('You are allowed to publish this item');
      -	 *    });
      -	 * 
      - * - * @param {String} permission char representing the permission to check - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ - checkPermission: function (permission, id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "HasPermission", - [{ permissionToCheck: permission }, { nodeId: id }])), - 'Failed to check permission for item ' + id); - }, - - getPermissions: function (nodeIds) { - return umbRequestHelper.resourcePromise( + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostUnPublish", + [{ id: id }])), + 'Failed to publish content with id ' + id); + }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#emptyRecycleBin + * @methodOf umbraco.resources.contentResource + * + * @description + * Empties the content recycle bin + * + * ##usage + *
      +        * contentResource.emptyRecycleBin()
      +        *    .then(function() {
      +        *        alert('its empty!');
      +        *    });
      +        * 
      + * + * @returns {Promise} resourcePromise object. + * + */ + emptyRecycleBin: function () { + return umbRequestHelper.resourcePromise( $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetPermissions"), - nodeIds), - 'Failed to get permissions'); - }, + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#save - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
      -	 * contentResource.getById(1234)
      -	 *    .then(function(content) {
      -	 *          content.name = "I want a new name!";
      -	 *          contentResource.save(content, false)
      -	 *            .then(function(content){
      -	 *                alert("Retrieved, updated and saved again");
      -	 *            });
      -	 *    });
      -	 * 
      - * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - save: function (content, isNew, files) { - return saveContentItem(content, "save" + (isNew ? "New" : ""), files); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#deleteById + * @methodOf umbraco.resources.contentResource + * + * @description + * Deletes a content item with a given id + * + * ##usage + *
      +        * contentResource.deleteById(1234)
      +        *    .then(function() {
      +        *        alert('its gone!');
      +        *    });
      +        * 
      + * + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete item ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getById + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets a content item with a given id + * + * ##usage + *
      +        * contentResource.getById(1234)
      +        *    .then(function(content) {
      +        *        var myDoc = content; 
      +        *        alert('its here!');
      +        *    });
      +        * 
      + * + * @param {Int} id id of content item to return + * @returns {Promise} resourcePromise object containing the content item. + * + */ + getById: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve data for content id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getByIds + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets an array of content items, given a collection of ids + * + * ##usage + *
      +        * contentResource.getByIds( [1234,2526,28262])
      +        *    .then(function(contentArray) {
      +        *        var myDoc = contentArray; 
      +        *        alert('they are here!');
      +        *    });
      +        * 
      + * + * @param {Array} ids ids of content items to return as an array + * @returns {Promise} resourcePromise object containing the content items array. + * + */ + getByIds: function (ids) { + + var idQuery = ""; + _.each(ids, function (item) { + idQuery += "ids=" + item + "&"; + }); + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetByIds", + idQuery)), + 'Failed to retrieve data for content with multiple ids'); + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#publish - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
      -	 * contentResource.getById(1234)
      -	 *    .then(function(content) {
      -	 *          content.name = "I want a new name, and be published!";
      -	 *          contentResource.publish(content, false)
      -	 *            .then(function(content){
      -	 *                alert("Retrieved, updated and published again");
      -	 *            });
      -	 *    });
      -	 * 
      - * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - publish: function (content, isNew, files) { - return saveContentItem(content, "publish" + (isNew ? "New" : ""), files); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getScaffold + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. + * + * - Parent Id must be provided so umbraco knows where to store the content + * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold + * + * The scaffold is used to build editors for content that has not yet been populated with data. + * + * ##usage + *
      +        * contentResource.getScaffold(1234, 'homepage')
      +        *    .then(function(scaffold) {
      +        *        var myDoc = scaffold;
      +        *        myDoc.name = "My new document"; 
      +        *
      +        *        contentResource.publish(myDoc, true)
      +        *            .then(function(content){
      +        *                alert("Retrieved, updated and published again");
      +        *            });
      +        *    });
      +        * 
      + * + * @param {Int} parentId id of content item to return + * @param {String} alias contenttype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the content scaffold. + * + */ + getScaffold: function (parentId, alias) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty content item type ' + alias); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getNiceUrl + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a url, given a node ID + * + * ##usage + *
      +        * contentResource.getNiceUrl(id)
      +        *    .then(function(url) {
      +        *        alert('its here!');
      +        *    });
      +        * 
      + * + * @param {Int} id Id of node to return the public url to + * @returns {Promise} resourcePromise object containing the url. + * + */ + getNiceUrl: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetNiceUrl", [{ id: id }])), + 'Failed to retrieve url for id:' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getChildren + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets children of a content item with a given id + * + * ##usage + *
      +        * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
      +        *    .then(function(contentArray) {
      +        *        var children = contentArray; 
      +        *        alert('they are here!');
      +        *    });
      +        * 
      + * + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getChildren: function (parentId, options) { + + var defaults = { + pageSize: 0, + pageNumber: 0, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + orderBySystemField: true + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetChildren", + [ + { id: parentId }, + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: options.orderBySystemField }, + { filter: options.filter } + ])), + 'Failed to retrieve children for content item ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#hasPermission + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns true/false given a permission char to check against a nodeID + * for the current user + * + * ##usage + *
      +        * contentResource.hasPermission('p',1234)
      +        *    .then(function() {
      +        *        alert('You are allowed to publish this item');
      +        *    });
      +        * 
      + * + * @param {String} permission char representing the permission to check + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ + checkPermission: function (permission, id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "HasPermission", + [{ permissionToCheck: permission }, { nodeId: id }])), + 'Failed to check permission for item ' + id); + }, + + getPermissions: function (nodeIds) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetPermissions"), + nodeIds), + 'Failed to get permissions'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#save + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
      +        * contentResource.getById(1234)
      +        *    .then(function(content) {
      +        *          content.name = "I want a new name!";
      +        *          contentResource.save(content, false)
      +        *            .then(function(content){
      +        *                alert("Retrieved, updated and saved again");
      +        *            });
      +        *    });
      +        * 
      + * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + save: function (content, isNew, files) { + return saveContentItem(content, "save" + (isNew ? "New" : ""), files); + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#sendToPublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves changes made to a content item, and notifies any subscribers about a pending publication - * - * ##usage - *
      -	 * contentResource.getById(1234)
      -	 *    .then(function(content) {
      -	 *          content.name = "I want a new name, and be published!";
      -	 *          contentResource.sendToPublish(content, false)
      -	 *            .then(function(content){
      -	 *                alert("Retrieved, updated and notication send off");
      -	 *            });
      -	 *    });
      -	 * 
      - * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - sendToPublish: function (content, isNew, files) { - return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#publishByid - * @methodOf umbraco.resources.contentResource - * - * @description - * Publishes a content item with a given ID - * - * ##usage - *
      -	 * contentResource.publishById(1234)
      -	 *    .then(function(content) {
      -	 *        alert("published");
      -	 *    });
      -	 * 
      - * - * @param {Int} id The ID of the conten to publish - * @returns {Promise} resourcePromise object containing the published content item. - * - */ - publishById: function (id) { - - if (!id) { - throw "id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostPublishById", - [{ id: id }])), - 'Failed to publish content with id ' + id); - - } + /** + * @ngdoc method + * @name umbraco.resources.contentResource#publish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
      +        * contentResource.getById(1234)
      +        *    .then(function(content) {
      +        *          content.name = "I want a new name, and be published!";
      +        *          contentResource.publish(content, false)
      +        *            .then(function(content){
      +        *                alert("Retrieved, updated and published again");
      +        *            });
      +        *    });
      +        * 
      + * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + publish: function (content, isNew, files) { + return saveContentItem(content, "publish" + (isNew ? "New" : ""), files); + }, - }; + /** + * @ngdoc method + * @name umbraco.resources.contentResource#sendToPublish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item, and notifies any subscribers about a pending publication + * + * ##usage + *
      +        * contentResource.getById(1234)
      +        *    .then(function(content) {
      +        *          content.name = "I want a new name, and be published!";
      +        *          contentResource.sendToPublish(content, false)
      +        *            .then(function(content){
      +        *                alert("Retrieved, updated and notication send off");
      +        *            });
      +        *    });
      +        * 
      + * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + sendToPublish: function (content, isNew, files) { + return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#publishByid + * @methodOf umbraco.resources.contentResource + * + * @description + * Publishes a content item with a given ID + * + * ##usage + *
      +        * contentResource.publishById(1234)
      +        *    .then(function(content) {
      +        *        alert("published");
      +        *    });
      +        * 
      + * + * @param {Int} id The ID of the conten to publish + * @returns {Promise} resourcePromise object containing the published content item. + * + */ + publishById: function (id) { + + if (!id) { + throw "id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostPublishById", + [{ id: id }])), + 'Failed to publish content with id ' + id); + + } + + + }; } angular.module('umbraco.resources').factory('contentResource', contentResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index 887ed661a7..a85b2cc104 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -5,471 +5,471 @@ **/ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { - /** internal method process the saving of data and post processing the result */ - function saveMediaItem(content, action, files) { - return umbRequestHelper.postSaveContent({ - restApiUrl: umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "PostSave"), - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatMediaPostData(c, a); - } - }); - } - - return { - - getRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for media recycle bin'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#sort - * @methodOf umbraco.resources.mediaResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
      -	 * var ids = [123,34533,2334,23434];
      -	 * mediaResource.sort({ sortedIds: ids })
      -	 *    .then(function() {
      -	 *        $scope.complete = true;
      -	 *    });
      -	 * 
      - * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ - sort: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.sortedIds) { - throw "args.sortedIds cannot be null"; + /** internal method process the saving of data and post processing the result */ + function saveMediaItem(content, action, files) { + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "PostSave"), + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatMediaPostData(c, a); + } + }); } - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort media'); - }, + return { - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#move - * @methodOf umbraco.resources.mediaResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
      -	 * mediaResource.move({ parentId: 1244, id: 123 })
      -	 *    .then(function() {
      -	 *        alert("node was moved");
      -	 *    }, function(err){
      -	 *      alert("node didnt move:" + err.data.Message); 
      -	 *    });
      -	 * 
      - * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } + getRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRecycleBin")), + 'Failed to retrieve data for media recycle bin'); + }, - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move media'); - }, + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#sort + * @methodOf umbraco.resources.mediaResource + * + * @description + * Sorts all children below a given parent node id, based on a collection of node-ids + * + * ##usage + *
      +        * var ids = [123,34533,2334,23434];
      +        * mediaResource.sort({ sortedIds: ids })
      +        *    .then(function() {
      +        *        $scope.complete = true;
      +        *    });
      +        * 
      + * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ + sort: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.sortedIds) { + throw "args.sortedIds cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), + { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), + 'Failed to sort media'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#move + * @methodOf umbraco.resources.mediaResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
      +        * mediaResource.move({ parentId: 1244, id: 123 })
      +        *    .then(function() {
      +        *        alert("node was moved");
      +        *    }, function(err){
      +        *      alert("node didnt move:" + err.data.Message); 
      +        *    });
      +        * 
      + * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move media'); + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets a media item with a given id - * - * ##usage - *
      -	 * mediaResource.getById(1234)
      -	 *    .then(function(media) {
      -	 *        var myMedia = media; 
      -	 *        alert('its here!');
      -	 *    });
      -	 * 
      - * - * @param {Int} id id of media item to return - * @returns {Promise} resourcePromise object containing the media item. - * - */ - getById: function (id) { + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets a media item with a given id + * + * ##usage + *
      +        * mediaResource.getById(1234)
      +        *    .then(function(media) {
      +        *        var myMedia = media; 
      +        *        alert('its here!');
      +        *    });
      +        * 
      + * + * @param {Int} id id of media item to return + * @returns {Promise} resourcePromise object containing the media item. + * + */ + getById: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve data for media id ' + id); - }, + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve data for media id ' + id); + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#deleteById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Deletes a media item with a given id - * - * ##usage - *
      -	 * mediaResource.deleteById(1234)
      -	 *    .then(function() {
      -	 *        alert('its gone!');
      -	 *    });
      -	 * 
      - * - * @param {Int} id id of media item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteById: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); - }, + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#deleteById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Deletes a media item with a given id + * + * ##usage + *
      +        * mediaResource.deleteById(1234)
      +        *    .then(function() {
      +        *        alert('its gone!');
      +        *    });
      +        * 
      + * + * @param {Int} id id of media item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete item ' + id); + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getByIds - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets an array of media items, given a collection of ids - * - * ##usage - *
      -	 * mediaResource.getByIds( [1234,2526,28262])
      -	 *    .then(function(mediaArray) {
      -	 *        var myDoc = contentArray; 
      -	 *        alert('they are here!');
      -	 *    });
      -	 * 
      - * - * @param {Array} ids ids of media items to return as an array - * @returns {Promise} resourcePromise object containing the media items array. - * - */ - getByIds: function (ids) { + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getByIds + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets an array of media items, given a collection of ids + * + * ##usage + *
      +        * mediaResource.getByIds( [1234,2526,28262])
      +        *    .then(function(mediaArray) {
      +        *        var myDoc = contentArray; 
      +        *        alert('they are here!');
      +        *    });
      +        * 
      + * + * @param {Array} ids ids of media items to return as an array + * @returns {Promise} resourcePromise object containing the media items array. + * + */ + getByIds: function (ids) { - var idQuery = ""; - _.each(ids, function (item) { - idQuery += "ids=" + item + "&"; - }); + var idQuery = ""; + _.each(ids, function (item) { + idQuery += "ids=" + item + "&"; + }); - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for media ids ' + ids); - }, + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetByIds", + idQuery)), + 'Failed to retrieve data for media ids ' + ids); + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getScaffold - * @methodOf umbraco.resources.mediaResource - * - * @description - * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. - * - * - Parent Id must be provided so umbraco knows where to store the media - * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold - * - * The scaffold is used to build editors for media that has not yet been populated with data. - * - * ##usage - *
      -	 * mediaResource.getScaffold(1234, 'folder')
      -	 *    .then(function(scaffold) {
      -	 *        var myDoc = scaffold;
      -	 *        myDoc.name = "My new media item"; 
      -	 *
      -	 *        mediaResource.save(myDoc, true)
      -	 *            .then(function(media){
      -	 *                alert("Retrieved, updated and saved again");
      -	 *            });
      -	 *    });
      -	 * 
      - * - * @param {Int} parentId id of media item to return - * @param {String} alias mediatype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the media scaffold. - * - */ - getScaffold: function (parentId, alias) { + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getScaffold + * @methodOf umbraco.resources.mediaResource + * + * @description + * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. + * + * - Parent Id must be provided so umbraco knows where to store the media + * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold + * + * The scaffold is used to build editors for media that has not yet been populated with data. + * + * ##usage + *
      +        * mediaResource.getScaffold(1234, 'folder')
      +        *    .then(function(scaffold) {
      +        *        var myDoc = scaffold;
      +        *        myDoc.name = "My new media item"; 
      +        *
      +        *        mediaResource.save(myDoc, true)
      +        *            .then(function(media){
      +        *                alert("Retrieved, updated and saved again");
      +        *            });
      +        *    });
      +        * 
      + * + * @param {Int} parentId id of media item to return + * @param {String} alias mediatype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the media scaffold. + * + */ + getScaffold: function (parentId, alias) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty media item type ' + alias); + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty media item type ' + alias); - }, + }, - rootMedia: function () { + rootMedia: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRootMedia")), - 'Failed to retrieve data for root media'); + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRootMedia")), + 'Failed to retrieve data for root media'); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildren - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets children of a media item with a given id - * - * ##usage - *
      -	 * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
      -	 *    .then(function(contentArray) {
      -	 *        var children = contentArray; 
      -	 *        alert('they are here!');
      -	 *    });
      -	 * 
      - * - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getChildren: function (parentId, options) { + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildren + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets children of a media item with a given id + * + * ##usage + *
      +        * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
      +        *    .then(function(contentArray) {
      +        *        var children = contentArray; 
      +        *        alert('they are here!');
      +        *    });
      +        * 
      + * + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getChildren: function (parentId, options) { - var defaults = { - pageSize: 0, - pageNumber: 0, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder", - orderBySystemField: true + var defaults = { + pageSize: 0, + pageNumber: 0, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + orderBySystemField: true + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildren", + [ + { id: parentId }, + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: options.orderBySystemField }, + { filter: options.filter } + ])), + 'Failed to retrieve children for media item ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#save + * @methodOf umbraco.resources.mediaResource + * + * @description + * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation + * if the media item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
      +        * mediaResource.getById(1234)
      +        *    .then(function(media) {
      +        *          media.name = "I want a new name!";
      +        *          mediaResource.save(media, false)
      +        *            .then(function(media){
      +        *                alert("Retrieved, updated and saved again");
      +        *            });
      +        *    });
      +        * 
      + * + * @param {Object} media The media item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the media item + * @returns {Promise} resourcePromise object containing the saved media item. + * + */ + save: function (media, isNew, files) { + return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#addFolder + * @methodOf umbraco.resources.mediaResource + * + * @description + * Shorthand for adding a media item of the type "Folder" under a given parent ID + * + * ##usage + *
      +        * mediaResource.addFolder("My gallery", 1234)
      +        *    .then(function(folder) {
      +        *        alert('New folder');
      +        *    });
      +        * 
      + * + * @param {string} name Name of the folder to create + * @param {int} parentId Id of the media item to create the folder underneath + * @returns {Promise} resourcePromise object. + * + */ + addFolder: function (name, parentId) { + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper + .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), + { + name: name, + parentId: parentId + }), + 'Failed to add folder'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildFolders + * @methodOf umbraco.resources.mediaResource + * + * @description + * Retrieves all media children with types used as folders. + * Uses the convention of looking for media items with mediaTypes ending in + * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, + * + * ##usage + *
      +        * mediaResource.getChildFolders(1234)
      +        *    .then(function(data) {
      +        *        alert('folders');
      +        *    });
      +        * 
      + * + * @param {int} parentId Id of the media item to query for child folders + * @returns {Promise} resourcePromise object. + * + */ + getChildFolders: function (parentId) { + if (!parentId) { + parentId = -1; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildFolders", + [ + { id: parentId } + ])), + 'Failed to retrieve child folders for media item ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#emptyRecycleBin + * @methodOf umbraco.resources.mediaResource + * + * @description + * Empties the media recycle bin + * + * ##usage + *
      +        * mediaResource.emptyRecycleBin()
      +        *    .then(function() {
      +        *        alert('its empty!');
      +        *    });
      +        * 
      + * + * @returns {Promise} resourcePromise object. + * + */ + emptyRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); + } }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildren", - [ - { id: parentId }, - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { orderBySystemField: options.orderBySystemField }, - { filter: options.filter } - ])), - 'Failed to retrieve children for media item ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#save - * @methodOf umbraco.resources.mediaResource - * - * @description - * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation - * if the media item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
      -	 * mediaResource.getById(1234)
      -	 *    .then(function(media) {
      -	 *          media.name = "I want a new name!";
      -	 *          mediaResource.save(media, false)
      -	 *            .then(function(media){
      -	 *                alert("Retrieved, updated and saved again");
      -	 *            });
      -	 *    });
      -	 * 
      - * - * @param {Object} media The media item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the media item - * @returns {Promise} resourcePromise object containing the saved media item. - * - */ - save: function (media, isNew, files) { - return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#addFolder - * @methodOf umbraco.resources.mediaResource - * - * @description - * Shorthand for adding a media item of the type "Folder" under a given parent ID - * - * ##usage - *
      -	 * mediaResource.addFolder("My gallery", 1234)
      -	 *    .then(function(folder) {
      -	 *        alert('New folder');
      -	 *    });
      -	 * 
      - * - * @param {string} name Name of the folder to create - * @param {int} parentId Id of the media item to create the folder underneath - * @returns {Promise} resourcePromise object. - * - */ - addFolder: function (name, parentId) { - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper - .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), - { - name: name, - parentId: parentId - }), - 'Failed to add folder'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildFolders - * @methodOf umbraco.resources.mediaResource - * - * @description - * Retrieves all media children with types used as folders. - * Uses the convention of looking for media items with mediaTypes ending in - * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, - * - * ##usage - *
      -	 * mediaResource.getChildFolders(1234)
      -	 *    .then(function(data) {
      -	 *        alert('folders');
      -	 *    });
      -	 * 
      - * - * @param {int} parentId Id of the media item to query for child folders - * @returns {Promise} resourcePromise object. - * - */ - getChildFolders: function (parentId) { - if (!parentId) { - parentId = -1; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildFolders", - [ - { id: parentId } - ])), - 'Failed to retrieve child folders for media item ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#emptyRecycleBin - * @methodOf umbraco.resources.mediaResource - * - * @description - * Empties the media recycle bin - * - * ##usage - *
      -	 * mediaResource.emptyRecycleBin()
      -	 *    .then(function() {
      -	 *        alert('its empty!');
      -	 *    });
      -	 * 
      - * - * @returns {Promise} resourcePromise object. - * - */ - emptyRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "EmptyRecycleBin")), - 'Failed to empty the recycle bin'); - } - }; } angular.module('umbraco.resources').factory('mediaResource', mediaResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js index c231a4944c..3ec9258f98 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js @@ -5,231 +5,231 @@ **/ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { - /** internal method process the saving of data and post processing the result */ - function saveMember(content, action, files) { + /** internal method process the saving of data and post processing the result */ + function saveMember(content, action, files) { - return umbRequestHelper.postSaveContent({ - restApiUrl: umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "PostSave"), - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatMemberPostData(c, a); - } - }); - } - - return { - - getPagedResults: function (memberTypeAlias, options) { - - if (memberTypeAlias === 'all-members') { - memberTypeAlias = null; + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "PostSave"), + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatMemberPostData(c, a); + } + }); } - var defaults = { - pageSize: 25, - pageNumber: 1, - filter: '', - orderDirection: "Ascending", - orderBy: "LoginName", - orderBySystemField: true + return { + + getPagedResults: function (memberTypeAlias, options) { + + if (memberTypeAlias === 'all-members') { + memberTypeAlias = null; + } + + var defaults = { + pageSize: 25, + pageNumber: 1, + filter: '', + orderDirection: "Ascending", + orderBy: "LoginName", + orderBySystemField: true + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + var params = [ + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: options.orderBySystemField }, + { filter: options.filter } + ]; + if (memberTypeAlias != null) { + params.push({ memberTypeAlias: memberTypeAlias }); + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetPagedResults", + params)), + 'Failed to retrieve member paged result'); + }, + + getListNode: function (listName) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetListNodeDisplay", + [{ listName: listName }])), + 'Failed to retrieve data for member list ' + listName); + }, + + /** + * @ngdoc method + * @name umbraco.resources.memberResource#getByKey + * @methodOf umbraco.resources.memberResource + * + * @description + * Gets a member item with a given key + * + * ##usage + *
      +        * memberResource.getByKey("0000-0000-000-00000-000")
      +        *    .then(function(member) {
      +        *        var mymember = member; 
      +        *        alert('its here!');
      +        *    });
      +        * 
      + * + * @param {Guid} key key of member item to return + * @returns {Promise} resourcePromise object containing the member item. + * + */ + getByKey: function (key) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetByKey", + [{ key: key }])), + 'Failed to retrieve data for member id ' + key); + }, + + /** + * @ngdoc method + * @name umbraco.resources.memberResource#deleteByKey + * @methodOf umbraco.resources.memberResource + * + * @description + * Deletes a member item with a given key + * + * ##usage + *
      +        * memberResource.deleteByKey("0000-0000-000-00000-000")
      +        *    .then(function() {
      +        *        alert('its gone!');
      +        *    });
      +        * 
      + * + * @param {Guid} key id of member item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteByKey: function (key) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "DeleteByKey", + [{ key: key }])), + 'Failed to delete item ' + key); + }, + + /** + * @ngdoc method + * @name umbraco.resources.memberResource#getScaffold + * @methodOf umbraco.resources.memberResource + * + * @description + * Returns a scaffold of an empty member item, given the id of the member item to place it underneath and the member type alias. + * + * - Member Type alias must be provided so umbraco knows which properties to put on the member scaffold + * + * The scaffold is used to build editors for member that has not yet been populated with data. + * + * ##usage + *
      +        * memberResource.getScaffold('client')
      +        *    .then(function(scaffold) {
      +        *        var myDoc = scaffold;
      +        *        myDoc.name = "My new member item"; 
      +        *
      +        *        memberResource.save(myDoc, true)
      +        *            .then(function(member){
      +        *                alert("Retrieved, updated and saved again");
      +        *            });
      +        *    });
      +        * 
      + * + * @param {String} alias membertype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the member scaffold. + * + */ + getScaffold: function (alias) { + + if (alias) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }])), + 'Failed to retrieve data for empty member item type ' + alias); + } + else { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetEmpty")), + 'Failed to retrieve data for empty member item type ' + alias); + } + + }, + + /** + * @ngdoc method + * @name umbraco.resources.memberResource#save + * @methodOf umbraco.resources.memberResource + * + * @description + * Saves changes made to a member, if the member is new, the isNew paramater must be passed to force creation + * if the member needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
      +        * memberResource.getBykey("23234-sd8djsd-3h8d3j-sdh8d")
      +        *    .then(function(member) {
      +        *          member.name = "Bob";
      +        *          memberResource.save(member, false)
      +        *            .then(function(member){
      +        *                alert("Retrieved, updated and saved again");
      +        *            });
      +        *    });
      +        * 
      + * + * @param {Object} media The member item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the media item + * @returns {Promise} resourcePromise object containing the saved media item. + * + */ + save: function (member, isNew, files) { + return saveMember(member, "save" + (isNew ? "New" : ""), files); + } }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - var params = [ - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { orderBySystemField: options.orderBySystemField }, - { filter: options.filter } - ]; - if (memberTypeAlias != null) { - params.push({ memberTypeAlias: memberTypeAlias }); - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetPagedResults", - params)), - 'Failed to retrieve member paged result'); - }, - - getListNode: function (listName) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetListNodeDisplay", - [{ listName: listName }])), - 'Failed to retrieve data for member list ' + listName); - }, - - /** - * @ngdoc method - * @name umbraco.resources.memberResource#getByKey - * @methodOf umbraco.resources.memberResource - * - * @description - * Gets a member item with a given key - * - * ##usage - *
      -	 * memberResource.getByKey("0000-0000-000-00000-000")
      -	 *    .then(function(member) {
      -	 *        var mymember = member; 
      -	 *        alert('its here!');
      -	 *    });
      -	 * 
      - * - * @param {Guid} key key of member item to return - * @returns {Promise} resourcePromise object containing the member item. - * - */ - getByKey: function (key) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetByKey", - [{ key: key }])), - 'Failed to retrieve data for member id ' + key); - }, - - /** - * @ngdoc method - * @name umbraco.resources.memberResource#deleteByKey - * @methodOf umbraco.resources.memberResource - * - * @description - * Deletes a member item with a given key - * - * ##usage - *
      -	 * memberResource.deleteByKey("0000-0000-000-00000-000")
      -	 *    .then(function() {
      -	 *        alert('its gone!');
      -	 *    });
      -	 * 
      - * - * @param {Guid} key id of member item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteByKey: function (key) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "DeleteByKey", - [{ key: key }])), - 'Failed to delete item ' + key); - }, - - /** - * @ngdoc method - * @name umbraco.resources.memberResource#getScaffold - * @methodOf umbraco.resources.memberResource - * - * @description - * Returns a scaffold of an empty member item, given the id of the member item to place it underneath and the member type alias. - * - * - Member Type alias must be provided so umbraco knows which properties to put on the member scaffold - * - * The scaffold is used to build editors for member that has not yet been populated with data. - * - * ##usage - *
      -	 * memberResource.getScaffold('client')
      -	 *    .then(function(scaffold) {
      -	 *        var myDoc = scaffold;
      -	 *        myDoc.name = "My new member item"; 
      -	 *
      -	 *        memberResource.save(myDoc, true)
      -	 *            .then(function(member){
      -	 *                alert("Retrieved, updated and saved again");
      -	 *            });
      -	 *    });
      -	 * 
      - * - * @param {String} alias membertype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the member scaffold. - * - */ - getScaffold: function (alias) { - - if (alias) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }])), - 'Failed to retrieve data for empty member item type ' + alias); - } - else { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetEmpty")), - 'Failed to retrieve data for empty member item type ' + alias); - } - - }, - - /** - * @ngdoc method - * @name umbraco.resources.memberResource#save - * @methodOf umbraco.resources.memberResource - * - * @description - * Saves changes made to a member, if the member is new, the isNew paramater must be passed to force creation - * if the member needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
      -	 * memberResource.getBykey("23234-sd8djsd-3h8d3j-sdh8d")
      -	 *    .then(function(member) {
      -	 *          member.name = "Bob";
      -	 *          memberResource.save(member, false)
      -	 *            .then(function(member){
      -	 *                alert("Retrieved, updated and saved again");
      -	 *            });
      -	 *    });
      -	 * 
      - * - * @param {Object} media The member item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the media item - * @returns {Promise} resourcePromise object containing the saved media item. - * - */ - save: function (member, isNew, files) { - return saveMember(member, "save" + (isNew ? "New" : ""), files); - } - }; } angular.module('umbraco.resources').factory('memberResource', memberResource); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html index 1ab22eb406..8d9b45e6be 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html @@ -1,60 +1,60 @@ -
      -
      - -
      -
      -
      - -
      - - -
      -
      - -
      -
      - -
      - - -
      -
      - - -
      -
      - {{item[column.alias]}} -
      -
      -
      -
      - - - There are no items show in the list. - -
      +
      +
      + +
      +
      +
      + +
      + + +
      +
      + +
      +
      + +
      + + +
      +
      + + +
      +
      + {{item[column.alias]}} +
      +
      +
      +
      + + + There are no items show in the list. + +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js index de4902bdd1..b396b66564 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js @@ -1,76 +1,76 @@ -(function() { - "use strict"; - - function ListViewListLayoutController($scope, listViewHelper, $location, mediaHelper) { - - var vm = this; - - vm.nodeId = $scope.contentId; - //vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); - //instead of passing in a whitelist, we pass in a blacklist by adding ! to the ext - vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles).replace(/./g, "!."); - vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; - vm.activeDrag = false; - vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; - - vm.selectItem = selectItem; - vm.clickItem = clickItem; - vm.selectAll = selectAll; - vm.isSelectedAll = isSelectedAll; - vm.isSortDirection = isSortDirection; - vm.sort = sort; - vm.dragEnter = dragEnter; - vm.dragLeave = dragLeave; - vm.onFilesQueue = onFilesQueue; - vm.onUploadComplete = onUploadComplete; - - function selectAll($event) { - listViewHelper.selectAllItems($scope.items, $scope.selection, $event); - } - - function isSelectedAll() { - return listViewHelper.isSelectedAll($scope.items, $scope.selection); - } - - function selectItem(selectedItem, $index, $event) { - listViewHelper.selectHandler(selectedItem, $index, $scope.items, $scope.selection, $event); - } - - function clickItem(item) { - $location.path($scope.entityType + '/' + $scope.entityType + '/edit/' + item.id); - } - - function isSortDirection(col, direction) { - return listViewHelper.setSortingDirection(col, direction, $scope.options); - } - - function sort(field, allow, isSystem) { - if (allow) { - $scope.options.orderBySystemField = isSystem; - listViewHelper.setSorting(field, allow, $scope.options); - $scope.getContent($scope.contentId); - } - } - - // Dropzone upload functions - function dragEnter(el, event) { - vm.activeDrag = true; - } - - function dragLeave(el, event) { - vm.activeDrag = false; - } - - function onFilesQueue() { - vm.activeDrag = false; - } - - function onUploadComplete() { - $scope.getContent($scope.contentId); - } - - } - - angular.module("umbraco").controller("Umbraco.PropertyEditors.ListView.ListLayoutController", ListViewListLayoutController); - -})(); +(function () { + "use strict"; + + function ListViewListLayoutController($scope, listViewHelper, $location, mediaHelper) { + + var vm = this; + + vm.nodeId = $scope.contentId; + //vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); + //instead of passing in a whitelist, we pass in a blacklist by adding ! to the ext + vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles).replace(/./g, "!."); + vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; + vm.activeDrag = false; + vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; + + vm.selectItem = selectItem; + vm.clickItem = clickItem; + vm.selectAll = selectAll; + vm.isSelectedAll = isSelectedAll; + vm.isSortDirection = isSortDirection; + vm.sort = sort; + vm.dragEnter = dragEnter; + vm.dragLeave = dragLeave; + vm.onFilesQueue = onFilesQueue; + vm.onUploadComplete = onUploadComplete; + + function selectAll($event) { + listViewHelper.selectAllItems($scope.items, $scope.selection, $event); + } + + function isSelectedAll() { + return listViewHelper.isSelectedAll($scope.items, $scope.selection); + } + + function selectItem(selectedItem, $index, $event) { + listViewHelper.selectHandler(selectedItem, $index, $scope.items, $scope.selection, $event); + } + + function clickItem(item) { + $location.path($scope.entityType + '/' +$scope.entityType + '/edit/' +item.id); + } + + function isSortDirection(col, direction) { + return listViewHelper.setSortingDirection(col, direction, $scope.options); + } + + function sort(field, allow, isSystem) { + if (allow) { + $scope.options.orderBySystemField = isSystem; + listViewHelper.setSorting(field, allow, $scope.options); + $scope.getContent($scope.contentId); + } + } + + // Dropzone upload functions + function dragEnter(el, event) { + vm.activeDrag = true; + } + + function dragLeave(el, event) { + vm.activeDrag = false; + } + + function onFilesQueue() { + vm.activeDrag = false; + } + + function onUploadComplete() { + $scope.getContent($scope.contentId); + } + + } + +angular.module("umbraco").controller("Umbraco.PropertyEditors.ListView.ListLayoutController", ListViewListLayoutController); + +}) (); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index fcb738a358..0d5451274c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -1,578 +1,578 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookieStore, notificationsService, iconHelper, dialogService, editorState, localizationService, $location, appState, $timeout, $q, mediaResource, listViewHelper, userService) { - //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content - // that isn't created yet, if we continue this will use the parent id in the route params which isn't what - // we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove - // the list view tab entirely when it's new. - if ($routeParams.create) { - $scope.isNew = true; - return; - } + //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content + // that isn't created yet, if we continue this will use the parent id in the route params which isn't what + // we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove + // the list view tab entirely when it's new. + if ($routeParams.create) { + $scope.isNew = true; + return; + } - //Now we need to check if this is for media, members or content because that will depend on the resources we use - var contentResource, getContentTypesCallback, getListResultsCallback, deleteItemCallback, getIdCallback, createEditUrlCallback; + //Now we need to check if this is for media, members or content because that will depend on the resources we use + var contentResource, getContentTypesCallback, getListResultsCallback, deleteItemCallback, getIdCallback, createEditUrlCallback; - //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) - if (($scope.model.config.entityType && $scope.model.config.entityType === "member") || (appState.getSectionState("currentSection") === "member")) { - $scope.entityType = "member"; - contentResource = $injector.get('memberResource'); - getContentTypesCallback = $injector.get('memberTypeResource').getTypes; - getListResultsCallback = contentResource.getPagedResults; - deleteItemCallback = contentResource.deleteByKey; - getIdCallback = function (selected) { - var selectedKey = getItemKey(selected.id); - return selectedKey; - }; - createEditUrlCallback = function (item) { - return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.key + "?page=" + $scope.options.pageNumber + "&listName=" + $scope.contentId; - }; - } - else { - //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) - if (($scope.model.config.entityType && $scope.model.config.entityType === "media") || (appState.getSectionState("currentSection") === "media")) { - $scope.entityType = "media"; - contentResource = $injector.get('mediaResource'); - getContentTypesCallback = $injector.get('mediaTypeResource').getAllowedTypes; - } - else { - $scope.entityType = "content"; - contentResource = $injector.get('contentResource'); - getContentTypesCallback = $injector.get('contentTypeResource').getAllowedTypes; - } - getListResultsCallback = contentResource.getChildren; - deleteItemCallback = contentResource.deleteById; - getIdCallback = function (selected) { - return selected.id; - }; - createEditUrlCallback = function (item) { - return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.id + "?page=" + $scope.options.pageNumber; - }; - } + //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) + if (($scope.model.config.entityType && $scope.model.config.entityType === "member") || (appState.getSectionState("currentSection") === "member")) { + $scope.entityType = "member"; + contentResource = $injector.get('memberResource'); + getContentTypesCallback = $injector.get('memberTypeResource').getTypes; + getListResultsCallback = contentResource.getPagedResults; + deleteItemCallback = contentResource.deleteByKey; + getIdCallback = function (selected) { + var selectedKey = getItemKey(selected.id); + return selectedKey; + }; + createEditUrlCallback = function (item) { + return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.key + "?page=" + $scope.options.pageNumber + "&listName=" + $scope.contentId; + }; + } + else { + //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) + if (($scope.model.config.entityType && $scope.model.config.entityType === "media") || (appState.getSectionState("currentSection") === "media")) { + $scope.entityType = "media"; + contentResource = $injector.get('mediaResource'); + getContentTypesCallback = $injector.get('mediaTypeResource').getAllowedTypes; + } + else { + $scope.entityType = "content"; + contentResource = $injector.get('contentResource'); + getContentTypesCallback = $injector.get('contentTypeResource').getAllowedTypes; + } + getListResultsCallback = contentResource.getChildren; + deleteItemCallback = contentResource.deleteById; + getIdCallback = function (selected) { + return selected.id; + }; + createEditUrlCallback = function (item) { + return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.id + "?page=" + $scope.options.pageNumber; + }; + } - $scope.pagination = []; - $scope.isNew = false; - $scope.actionInProgress = false; - $scope.selection = []; - $scope.folders = []; - $scope.listViewResultSet = { - totalPages: 0, - items: [] - }; - - $scope.currentNodePermissions = {} - - //Just ensure we do have an editorState - if (editorState.current) { - //Fetch current node allowed actions for the current user - //This is the current node & not each individual child node in the list - var currentUserPermissions = editorState.current.allowedActions; - - //Create a nicer model rather than the funky & hard to remember permissions strings - $scope.currentNodePermissions = { - "canCopy": _.contains(currentUserPermissions, 'O'), //Magic Char = O - "canCreate": _.contains(currentUserPermissions, 'C'), //Magic Char = C - "canDelete": _.contains(currentUserPermissions, 'D'), //Magic Char = D - "canMove": _.contains(currentUserPermissions, 'M'), //Magic Char = M - "canPublish": _.contains(currentUserPermissions, 'U'), //Magic Char = U - "canUnpublish": _.contains(currentUserPermissions, 'U'), //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish) - }; - } - - //when this is null, we don't check permissions - $scope.buttonPermissions = null; - - //When we are dealing with 'content', we need to deal with permissions on child nodes. - // Currently there is no real good way to - if ($scope.entityType === "content") { - - var idsWithPermissions = null; - - $scope.buttonPermissions = { - canCopy: true, - canCreate: true, - canDelete: true, - canMove: true, - canPublish: true, - canUnpublish: true - }; - - $scope.$watch(function() { - return $scope.selection.length; - }, function(newVal, oldVal) { - - if ((idsWithPermissions == null && newVal > 0) || (idsWithPermissions != null)) { - - //get all of the selected ids - var ids = _.map($scope.selection, function(i) { - return i.id.toString(); - }); - - //remove the dictionary items that don't have matching ids - var filtered = {}; - _.each(idsWithPermissions, function (value, key, list) { - if (_.contains(ids, key)) { - filtered[key] = value; - } - }); - idsWithPermissions = filtered; - - //find all ids that we haven't looked up permissions for - var existingIds = _.keys(idsWithPermissions); - var missingLookup = _.map(_.difference(ids, existingIds), function (i) { - return Number(i); - }); - - if (missingLookup.length > 0) { - contentResource.getPermissions(missingLookup).then(function(p) { - $scope.buttonPermissions = listViewHelper.getButtonPermissions(p, idsWithPermissions); - }); - } - else { - $scope.buttonPermissions = listViewHelper.getButtonPermissions({}, idsWithPermissions); - } - } - }); - - } - - $scope.options = { - displayAtTabNumber: $scope.model.config.displayAtTabNumber ? $scope.model.config.displayAtTabNumber : 1, - pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10, - pageNumber: ($routeParams.page && Number($routeParams.page) != NaN && Number($routeParams.page) > 0) ? $routeParams.page : 1, - filter: '', - orderBy: ($scope.model.config.orderBy ? $scope.model.config.orderBy : 'VersionDate').trim(), - orderDirection: $scope.model.config.orderDirection ? $scope.model.config.orderDirection.trim() : "desc", - orderBySystemField: true, - includeProperties: $scope.model.config.includeProperties ? $scope.model.config.includeProperties : [ - { alias: 'updateDate', header: 'Last edited', isSystem: 1 }, - { alias: 'updater', header: 'Last edited by', isSystem: 1 } - ], - layout: { - layouts: $scope.model.config.layouts, - activeLayout: listViewHelper.getLayout($routeParams.id, $scope.model.config.layouts) - }, - allowBulkPublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkPublish, - allowBulkUnpublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkUnpublish, - allowBulkCopy: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkCopy, - allowBulkMove: $scope.model.config.bulkActionPermissions.allowBulkMove, - allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete - }; - - //update all of the system includeProperties to enable sorting - _.each($scope.options.includeProperties, function (e, i) { - - - //NOTE: special case for contentTypeAlias, it's a system property that cannot be sorted - // to do that, we'd need to update the base query for content to include the content type alias column - // which requires another join and would be slower. BUT We are doing this for members so not sure it makes a diff? - if (e.alias != "contentTypeAlias") { - e.allowSorting = true; - } - - // Another special case for lasted edited data/update date for media, again this field isn't available on the base table so we can't sort by it - if (e.isSystem && $scope.entityType == "media") { - e.allowSorting = e.alias != 'updateDate'; - } - - // Another special case for members, only fields on the base table (cmsMember) can be used for sorting - if (e.isSystem && $scope.entityType == "member") { - e.allowSorting = e.alias == 'username' || e.alias == 'email'; - } - if (e.isSystem) { - - //localize the header - var key = getLocalizedKey(e.alias); - localizationService.localize(key).then(function (v) { - e.header = v; - }); - } - }); - - $scope.selectLayout = function (selectedLayout) { - $scope.options.layout.activeLayout = listViewHelper.setLayout($routeParams.id, selectedLayout, $scope.model.config.layouts); - }; - - function showNotificationsAndReset(err, reload, successMsg) { - - //check if response is ysod - if (err.status && err.status >= 500) { - - // Open ysod overlay - $scope.ysodOverlay = { - view: "ysod", - error: err, - show: true + $scope.pagination = []; + $scope.isNew = false; + $scope.actionInProgress = false; + $scope.selection = []; + $scope.folders = []; + $scope.listViewResultSet = { + totalPages: 0, + items: [] }; - } - $timeout(function () { - $scope.bulkStatus = ""; - $scope.actionInProgress = false; - }, 500); + $scope.currentNodePermissions = {} - if (reload === true) { - $scope.reloadView($scope.contentId); - } + //Just ensure we do have an editorState + if (editorState.current) { + //Fetch current node allowed actions for the current user + //This is the current node & not each individual child node in the list + var currentUserPermissions = editorState.current.allowedActions; - if (err.data && angular.isArray(err.data.notifications)) { - for (var i = 0; i < err.data.notifications.length; i++) { - notificationsService.showNotification(err.data.notifications[i]); - } - } - else if (successMsg) { - notificationsService.success("Done", successMsg); - } - } - - $scope.next = function (pageNumber) { - $scope.options.pageNumber = pageNumber; - $scope.reloadView($scope.contentId); - }; - - $scope.goToPage = function (pageNumber) { - $scope.options.pageNumber = pageNumber; - $scope.reloadView($scope.contentId); - }; - - $scope.prev = function (pageNumber) { - $scope.options.pageNumber = pageNumber; - $scope.reloadView($scope.contentId); - }; - - - /*Loads the search results, based on parameters set in prev,next,sort and so on*/ - /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state - with simple values */ - - $scope.reloadView = function (id) { - - $scope.viewLoaded = false; - - listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); - - getListResultsCallback(id, $scope.options).then(function (data) { - - $scope.actionInProgress = false; - $scope.listViewResultSet = data; - - //update all values for display - if ($scope.listViewResultSet.items) { - _.each($scope.listViewResultSet.items, function (e, index) { - setPropertyValues(e); - }); + //Create a nicer model rather than the funky & hard to remember permissions strings + $scope.currentNodePermissions = { + "canCopy": _.contains(currentUserPermissions, 'O'), //Magic Char = O + "canCreate": _.contains(currentUserPermissions, 'C'), //Magic Char = C + "canDelete": _.contains(currentUserPermissions, 'D'), //Magic Char = D + "canMove": _.contains(currentUserPermissions, 'M'), //Magic Char = M + "canPublish": _.contains(currentUserPermissions, 'U'), //Magic Char = U + "canUnpublish": _.contains(currentUserPermissions, 'U'), //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish) + }; } - if ($scope.entityType === 'media') { + //when this is null, we don't check permissions + $scope.buttonPermissions = null; - mediaResource.getChildFolders($scope.contentId) - .then(function (folders) { - $scope.folders = folders; - $scope.viewLoaded = true; - }); + //When we are dealing with 'content', we need to deal with permissions on child nodes. + // Currently there is no real good way to + if ($scope.entityType === "content") { + + var idsWithPermissions = null; + + $scope.buttonPermissions = { + canCopy: true, + canCreate: true, + canDelete: true, + canMove: true, + canPublish: true, + canUnpublish: true + }; + + $scope.$watch(function () { + return $scope.selection.length; + }, function (newVal, oldVal) { + + if ((idsWithPermissions == null && newVal > 0) || (idsWithPermissions != null)) { + + //get all of the selected ids + var ids = _.map($scope.selection, function (i) { + return i.id.toString(); + }); + + //remove the dictionary items that don't have matching ids + var filtered = {}; + _.each(idsWithPermissions, function (value, key, list) { + if (_.contains(ids, key)) { + filtered[key] = value; + } + }); + idsWithPermissions = filtered; + + //find all ids that we haven't looked up permissions for + var existingIds = _.keys(idsWithPermissions); + var missingLookup = _.map(_.difference(ids, existingIds), function (i) { + return Number(i); + }); + + if (missingLookup.length > 0) { + contentResource.getPermissions(missingLookup).then(function (p) { + $scope.buttonPermissions = listViewHelper.getButtonPermissions(p, idsWithPermissions); + }); + } + else { + $scope.buttonPermissions = listViewHelper.getButtonPermissions({}, idsWithPermissions); + } + } + }); - } else { - $scope.viewLoaded = true; } - //NOTE: This might occur if we are requesting a higher page number than what is actually available, for example - // if you have more than one page and you delete all items on the last page. In this case, we need to reset to the last - // available page and then re-load again - if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) { - $scope.options.pageNumber = $scope.listViewResultSet.totalPages; + $scope.options = { + displayAtTabNumber: $scope.model.config.displayAtTabNumber ? $scope.model.config.displayAtTabNumber : 1, + pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10, + pageNumber: ($routeParams.page && Number($routeParams.page) != NaN && Number($routeParams.page) > 0) ? $routeParams.page : 1, + filter: '', + orderBy: ($scope.model.config.orderBy ? $scope.model.config.orderBy : 'VersionDate').trim(), + orderDirection: $scope.model.config.orderDirection ? $scope.model.config.orderDirection.trim() : "desc", + orderBySystemField: true, + includeProperties: $scope.model.config.includeProperties ? $scope.model.config.includeProperties : [ + { alias: 'updateDate', header: 'Last edited', isSystem: 1 }, + { alias: 'updater', header: 'Last edited by', isSystem: 1 } + ], + layout: { + layouts: $scope.model.config.layouts, + activeLayout: listViewHelper.getLayout($routeParams.id, $scope.model.config.layouts) + }, + allowBulkPublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkPublish, + allowBulkUnpublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkUnpublish, + allowBulkCopy: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkCopy, + allowBulkMove: $scope.model.config.bulkActionPermissions.allowBulkMove, + allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete + }; - //reload! - $scope.reloadView(id); + //update all of the system includeProperties to enable sorting + _.each($scope.options.includeProperties, function (e, i) { + + + //NOTE: special case for contentTypeAlias, it's a system property that cannot be sorted + // to do that, we'd need to update the base query for content to include the content type alias column + // which requires another join and would be slower. BUT We are doing this for members so not sure it makes a diff? + if (e.alias != "contentTypeAlias") { + e.allowSorting = true; + } + + // Another special case for lasted edited data/update date for media, again this field isn't available on the base table so we can't sort by it + if (e.isSystem && $scope.entityType == "media") { + e.allowSorting = e.alias != 'updateDate'; + } + + // Another special case for members, only fields on the base table (cmsMember) can be used for sorting + if (e.isSystem && $scope.entityType == "member") { + e.allowSorting = e.alias == 'username' || e.alias == 'email'; + } + if (e.isSystem) { + + //localize the header + var key = getLocalizedKey(e.alias); + localizationService.localize(key).then(function (v) { + e.header = v; + }); + } + }); + + $scope.selectLayout = function (selectedLayout) { + $scope.options.layout.activeLayout = listViewHelper.setLayout($routeParams.id, selectedLayout, $scope.model.config.layouts); + }; + + function showNotificationsAndReset(err, reload, successMsg) { + + //check if response is ysod + if (err.status && err.status >= 500) { + + // Open ysod overlay + $scope.ysodOverlay = { + view: "ysod", + error: err, + show: true + }; + } + + $timeout(function () { + $scope.bulkStatus = ""; + $scope.actionInProgress = false; + }, 500); + + if (reload === true) { + $scope.reloadView($scope.contentId); + } + + if (err.data && angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + else if (successMsg) { + notificationsService.success("Done", successMsg); + } } - }); - }; + $scope.next = function (pageNumber) { + $scope.options.pageNumber = pageNumber; + $scope.reloadView($scope.contentId); + }; - var searchListView = _.debounce(function () { - $scope.$apply(function () { - makeSearch(); - }); - }, 500); + $scope.goToPage = function (pageNumber) { + $scope.options.pageNumber = pageNumber; + $scope.reloadView($scope.contentId); + }; - $scope.forceSearch = function (ev) { - //13: enter - switch (ev.keyCode) { - case 13: - makeSearch(); - break; - } - }; + $scope.prev = function (pageNumber) { + $scope.options.pageNumber = pageNumber; + $scope.reloadView($scope.contentId); + }; - $scope.enterSearch = function () { - $scope.viewLoaded = false; - searchListView(); - }; - function makeSearch() { - if ($scope.options.filter !== null && $scope.options.filter !== undefined) { - $scope.options.pageNumber = 1; - //$scope.actionInProgress = true; - $scope.reloadView($scope.contentId); - } - } + /*Loads the search results, based on parameters set in prev,next,sort and so on*/ + /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state + with simple values */ - $scope.isAnythingSelected = function () { - if ($scope.selection.length === 0) { - return false; - } else { - return true; - } - }; + $scope.reloadView = function (id) { - $scope.selectedItemsCount = function () { - return $scope.selection.length; - }; + $scope.viewLoaded = false; - $scope.clearSelection = function () { - listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); - }; + listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); - $scope.getIcon = function (entry) { - return iconHelper.convertFromLegacyIcon(entry.icon); - }; + getListResultsCallback(id, $scope.options).then(function (data) { - function serial(selected, fn, getStatusMsg, index) { - return fn(selected, index).then(function (content) { - index++; - $scope.bulkStatus = getStatusMsg(index, selected.length); - return index < selected.length ? serial(selected, fn, getStatusMsg, index) : content; - }, function (err) { - var reload = index > 0; - showNotificationsAndReset(err, reload); - return err; - }); - } + $scope.actionInProgress = false; + $scope.listViewResultSet = data; - function applySelected(fn, getStatusMsg, getSuccessMsg, confirmMsg) { - var selected = $scope.selection; - if (selected.length === 0) - return; - if (confirmMsg && !confirm(confirmMsg)) - return; + //update all values for display + if ($scope.listViewResultSet.items) { + _.each($scope.listViewResultSet.items, function (e, index) { + setPropertyValues(e); + }); + } - $scope.actionInProgress = true; - $scope.bulkStatus = getStatusMsg(0, selected.length); + if ($scope.entityType === 'media') { - serial(selected, fn, getStatusMsg, 0).then(function (result) { - // executes once the whole selection has been processed - // in case of an error (caught by serial), result will be the error - if (!(result.data && angular.isArray(result.data.notifications))) - showNotificationsAndReset(result, true, getSuccessMsg(selected.length)); - }); - } + mediaResource.getChildFolders($scope.contentId) + .then(function (folders) { + $scope.folders = folders; + $scope.viewLoaded = true; + }); - $scope.delete = function () { - applySelected( - function (selected, index) { return deleteItemCallback(getIdCallback(selected[index])); }, - function (count, total) { return "Deleted " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Deleted " + total + " item" + (total > 1 ? "s" : ""); }, - "Sure you want to delete?"); - }; + } else { + $scope.viewLoaded = true; + } - $scope.publish = function () { - applySelected( - function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); }, - function (count, total) { return "Published " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Published " + total + " item" + (total > 1 ? "s" : ""); }); - }; + //NOTE: This might occur if we are requesting a higher page number than what is actually available, for example + // if you have more than one page and you delete all items on the last page. In this case, we need to reset to the last + // available page and then re-load again + if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) { + $scope.options.pageNumber = $scope.listViewResultSet.totalPages; - $scope.unpublish = function () { - applySelected( - function (selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); }, - function (count, total) { return "Unpublished " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Unpublished " + total + " item" + (total > 1 ? "s" : ""); }); - }; + //reload! + $scope.reloadView(id); + } - $scope.move = function () { - $scope.moveDialog = {}; - $scope.moveDialog.title = "Move"; - $scope.moveDialog.section = $scope.entityType; - $scope.moveDialog.currentNode = $scope.contentId; - $scope.moveDialog.view = "move"; - $scope.moveDialog.show = true; + }); + }; - $scope.moveDialog.submit = function (model) { + var searchListView = _.debounce(function () { + $scope.$apply(function () { + makeSearch(); + }); + }, 500); - if (model.target) { - performMove(model.target); + $scope.forceSearch = function (ev) { + //13: enter + switch (ev.keyCode) { + case 13: + makeSearch(); + break; + } + }; + + $scope.enterSearch = function () { + $scope.viewLoaded = false; + searchListView(); + }; + + function makeSearch() { + if ($scope.options.filter !== null && $scope.options.filter !== undefined) { + $scope.options.pageNumber = 1; + //$scope.actionInProgress = true; + $scope.reloadView($scope.contentId); + } } - $scope.moveDialog.show = false; - $scope.moveDialog = null; - }; + $scope.isAnythingSelected = function () { + if ($scope.selection.length === 0) { + return false; + } else { + return true; + } + }; - $scope.moveDialog.close = function (oldModel) { - $scope.moveDialog.show = false; - $scope.moveDialog = null; - }; + $scope.selectedItemsCount = function () { + return $scope.selection.length; + }; - }; + $scope.clearSelection = function () { + listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); + }; - function performMove(target) { + $scope.getIcon = function (entry) { + return iconHelper.convertFromLegacyIcon(entry.icon); + }; - applySelected( - function (selected, index) { return contentResource.move({ parentId: target.id, id: getIdCallback(selected[index]) }); }, - function (count, total) { return "Moved " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Moved " + total + " item" + (total > 1 ? "s" : ""); }); - } - - $scope.copy = function () { - $scope.copyDialog = {}; - $scope.copyDialog.title = "Copy"; - $scope.copyDialog.section = $scope.entityType; - $scope.copyDialog.currentNode = $scope.contentId; - $scope.copyDialog.view = "copy"; - $scope.copyDialog.show = true; - - $scope.copyDialog.submit = function (model) { - if (model.target) { - performCopy(model.target, model.relateToOriginal); + function serial(selected, fn, getStatusMsg, index) { + return fn(selected, index).then(function (content) { + index++; + $scope.bulkStatus = getStatusMsg(index, selected.length); + return index < selected.length ? serial(selected, fn, getStatusMsg, index) : content; + }, function (err) { + var reload = index > 0; + showNotificationsAndReset(err, reload); + return err; + }); } - $scope.copyDialog.show = false; - $scope.copyDialog = null; - }; + function applySelected(fn, getStatusMsg, getSuccessMsg, confirmMsg) { + var selected = $scope.selection; + if (selected.length === 0) + return; + if (confirmMsg && !confirm(confirmMsg)) + return; - $scope.copyDialog.close = function (oldModel) { - $scope.copyDialog.show = false; - $scope.copyDialog = null; - }; + $scope.actionInProgress = true; + $scope.bulkStatus = getStatusMsg(0, selected.length); - }; - - function performCopy(target, relateToOriginal) { - applySelected( - function (selected, index) { return contentResource.copy({ parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal }); }, - function (count, total) { return "Copied " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Copied " + total + " item" + (total > 1 ? "s" : ""); }); - } - - function getCustomPropertyValue(alias, properties) { - var value = ''; - var index = 0; - var foundAlias = false; - for (var i = 0; i < properties.length; i++) { - if (properties[i].alias == alias) { - foundAlias = true; - break; - } - index++; - } - - if (foundAlias) { - value = properties[index].value; - } - - return value; - } - - /** This ensures that the correct value is set for each item in a row, we don't want to call a function during interpolation or ng-bind as performance is really bad that way */ - function setPropertyValues(result) { - - //set the edit url - result.editPath = createEditUrlCallback(result); - - _.each($scope.options.includeProperties, function (e, i) { - - var alias = e.alias; - - // First try to pull the value directly from the alias (e.g. updatedBy) - var value = result[alias]; - - // If this returns an object, look for the name property of that (e.g. owner.name) - if (value === Object(value)) { - value = value['name']; + serial(selected, fn, getStatusMsg, 0).then(function (result) { + // executes once the whole selection has been processed + // in case of an error (caught by serial), result will be the error + if (!(result.data && angular.isArray(result.data.notifications))) + showNotificationsAndReset(result, true, getSuccessMsg(selected.length)); + }); } - // If we've got nothing yet, look at a user defined property - if (typeof value === 'undefined') { - value = getCustomPropertyValue(alias, result.properties); + $scope.delete = function () { + applySelected( + function (selected, index) { return deleteItemCallback(getIdCallback(selected[index])); }, + function (count, total) { return "Deleted " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Deleted " + total + " item" + (total > 1 ? "s" : ""); }, + "Sure you want to delete?"); + }; + + $scope.publish = function () { + applySelected( + function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); }, + function (count, total) { return "Published " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Published " + total + " item" + (total > 1 ? "s" : ""); }); + }; + + $scope.unpublish = function () { + applySelected( + function (selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); }, + function (count, total) { return "Unpublished " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Unpublished " + total + " item" + (total > 1 ? "s" : ""); }); + }; + + $scope.move = function () { + $scope.moveDialog = {}; + $scope.moveDialog.title = "Move"; + $scope.moveDialog.section = $scope.entityType; + $scope.moveDialog.currentNode = $scope.contentId; + $scope.moveDialog.view = "move"; + $scope.moveDialog.show = true; + + $scope.moveDialog.submit = function (model) { + + if (model.target) { + performMove(model.target); + } + + $scope.moveDialog.show = false; + $scope.moveDialog = null; + }; + + $scope.moveDialog.close = function (oldModel) { + $scope.moveDialog.show = false; + $scope.moveDialog = null; + }; + + }; + + function performMove(target) { + + applySelected( + function (selected, index) { return contentResource.move({ parentId: target.id, id: getIdCallback(selected[index]) }); }, + function (count, total) { return "Moved " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Moved " + total + " item" + (total > 1 ? "s" : ""); }); } - // If we have a date, format it - if (isDate(value)) { - value = value.substring(0, value.length - 3); + $scope.copy = function () { + $scope.copyDialog = {}; + $scope.copyDialog.title = "Copy"; + $scope.copyDialog.section = $scope.entityType; + $scope.copyDialog.currentNode = $scope.contentId; + $scope.copyDialog.view = "copy"; + $scope.copyDialog.show = true; + + $scope.copyDialog.submit = function (model) { + if (model.target) { + performCopy(model.target, model.relateToOriginal); + } + + $scope.copyDialog.show = false; + $scope.copyDialog = null; + }; + + $scope.copyDialog.close = function (oldModel) { + $scope.copyDialog.show = false; + $scope.copyDialog = null; + }; + + }; + + function performCopy(target, relateToOriginal) { + applySelected( + function (selected, index) { return contentResource.copy({ parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal }); }, + function (count, total) { return "Copied " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Copied " + total + " item" + (total > 1 ? "s" : ""); }); } - // set what we've got on the result - result[alias] = value; - }); + function getCustomPropertyValue(alias, properties) { + var value = ''; + var index = 0; + var foundAlias = false; + for (var i = 0; i < properties.length; i++) { + if (properties[i].alias == alias) { + foundAlias = true; + break; + } + index++; + } + if (foundAlias) { + value = properties[index].value; + } - } - - function isDate(val) { - if (angular.isString(val)) { - return val.match(/^(\d{4})\-(\d{2})\-(\d{2})\ (\d{2})\:(\d{2})\:(\d{2})$/); - } - return false; - } - - function initView() { - //default to root id if the id is undefined - var id = $routeParams.id; - if (id === undefined) { - id = -1; - } - - $scope.listViewAllowedTypes = getContentTypesCallback(id); - - $scope.contentId = id; - $scope.isTrashed = id === "-20" || id === "-21"; - - $scope.options.allowBulkPublish = $scope.options.allowBulkPublish && !$scope.isTrashed; - $scope.options.allowBulkUnpublish = $scope.options.allowBulkUnpublish && !$scope.isTrashed; - - $scope.options.bulkActionsAllowed = $scope.options.allowBulkPublish || - $scope.options.allowBulkUnpublish || - $scope.options.allowBulkCopy || - $scope.options.allowBulkMove || - $scope.options.allowBulkDelete; - - $scope.reloadView($scope.contentId); - } - - function getLocalizedKey(alias) { - - switch (alias) { - case "sortOrder": - return "general_sort"; - case "updateDate": - return "content_updateDate"; - case "updater": - return "content_updatedBy"; - case "createDate": - return "content_createDate"; - case "owner": - return "content_createBy"; - case "published": - return "content_isPublished"; - case "contentTypeAlias": - //TODO: Check for members - return $scope.entityType === "content" ? "content_documentType" : "content_mediatype"; - case "email": - return "general_email"; - case "username": - return "general_username"; - } - return alias; - } - - function getItemKey(itemId) { - for (var i = 0; i < $scope.listViewResultSet.items.length; i++) { - var item = $scope.listViewResultSet.items[i]; - if (item.id === itemId) { - return item.key; + return value; } - } - } - //GO! - initView(); + /** This ensures that the correct value is set for each item in a row, we don't want to call a function during interpolation or ng-bind as performance is really bad that way */ + function setPropertyValues(result) { + + //set the edit url + result.editPath = createEditUrlCallback(result); + + _.each($scope.options.includeProperties, function (e, i) { + + var alias = e.alias; + + // First try to pull the value directly from the alias (e.g. updatedBy) + var value = result[alias]; + + // If this returns an object, look for the name property of that (e.g. owner.name) + if (value === Object(value)) { + value = value['name']; + } + + // If we've got nothing yet, look at a user defined property + if (typeof value === 'undefined') { + value = getCustomPropertyValue(alias, result.properties); + } + + // If we have a date, format it + if (isDate(value)) { + value = value.substring(0, value.length - 3); + } + + // set what we've got on the result + result[alias] = value; + }); + + + } + + function isDate(val) { + if (angular.isString(val)) { + return val.match(/^(\d{4})\-(\d{2})\-(\d{2})\ (\d{2})\:(\d{2})\:(\d{2})$/); + } + return false; + } + + function initView() { + //default to root id if the id is undefined + var id = $routeParams.id; + if (id === undefined) { + id = -1; + } + + $scope.listViewAllowedTypes = getContentTypesCallback(id); + + $scope.contentId = id; + $scope.isTrashed = id === "-20" || id === "-21"; + + $scope.options.allowBulkPublish = $scope.options.allowBulkPublish && !$scope.isTrashed; + $scope.options.allowBulkUnpublish = $scope.options.allowBulkUnpublish && !$scope.isTrashed; + + $scope.options.bulkActionsAllowed = $scope.options.allowBulkPublish || + $scope.options.allowBulkUnpublish || + $scope.options.allowBulkCopy || + $scope.options.allowBulkMove || + $scope.options.allowBulkDelete; + + $scope.reloadView($scope.contentId); + } + + function getLocalizedKey(alias) { + + switch (alias) { + case "sortOrder": + return "general_sort"; + case "updateDate": + return "content_updateDate"; + case "updater": + return "content_updatedBy"; + case "createDate": + return "content_createDate"; + case "owner": + return "content_createBy"; + case "published": + return "content_isPublished"; + case "contentTypeAlias": + //TODO: Check for members + return $scope.entityType === "content" ? "content_documentType" : "content_mediatype"; + case "email": + return "general_email"; + case "username": + return "general_username"; + } + return alias; + } + + function getItemKey(itemId) { + for (var i = 0; i < $scope.listViewResultSet.items.length; i++) { + var item = $scope.listViewResultSet.items[i]; + if (item.id === itemId) { + return item.key; + } + } + } + + //GO! + initView(); } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 217d3123b8..580e1a018c 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -38,769 +38,769 @@ using Notification = Umbraco.Web.Models.ContentEditing.Notification; namespace Umbraco.Web.Editors { - /// - /// The API controller used for editing content - /// - /// - /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting - /// access to ALL of the methods on this controller will need access to the content application. - /// - [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content)] - public class ContentController : ContentControllerBase - { - /// - /// Constructor - /// - public ContentController() - : this(UmbracoContext.Current) - { - } - - /// - /// Constructor - /// - /// - public ContentController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - } - - /// - /// Return content for the specified ids - /// - /// - /// - [FilterAllowedOutgoingContent(typeof(IEnumerable))] - public IEnumerable GetByIds([FromUri]int[] ids) - { - var foundContent = Services.ContentService.GetByIds(ids); - return foundContent.Select(Mapper.Map); - } - - /// - /// Returns an item to be used to display the recycle bin for content - /// - /// - public ContentItemDisplay GetRecycleBin() - { - var display = new ContentItemDisplay - { - Id = Constants.System.RecycleBinContent, - Alias = "recycleBin", - ParentId = -1, - Name = Services.TextService.Localize("general/recycleBin"), - ContentTypeAlias = "recycleBin", - CreateDate = DateTime.Now, - IsContainer = true, - Path = "-1," + Constants.System.RecycleBinContent - }; - - TabsAndPropertiesResolver.AddListView(display, "content", Services.DataTypeService, Services.TextService); - - return display; - } - - /// - /// Gets the content json for the content id - /// - /// - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetById(int id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - if (foundContent == null) - { - HandleContentNotFound(id); - } - - var content = Mapper.Map(foundContent); - return content; - } - - [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetWithTreeDefinition(int id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - if (foundContent == null) - { - HandleContentNotFound(id); - } - - var content = Mapper.Map(foundContent); - return content; - } - - /// - /// Gets an empty content item for the - /// - /// - /// - /// - /// If this is a container type, we'll remove the umbContainerView tab for a new item since - /// it cannot actually list children if it doesn't exist yet. - /// - [OutgoingEditorModelEvent] - public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId) - { - var contentType = Services.ContentTypeService.GetContentType(contentTypeAlias); - if (contentType == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var emptyContent = Services.ContentService.CreateContent("", parentId, contentType.Alias, UmbracoUser.Id); - var mapped = Mapper.Map(emptyContent); - - //remove this tab if it exists: umbContainerView - var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); - mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); - return mapped; - } - - /// - /// Gets the Url for a given node ID - /// - /// - /// - public HttpResponseMessage GetNiceUrl(int id) - { - var url = Umbraco.NiceUrl(id); - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(url, Encoding.UTF8, "application/json"); - return response; - } - - /// - /// Gets the children for the content id passed in - /// - /// - [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren( - int id, - int pageNumber = 0, //TODO: This should be '1' as it's not the index - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - int? orderBySystemField = 2, - string filter = "") - { - var orderBySystemFieldBool = orderBySystemField == 1 || orderBySystemField == 2; - long totalChildren; - IContent[] children; - if (pageNumber > 0 && pageSize > 0) - { - children = Services.ContentService - .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren - , orderBy, orderDirection, orderBySystemFieldBool, filter).ToArray(); - } - else - { - children = Services.ContentService.GetChildren(id).ToArray(); - totalChildren = children.Length; - } - - if (totalChildren == 0) - { - return new PagedResult>(0, 0, 0); - } - - var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); - pagedResult.Items = children - .Select(Mapper.Map>); - - return pagedResult; - } - - [Obsolete("Dont use this, it is incorrectly named, use HasPermission instead")] - public bool GetHasPermission(string permissionToCheck, int nodeId) - { - return HasPermission(permissionToCheck, nodeId); - } - - /// - /// Returns permissions for all nodes passed in for the current user - /// - /// - /// - [HttpPost] - public Dictionary GetPermissions(int[] nodeIds) - { - return Services.UserService - .GetPermissions(Security.CurrentUser, nodeIds) - .ToDictionary(x => x.EntityId, x => x.AssignedPermissions); - } - - [HttpGet] - public bool HasPermission(string permissionToCheck, int nodeId) - { - var p = Services.UserService.GetPermissions(Security.CurrentUser, nodeId).FirstOrDefault(); - if (p != null && p.AssignedPermissions.Contains(permissionToCheck.ToString(CultureInfo.InvariantCulture))) - { - return true; - } - - return false; - } - - /// - /// Saves content - /// - /// - [FileUploadCleanupFilter] - [ContentPostValidate] - public ContentItemDisplay PostSave( - [ModelBinder(typeof(ContentItemBinder))] - ContentItemSave contentItem) - { - //If we've reached here it means: - // * Our model has been bound - // * and validated - // * any file attachments have been saved to their temporary location for us to use - // * we have a reference to the DTO object and the persisted object - // * Permissions are valid - - MapPropertyValues(contentItem); - - //We need to manually check the validation results here because: - // * We still need to save the entity even if there are validation value errors - // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) - // then we cannot continue saving, we can only display errors - // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display - // a message indicating this - if (ModelState.IsValid == false) - { - if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) && IsCreatingAction(contentItem.Action)) - { - //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! - // add the modelstate to the outgoing object and throw a validation message - var forDisplay = Mapper.Map(contentItem.PersistedContent); - forDisplay.Errors = ModelState.ToErrorDictionary(); - throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); - - } - - //if the model state is not valid we cannot publish so change it to save - switch (contentItem.Action) - { - case ContentSaveAction.Publish: - contentItem.Action = ContentSaveAction.Save; - break; - case ContentSaveAction.PublishNew: - contentItem.Action = ContentSaveAction.SaveNew; - break; - } - } - - //initialize this to successful - var publishStatus = Attempt.Succeed(); - var wasCancelled = false; - - if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew) - { - //save the item - var saveResult = Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id); - - wasCancelled = saveResult.Success == false && saveResult.Result.StatusType == OperationStatusType.FailedCancelledByEvent; - } - else if (contentItem.Action == ContentSaveAction.SendPublish || contentItem.Action == ContentSaveAction.SendPublishNew) - { - var sendResult = Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id); - wasCancelled = sendResult == false; - } - else - { - //publish the item and check if it worked, if not we will show a diff msg below - publishStatus = Services.ContentService.SaveAndPublishWithStatus(contentItem.PersistedContent, Security.CurrentUser.Id); - wasCancelled = publishStatus.Result.StatusType == PublishStatusType.FailedCancelledByEvent; - } - - //return the updated model - var display = Mapper.Map(contentItem.PersistedContent); - - //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 - HandleInvalidModelState(display); - - //put the correct msgs in - switch (contentItem.Action) - { - case ContentSaveAction.Save: - case ContentSaveAction.SaveNew: - if (wasCancelled == false) - { - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentSavedHeader"), - Services.TextService.Localize("speechBubbles/editContentSavedText")); - } - else - { - AddCancelMessage(display); - } - break; - case ContentSaveAction.SendPublish: - case ContentSaveAction.SendPublishNew: - if (wasCancelled == false) - { - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentSendToPublish"), - Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); - } - else - { - AddCancelMessage(display); - } - break; - case ContentSaveAction.Publish: - case ContentSaveAction.PublishNew: - ShowMessageForPublishStatus(publishStatus.Result, display); - break; - } - - UpdatePreviewContext(contentItem.PersistedContent.Id); - - //If the item is new and the operation was cancelled, we need to return a different - // status code so the UI can handle it since it won't be able to redirect since there - // is no Id to redirect to! - if (wasCancelled && IsCreatingAction(contentItem.Action)) - { - throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); - } - - return display; - } - - /// - /// Publishes a document with a given ID - /// - /// - /// - /// - /// The CanAccessContentAuthorize attribute will deny access to this method if the current user - /// does not have Publish access to this node. - /// - /// - [EnsureUserPermissionForContent("id", 'U')] - public HttpResponseMessage PostPublishById(int id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - - if (foundContent == null) - { - return HandleContentNotFound(id, false); - } - - var publishResult = Services.ContentService.PublishWithStatus(foundContent, Security.GetUserId()); - if (publishResult.Success == false) - { - var notificationModel = new SimpleNotificationModel(); - ShowMessageForPublishStatus(publishResult.Result, notificationModel); - return Request.CreateValidationErrorResponse(notificationModel); - } - - //return ok - return Request.CreateResponse(HttpStatusCode.OK); - - } - - /// - /// Moves an item to the recycle bin, if it is already there then it will permanently delete it - /// - /// - /// - /// - /// The CanAccessContentAuthorize attribute will deny access to this method if the current user - /// does not have Delete access to this node. - /// - [EnsureUserPermissionForContent("id", 'D')] - [HttpDelete] - [HttpPost] - public HttpResponseMessage DeleteById(int id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - - if (foundContent == null) - { - return HandleContentNotFound(id, false); - } - - //if the current item is in the recycle bin - if (foundContent.IsInRecycleBin() == false) - { - var moveResult = Services.ContentService.WithResult().MoveToRecycleBin(foundContent, Security.GetUserId()); - if (moveResult == false) - { - //returning an object of INotificationModel will ensure that any pending - // notification messages are added to the response. - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } - } - else - { - var deleteResult = Services.ContentService.WithResult().Delete(foundContent, Security.GetUserId()); - if (deleteResult == false) - { - //returning an object of INotificationModel will ensure that any pending - // notification messages are added to the response. - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } - } - - return Request.CreateResponse(HttpStatusCode.OK); - } - - /// - /// Empties the recycle bin - /// - /// - /// - /// attributed with EnsureUserPermissionForContent to verify the user has access to the recycle bin - /// - [HttpDelete] - [HttpPost] - [EnsureUserPermissionForContent(Constants.System.RecycleBinContent)] - public HttpResponseMessage EmptyRecycleBin() - { - Services.ContentService.EmptyRecycleBin(); - return Request.CreateResponse(HttpStatusCode.OK); - } - - /// - /// Change the sort order for media - /// - /// - /// - [EnsureUserPermissionForContent("sorted.ParentId", 'S')] - public HttpResponseMessage PostSort(ContentSortOrder sorted) - { - if (sorted == null) - { - return Request.CreateResponse(HttpStatusCode.NotFound); - } - - //if there's nothing to sort just return ok - if (sorted.IdSortOrder.Length == 0) - { - return Request.CreateResponse(HttpStatusCode.OK); - } - - var contentService = Services.ContentService; - var sortedContent = new List(); - try - { - sortedContent.AddRange(Services.ContentService.GetByIds(sorted.IdSortOrder)); - - // Save content with new sort order and update content xml in db accordingly - if (contentService.Sort(sortedContent) == false) - { - LogHelper.Warn("Content sorting failed, this was probably caused by an event being cancelled"); - return Request.CreateValidationErrorResponse("Content sorting failed, this was probably caused by an event being cancelled"); - } - return Request.CreateResponse(HttpStatusCode.OK); - } - catch (Exception ex) - { - LogHelper.Error("Could not update content sort order", ex); - throw; - } - } - - /// - /// Change the sort order for media - /// - /// - /// - [EnsureUserPermissionForContent("move.ParentId", 'M')] - public HttpResponseMessage PostMove(MoveOrCopy move) - { - var toMove = ValidateMoveOrCopy(move); - - Services.ContentService.Move(toMove, move.ParentId); - - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); - return response; - } - - /// - /// Copies a content item and places the copy as a child of a given parent Id - /// - /// - /// - [EnsureUserPermissionForContent("copy.ParentId", 'C')] - public HttpResponseMessage PostCopy(MoveOrCopy copy) - { - var toCopy = ValidateMoveOrCopy(copy); - - var c = Services.ContentService.Copy(toCopy, copy.ParentId, copy.RelateToOriginal, copy.Recursive); - - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(c.Path, Encoding.UTF8, "application/json"); - return response; - } - - /// - /// Unpublishes a node with a given Id and returns the unpublished entity - /// - /// - /// - [EnsureUserPermissionForContent("id", 'U')] - public ContentItemDisplay PostUnPublish(int id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - - if (foundContent == null) - HandleContentNotFound(id); - - var unpublishResult = Services.ContentService.WithResult().UnPublish(foundContent, Security.CurrentUser.Id); - - var content = Mapper.Map(foundContent); - - if (unpublishResult == false) - { - AddCancelMessage(content); - throw new HttpResponseException(Request.CreateValidationErrorResponse(content)); - } - else - { - content.AddSuccessNotification(Services.TextService.Localize("content/unPublish"), Services.TextService.Localize("speechBubbles/contentUnpublished")); - return content; - } - } - - /// - /// Checks if the user is currently in preview mode and if so will update the preview content for this item - /// - /// - private void UpdatePreviewContext(int contentId) - { - var previewId = Request.GetPreviewCookieValue(); - if (previewId.IsNullOrWhiteSpace()) return; - Guid id; - if (Guid.TryParse(previewId, out id)) - { - var d = new Document(contentId); - var pc = new PreviewContent(UmbracoUser, id, false); - pc.PrepareDocument(UmbracoUser, d, true); - pc.SavePreviewSet(); - } - } - - /// - /// Maps the dto property values to the persisted model - /// - /// - private void MapPropertyValues(ContentItemSave contentItem) - { - UpdateName(contentItem); - - //TODO: We need to support 'send to publish' - - contentItem.PersistedContent.ExpireDate = contentItem.ExpireDate; - contentItem.PersistedContent.ReleaseDate = contentItem.ReleaseDate; - //only set the template if it didn't change - var templateChanged = (contentItem.PersistedContent.Template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) - || (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias) - || (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace()); - if (templateChanged) - { - var template = Services.FileService.GetTemplate(contentItem.TemplateAlias); - if (template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) - { - //ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias); - LogHelper.Warn("No template exists with the specified alias: " + contentItem.TemplateAlias); - } - else - { - //NOTE: this could be null if there was a template and the posted template is null, this should remove the assigned template - contentItem.PersistedContent.Template = template; - } - } - - base.MapPropertyValues(contentItem); - } - - /// - /// Ensures the item can be moved/copied to the new location - /// - /// - /// - private IContent ValidateMoveOrCopy(MoveOrCopy model) - { - if (model == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var contentService = Services.ContentService; - var toMove = contentService.GetById(model.Id); - if (toMove == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - if (model.ParentId < 0) - { - //cannot move if the content item is not allowed at the root - if (toMove.ContentType.AllowedAsRoot == false) - { - throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"))); - } - } - else - { - var parent = contentService.GetById(model.ParentId); - if (parent == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - //check if the item is allowed under this one - if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() - .Any(x => x.Value == toMove.ContentType.Id) == false) - { - throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("moveOrCopy/notAllowedByContentType"))); - } - - // Check on paths - if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) - { - throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("moveOrCopy/notAllowedByPath"))); - } - } - - return toMove; - } - - private void ShowMessageForPublishStatus(PublishStatus status, INotificationModel display) - { - switch (status.StatusType) - { - case PublishStatusType.Success: - case PublishStatusType.SuccessAlreadyPublished: - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), - Services.TextService.Localize("speechBubbles/editContentPublishedText")); - break; - case PublishStatusType.FailedPathNotPublished: - display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedByParent", - new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); - break; - case PublishStatusType.FailedCancelledByEvent: - AddCancelMessage(display, "publish", "speechBubbles/contentPublishedFailedByEvent"); - break; - case PublishStatusType.FailedAwaitingRelease: - display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease", - new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); - break; - case PublishStatusType.FailedHasExpired: - display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedExpired", - new[] - { - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), - }).Trim()); - break; - case PublishStatusType.FailedIsTrashed: - //TODO: We should add proper error messaging for this! - break; - case PublishStatusType.FailedContentInvalid: - display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedInvalid", - new[] - { - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), - string.Join(",", status.InvalidProperties.Select(x => x.Alias)) - }).Trim()); - break; - default: - throw new IndexOutOfRangeException(); - } - } - - - - /// - /// Performs a permissions check for the user to check if it has access to the node based on - /// start node and/or permissions for the node - /// - /// The storage to add the content item to so it can be reused - /// - /// - /// - /// The content to lookup, if the contentItem is not specified - /// - /// Specifies the already resolved content item to check against - /// - internal static bool CheckPermissions( - IDictionary storage, - IUser user, - IUserService userService, - IContentService contentService, - int nodeId, - char[] permissionsToCheck = null, - IContent contentItem = null) - { - - if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) - { - contentItem = contentService.GetById(nodeId); - //put the content item into storage so it can be retreived - // in the controller (saves a lookup) - storage[typeof(IContent).ToString()] = contentItem; - } - - if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var hasPathAccess = (nodeId == Constants.System.Root) - ? UserExtensions.HasPathAccess( - Constants.System.Root.ToInvariantString(), - user.StartContentId, - Constants.System.RecycleBinContent) - : (nodeId == Constants.System.RecycleBinContent) - ? UserExtensions.HasPathAccess( - Constants.System.RecycleBinContent.ToInvariantString(), - user.StartContentId, - Constants.System.RecycleBinContent) - : user.HasPathAccess(contentItem); - - if (hasPathAccess == false) - { - return false; - } - - if (permissionsToCheck == null || permissionsToCheck.Any() == false) - { - return true; - } - - var permission = userService.GetPermissions(user, nodeId).FirstOrDefault(); - - var allowed = true; - foreach (var p in permissionsToCheck) - { - if (permission == null || permission.AssignedPermissions.Contains(p.ToString(CultureInfo.InvariantCulture)) == false) - { - allowed = false; - } - } - return allowed; - } - - } + /// + /// The API controller used for editing content + /// + /// + /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting + /// access to ALL of the methods on this controller will need access to the content application. + /// + [PluginController("UmbracoApi")] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content)] + public class ContentController : ContentControllerBase + { + /// + /// Constructor + /// + public ContentController() + : this(UmbracoContext.Current) + { + } + + /// + /// Constructor + /// + /// + public ContentController(UmbracoContext umbracoContext) + : base(umbracoContext) + { + } + + /// + /// Return content for the specified ids + /// + /// + /// + [FilterAllowedOutgoingContent(typeof(IEnumerable))] + public IEnumerable GetByIds([FromUri]int[] ids) + { + var foundContent = Services.ContentService.GetByIds(ids); + return foundContent.Select(Mapper.Map); + } + + /// + /// Returns an item to be used to display the recycle bin for content + /// + /// + public ContentItemDisplay GetRecycleBin() + { + var display = new ContentItemDisplay + { + Id = Constants.System.RecycleBinContent, + Alias = "recycleBin", + ParentId = -1, + Name = Services.TextService.Localize("general/recycleBin"), + ContentTypeAlias = "recycleBin", + CreateDate = DateTime.Now, + IsContainer = true, + Path = "-1," + Constants.System.RecycleBinContent + }; + + TabsAndPropertiesResolver.AddListView(display, "content", Services.DataTypeService, Services.TextService); + + return display; + } + + /// + /// Gets the content json for the content id + /// + /// + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForContent("id")] + public ContentItemDisplay GetById(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + if (foundContent == null) + { + HandleContentNotFound(id); + } + + var content = Mapper.Map(foundContent); + return content; + } + + [EnsureUserPermissionForContent("id")] + public ContentItemDisplay GetWithTreeDefinition(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + if (foundContent == null) + { + HandleContentNotFound(id); + } + + var content = Mapper.Map(foundContent); + return content; + } + + /// + /// Gets an empty content item for the + /// + /// + /// + /// + /// If this is a container type, we'll remove the umbContainerView tab for a new item since + /// it cannot actually list children if it doesn't exist yet. + /// + [OutgoingEditorModelEvent] + public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId) + { + var contentType = Services.ContentTypeService.GetContentType(contentTypeAlias); + if (contentType == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var emptyContent = Services.ContentService.CreateContent("", parentId, contentType.Alias, UmbracoUser.Id); + var mapped = Mapper.Map(emptyContent); + + //remove this tab if it exists: umbContainerView + var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); + mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); + return mapped; + } + + /// + /// Gets the Url for a given node ID + /// + /// + /// + public HttpResponseMessage GetNiceUrl(int id) + { + var url = Umbraco.NiceUrl(id); + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(url, Encoding.UTF8, "application/json"); + return response; + } + + /// + /// Gets the children for the content id passed in + /// + /// + [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren( + int id, + int pageNumber = 0, //TODO: This should be '1' as it's not the index + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + int? orderBySystemField = 2, + string filter = "") + { + var orderBySystemFieldBool = orderBySystemField == 1 || orderBySystemField == 2; + long totalChildren; + IContent[] children; + if (pageNumber > 0 && pageSize > 0) + { + children = Services.ContentService + .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren + , orderBy, orderDirection, orderBySystemFieldBool, filter).ToArray(); + } + else + { + children = Services.ContentService.GetChildren(id).ToArray(); + totalChildren = children.Length; + } + + if (totalChildren == 0) + { + return new PagedResult>(0, 0, 0); + } + + var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); + pagedResult.Items = children + .Select(Mapper.Map>); + + return pagedResult; + } + + [Obsolete("Dont use this, it is incorrectly named, use HasPermission instead")] + public bool GetHasPermission(string permissionToCheck, int nodeId) + { + return HasPermission(permissionToCheck, nodeId); + } + + /// + /// Returns permissions for all nodes passed in for the current user + /// + /// + /// + [HttpPost] + public Dictionary GetPermissions(int[] nodeIds) + { + return Services.UserService + .GetPermissions(Security.CurrentUser, nodeIds) + .ToDictionary(x => x.EntityId, x => x.AssignedPermissions); + } + + [HttpGet] + public bool HasPermission(string permissionToCheck, int nodeId) + { + var p = Services.UserService.GetPermissions(Security.CurrentUser, nodeId).FirstOrDefault(); + if (p != null && p.AssignedPermissions.Contains(permissionToCheck.ToString(CultureInfo.InvariantCulture))) + { + return true; + } + + return false; + } + + /// + /// Saves content + /// + /// + [FileUploadCleanupFilter] + [ContentPostValidate] + public ContentItemDisplay PostSave( + [ModelBinder(typeof(ContentItemBinder))] + ContentItemSave contentItem) + { + //If we've reached here it means: + // * Our model has been bound + // * and validated + // * any file attachments have been saved to their temporary location for us to use + // * we have a reference to the DTO object and the persisted object + // * Permissions are valid + + MapPropertyValues(contentItem); + + //We need to manually check the validation results here because: + // * We still need to save the entity even if there are validation value errors + // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) + // then we cannot continue saving, we can only display errors + // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display + // a message indicating this + if (ModelState.IsValid == false) + { + if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) && IsCreatingAction(contentItem.Action)) + { + //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! + // add the modelstate to the outgoing object and throw a validation message + var forDisplay = Mapper.Map(contentItem.PersistedContent); + forDisplay.Errors = ModelState.ToErrorDictionary(); + throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); + + } + + //if the model state is not valid we cannot publish so change it to save + switch (contentItem.Action) + { + case ContentSaveAction.Publish: + contentItem.Action = ContentSaveAction.Save; + break; + case ContentSaveAction.PublishNew: + contentItem.Action = ContentSaveAction.SaveNew; + break; + } + } + + //initialize this to successful + var publishStatus = Attempt.Succeed(); + var wasCancelled = false; + + if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew) + { + //save the item + var saveResult = Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id); + + wasCancelled = saveResult.Success == false && saveResult.Result.StatusType == OperationStatusType.FailedCancelledByEvent; + } + else if (contentItem.Action == ContentSaveAction.SendPublish || contentItem.Action == ContentSaveAction.SendPublishNew) + { + var sendResult = Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id); + wasCancelled = sendResult == false; + } + else + { + //publish the item and check if it worked, if not we will show a diff msg below + publishStatus = Services.ContentService.SaveAndPublishWithStatus(contentItem.PersistedContent, Security.CurrentUser.Id); + wasCancelled = publishStatus.Result.StatusType == PublishStatusType.FailedCancelledByEvent; + } + + //return the updated model + var display = Mapper.Map(contentItem.PersistedContent); + + //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 + HandleInvalidModelState(display); + + //put the correct msgs in + switch (contentItem.Action) + { + case ContentSaveAction.Save: + case ContentSaveAction.SaveNew: + if (wasCancelled == false) + { + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentSavedHeader"), + Services.TextService.Localize("speechBubbles/editContentSavedText")); + } + else + { + AddCancelMessage(display); + } + break; + case ContentSaveAction.SendPublish: + case ContentSaveAction.SendPublishNew: + if (wasCancelled == false) + { + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentSendToPublish"), + Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); + } + else + { + AddCancelMessage(display); + } + break; + case ContentSaveAction.Publish: + case ContentSaveAction.PublishNew: + ShowMessageForPublishStatus(publishStatus.Result, display); + break; + } + + UpdatePreviewContext(contentItem.PersistedContent.Id); + + //If the item is new and the operation was cancelled, we need to return a different + // status code so the UI can handle it since it won't be able to redirect since there + // is no Id to redirect to! + if (wasCancelled && IsCreatingAction(contentItem.Action)) + { + throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); + } + + return display; + } + + /// + /// Publishes a document with a given ID + /// + /// + /// + /// + /// The CanAccessContentAuthorize attribute will deny access to this method if the current user + /// does not have Publish access to this node. + /// + /// + [EnsureUserPermissionForContent("id", 'U')] + public HttpResponseMessage PostPublishById(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + + if (foundContent == null) + { + return HandleContentNotFound(id, false); + } + + var publishResult = Services.ContentService.PublishWithStatus(foundContent, Security.GetUserId()); + if (publishResult.Success == false) + { + var notificationModel = new SimpleNotificationModel(); + ShowMessageForPublishStatus(publishResult.Result, notificationModel); + return Request.CreateValidationErrorResponse(notificationModel); + } + + //return ok + return Request.CreateResponse(HttpStatusCode.OK); + + } + + /// + /// Moves an item to the recycle bin, if it is already there then it will permanently delete it + /// + /// + /// + /// + /// The CanAccessContentAuthorize attribute will deny access to this method if the current user + /// does not have Delete access to this node. + /// + [EnsureUserPermissionForContent("id", 'D')] + [HttpDelete] + [HttpPost] + public HttpResponseMessage DeleteById(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + + if (foundContent == null) + { + return HandleContentNotFound(id, false); + } + + //if the current item is in the recycle bin + if (foundContent.IsInRecycleBin() == false) + { + var moveResult = Services.ContentService.WithResult().MoveToRecycleBin(foundContent, Security.GetUserId()); + if (moveResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } + } + else + { + var deleteResult = Services.ContentService.WithResult().Delete(foundContent, Security.GetUserId()); + if (deleteResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } + } + + return Request.CreateResponse(HttpStatusCode.OK); + } + + /// + /// Empties the recycle bin + /// + /// + /// + /// attributed with EnsureUserPermissionForContent to verify the user has access to the recycle bin + /// + [HttpDelete] + [HttpPost] + [EnsureUserPermissionForContent(Constants.System.RecycleBinContent)] + public HttpResponseMessage EmptyRecycleBin() + { + Services.ContentService.EmptyRecycleBin(); + return Request.CreateResponse(HttpStatusCode.OK); + } + + /// + /// Change the sort order for media + /// + /// + /// + [EnsureUserPermissionForContent("sorted.ParentId", 'S')] + public HttpResponseMessage PostSort(ContentSortOrder sorted) + { + if (sorted == null) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + //if there's nothing to sort just return ok + if (sorted.IdSortOrder.Length == 0) + { + return Request.CreateResponse(HttpStatusCode.OK); + } + + var contentService = Services.ContentService; + var sortedContent = new List(); + try + { + sortedContent.AddRange(Services.ContentService.GetByIds(sorted.IdSortOrder)); + + // Save content with new sort order and update content xml in db accordingly + if (contentService.Sort(sortedContent) == false) + { + LogHelper.Warn("Content sorting failed, this was probably caused by an event being cancelled"); + return Request.CreateValidationErrorResponse("Content sorting failed, this was probably caused by an event being cancelled"); + } + return Request.CreateResponse(HttpStatusCode.OK); + } + catch (Exception ex) + { + LogHelper.Error("Could not update content sort order", ex); + throw; + } + } + + /// + /// Change the sort order for media + /// + /// + /// + [EnsureUserPermissionForContent("move.ParentId", 'M')] + public HttpResponseMessage PostMove(MoveOrCopy move) + { + var toMove = ValidateMoveOrCopy(move); + + Services.ContentService.Move(toMove, move.ParentId); + + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); + return response; + } + + /// + /// Copies a content item and places the copy as a child of a given parent Id + /// + /// + /// + [EnsureUserPermissionForContent("copy.ParentId", 'C')] + public HttpResponseMessage PostCopy(MoveOrCopy copy) + { + var toCopy = ValidateMoveOrCopy(copy); + + var c = Services.ContentService.Copy(toCopy, copy.ParentId, copy.RelateToOriginal, copy.Recursive); + + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(c.Path, Encoding.UTF8, "application/json"); + return response; + } + + /// + /// Unpublishes a node with a given Id and returns the unpublished entity + /// + /// + /// + [EnsureUserPermissionForContent("id", 'U')] + public ContentItemDisplay PostUnPublish(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + + if (foundContent == null) + HandleContentNotFound(id); + + var unpublishResult = Services.ContentService.WithResult().UnPublish(foundContent, Security.CurrentUser.Id); + + var content = Mapper.Map(foundContent); + + if (unpublishResult == false) + { + AddCancelMessage(content); + throw new HttpResponseException(Request.CreateValidationErrorResponse(content)); + } + else + { + content.AddSuccessNotification(Services.TextService.Localize("content/unPublish"), Services.TextService.Localize("speechBubbles/contentUnpublished")); + return content; + } + } + + /// + /// Checks if the user is currently in preview mode and if so will update the preview content for this item + /// + /// + private void UpdatePreviewContext(int contentId) + { + var previewId = Request.GetPreviewCookieValue(); + if (previewId.IsNullOrWhiteSpace()) return; + Guid id; + if (Guid.TryParse(previewId, out id)) + { + var d = new Document(contentId); + var pc = new PreviewContent(UmbracoUser, id, false); + pc.PrepareDocument(UmbracoUser, d, true); + pc.SavePreviewSet(); + } + } + + /// + /// Maps the dto property values to the persisted model + /// + /// + private void MapPropertyValues(ContentItemSave contentItem) + { + UpdateName(contentItem); + + //TODO: We need to support 'send to publish' + + contentItem.PersistedContent.ExpireDate = contentItem.ExpireDate; + contentItem.PersistedContent.ReleaseDate = contentItem.ReleaseDate; + //only set the template if it didn't change + var templateChanged = (contentItem.PersistedContent.Template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) + || (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias) + || (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace()); + if (templateChanged) + { + var template = Services.FileService.GetTemplate(contentItem.TemplateAlias); + if (template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) + { + //ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias); + LogHelper.Warn("No template exists with the specified alias: " + contentItem.TemplateAlias); + } + else + { + //NOTE: this could be null if there was a template and the posted template is null, this should remove the assigned template + contentItem.PersistedContent.Template = template; + } + } + + base.MapPropertyValues(contentItem); + } + + /// + /// Ensures the item can be moved/copied to the new location + /// + /// + /// + private IContent ValidateMoveOrCopy(MoveOrCopy model) + { + if (model == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var contentService = Services.ContentService; + var toMove = contentService.GetById(model.Id); + if (toMove == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + if (model.ParentId < 0) + { + //cannot move if the content item is not allowed at the root + if (toMove.ContentType.AllowedAsRoot == false) + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"))); + } + } + else + { + var parent = contentService.GetById(model.ParentId); + if (parent == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + //check if the item is allowed under this one + if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() + .Any(x => x.Value == toMove.ContentType.Id) == false) + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedByContentType"))); + } + + // Check on paths + if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) + { + throw new HttpResponseException( + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedByPath"))); + } + } + + return toMove; + } + + private void ShowMessageForPublishStatus(PublishStatus status, INotificationModel display) + { + switch (status.StatusType) + { + case PublishStatusType.Success: + case PublishStatusType.SuccessAlreadyPublished: + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), + Services.TextService.Localize("speechBubbles/editContentPublishedText")); + break; + case PublishStatusType.FailedPathNotPublished: + display.AddWarningNotification( + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedByParent", + new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); + break; + case PublishStatusType.FailedCancelledByEvent: + AddCancelMessage(display, "publish", "speechBubbles/contentPublishedFailedByEvent"); + break; + case PublishStatusType.FailedAwaitingRelease: + display.AddWarningNotification( + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease", + new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); + break; + case PublishStatusType.FailedHasExpired: + display.AddWarningNotification( + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedExpired", + new[] + { + string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), + }).Trim()); + break; + case PublishStatusType.FailedIsTrashed: + //TODO: We should add proper error messaging for this! + break; + case PublishStatusType.FailedContentInvalid: + display.AddWarningNotification( + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedInvalid", + new[] + { + string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), + string.Join(",", status.InvalidProperties.Select(x => x.Alias)) + }).Trim()); + break; + default: + throw new IndexOutOfRangeException(); + } + } + + + + /// + /// Performs a permissions check for the user to check if it has access to the node based on + /// start node and/or permissions for the node + /// + /// The storage to add the content item to so it can be reused + /// + /// + /// + /// The content to lookup, if the contentItem is not specified + /// + /// Specifies the already resolved content item to check against + /// + internal static bool CheckPermissions( + IDictionary storage, + IUser user, + IUserService userService, + IContentService contentService, + int nodeId, + char[] permissionsToCheck = null, + IContent contentItem = null) + { + + if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) + { + contentItem = contentService.GetById(nodeId); + //put the content item into storage so it can be retreived + // in the controller (saves a lookup) + storage[typeof(IContent).ToString()] = contentItem; + } + + if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var hasPathAccess = (nodeId == Constants.System.Root) + ? UserExtensions.HasPathAccess( + Constants.System.Root.ToInvariantString(), + user.StartContentId, + Constants.System.RecycleBinContent) + : (nodeId == Constants.System.RecycleBinContent) + ? UserExtensions.HasPathAccess( + Constants.System.RecycleBinContent.ToInvariantString(), + user.StartContentId, + Constants.System.RecycleBinContent) + : user.HasPathAccess(contentItem); + + if (hasPathAccess == false) + { + return false; + } + + if (permissionsToCheck == null || permissionsToCheck.Any() == false) + { + return true; + } + + var permission = userService.GetPermissions(user, nodeId).FirstOrDefault(); + + var allowed = true; + foreach (var p in permissionsToCheck) + { + if (permission == null || permission.AssignedPermissions.Contains(p.ToString(CultureInfo.InvariantCulture)) == false) + { + allowed = false; + } + } + return allowed; + } + + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index da7c72a289..060fb06750 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -39,675 +39,675 @@ using Notification = Umbraco.Web.Models.ContentEditing.Notification; namespace Umbraco.Web.Editors { - /// - /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting - /// access to ALL of the methods on this controller will need access to the media application. - /// - [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Media)] - public class MediaController : ContentControllerBase - { - /// - /// Constructor - /// - public MediaController() - : this(UmbracoContext.Current) - { - } - - /// - /// Constructor - /// - /// - public MediaController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - } - - /// - /// Gets an empty content item for the - /// - /// - /// - /// - [OutgoingEditorModelEvent] - public MediaItemDisplay GetEmpty(string contentTypeAlias, int parentId) - { - var contentType = Services.ContentTypeService.GetMediaType(contentTypeAlias); - if (contentType == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var emptyContent = Services.MediaService.CreateMedia("", parentId, contentType.Alias, UmbracoUser.Id); - var mapped = Mapper.Map(emptyContent); - - //remove this tab if it exists: umbContainerView - var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); - mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); - return mapped; - } - - /// - /// Returns an item to be used to display the recycle bin for media - /// - /// - public ContentItemDisplay GetRecycleBin() - { - var display = new ContentItemDisplay - { - Id = Constants.System.RecycleBinMedia, - Alias = "recycleBin", - ParentId = -1, - Name = Services.TextService.Localize("general/recycleBin"), - ContentTypeAlias = "recycleBin", - CreateDate = DateTime.Now, - IsContainer = true, - Path = "-1," + Constants.System.RecycleBinMedia - }; - - TabsAndPropertiesResolver.AddListView(display, "media", Services.DataTypeService, Services.TextService); - - return display; - } - - /// - /// Gets the content json for the content id - /// - /// - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForMedia("id")] - public MediaItemDisplay GetById(int id) - { - var foundContent = GetObjectFromRequest(() => Services.MediaService.GetById(id)); - - if (foundContent == null) - { - HandleContentNotFound(id); - //HandleContentNotFound will throw an exception - return null; - } - return Mapper.Map(foundContent); - } - - /// - /// Return media for the specified ids - /// - /// - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable))] - public IEnumerable GetByIds([FromUri]int[] ids) - { - var foundMedia = Services.MediaService.GetByIds(ids); - return foundMedia.Select(Mapper.Map); - } - - /// - /// Returns media items known to be a container of other media items - /// - /// - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] - public IEnumerable> GetChildFolders(int id = -1) - { - //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 children = (id < 0) ? Services.MediaService.GetRootMedia() : Services.MediaService.GetById(id).Children(); - return children.Where(x => folderTypes.Contains(x.ContentTypeId)).Select(Mapper.Map>); - } - - /// - /// Returns the root media objects - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] - public IEnumerable> GetRootMedia() - { - //TODO: Add permissions check! - - return Services.MediaService.GetRootMedia() - .Select(Mapper.Map>); - } - - /// - /// Returns the child media objects - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(int id, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - int totalChildren; - IMedia[] children; - if (pageNumber > 0 && pageSize > 0) - { - children = Services.MediaService - .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren - , orderBy, orderDirection, orderBySystemField, filter).ToArray(); - } - else - { - children = Services.MediaService.GetChildren(id).ToArray(); - totalChildren = children.Length; - } - - if (totalChildren == 0) - { - return new PagedResult>(0, 0, 0); - } - - var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); - pagedResult.Items = children - .Select(Mapper.Map>); - - return pagedResult; - } - - /// - /// Moves an item to the recycle bin, if it is already there then it will permanently delete it - /// - /// - /// - [EnsureUserPermissionForMedia("id")] - [HttpPost] - public HttpResponseMessage DeleteById(int id) - { - var foundMedia = GetObjectFromRequest(() => Services.MediaService.GetById(id)); - - if (foundMedia == null) - { - return HandleContentNotFound(id, false); - } - - //if the current item is in the recycle bin - if (foundMedia.IsInRecycleBin() == false) - { - var moveResult = Services.MediaService.WithResult().MoveToRecycleBin(foundMedia, (int)Security.CurrentUser.Id); - if (moveResult == false) - { - //returning an object of INotificationModel will ensure that any pending - // notification messages are added to the response. - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } - } - else - { - var deleteResult = Services.MediaService.WithResult().Delete(foundMedia, (int)Security.CurrentUser.Id); - if (deleteResult == false) - { - //returning an object of INotificationModel will ensure that any pending - // notification messages are added to the response. - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } - } - - return Request.CreateResponse(HttpStatusCode.OK); - } - - /// - /// Change the sort order for media - /// - /// - /// - [EnsureUserPermissionForMedia("move.Id")] - public HttpResponseMessage PostMove(MoveOrCopy move) - { - var toMove = ValidateMoveOrCopy(move); - - Services.MediaService.Move(toMove, move.ParentId); - - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); - return response; - } - - /// - /// Saves content - /// - /// - [FileUploadCleanupFilter] - [MediaPostValidate] - public MediaItemDisplay PostSave( - [ModelBinder(typeof(MediaItemBinder))] - MediaItemSave contentItem) - { - //If we've reached here it means: - // * Our model has been bound - // * and validated - // * any file attachments have been saved to their temporary location for us to use - // * we have a reference to the DTO object and the persisted object - // * Permissions are valid - - MapPropertyValues(contentItem); - - //We need to manually check the validation results here because: - // * We still need to save the entity even if there are validation value errors - // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) - // then we cannot continue saving, we can only display errors - // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display - // a message indicating this - if (ModelState.IsValid == false) - { - if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) - && (contentItem.Action == ContentSaveAction.SaveNew)) - { - //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! - // add the modelstate to the outgoing object and throw validation response - var forDisplay = Mapper.Map(contentItem.PersistedContent); - forDisplay.Errors = ModelState.ToErrorDictionary(); - throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); - } - } - - //save the item - var saveStatus = Services.MediaService.WithResult().Save(contentItem.PersistedContent, (int)Security.CurrentUser.Id); - - //return the updated model - var display = Mapper.Map(contentItem.PersistedContent); - - //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 - HandleInvalidModelState(display); - - //put the correct msgs in - switch (contentItem.Action) - { - case ContentSaveAction.Save: - case ContentSaveAction.SaveNew: - if (saveStatus.Success) - { - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editMediaSaved"), - Services.TextService.Localize("speechBubbles/editMediaSavedText")); - } - else - { - AddCancelMessage(display); - - //If the item is new and the operation was cancelled, we need to return a different - // status code so the UI can handle it since it won't be able to redirect since there - // is no Id to redirect to! - if (saveStatus.Result.StatusType == OperationStatusType.FailedCancelledByEvent && IsCreatingAction(contentItem.Action)) - { - throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); - } - } - - break; - } - - return display; - } - - /// - /// Maps the property values to the persisted entity - /// - /// - protected override void MapPropertyValues(ContentBaseItemSave contentItem) - { - UpdateName(contentItem); - - //use the base method to map the rest of the properties - base.MapPropertyValues(contentItem); - } - - /// - /// Empties the recycle bin - /// - /// - [HttpDelete] - [HttpPost] - public HttpResponseMessage EmptyRecycleBin() - { - Services.MediaService.EmptyRecycleBin(); - return Request.CreateResponse(HttpStatusCode.OK); - } - - /// - /// Change the sort order for media - /// - /// - /// - [EnsureUserPermissionForMedia("sorted.ParentId")] - public HttpResponseMessage PostSort(ContentSortOrder sorted) - { - if (sorted == null) - { - return Request.CreateResponse(HttpStatusCode.NotFound); - } - - //if there's nothing to sort just return ok - if (sorted.IdSortOrder.Length == 0) - { - return Request.CreateResponse(HttpStatusCode.OK); - } - - var mediaService = base.ApplicationContext.Services.MediaService; - var sortedMedia = new List(); - try - { - sortedMedia.AddRange(sorted.IdSortOrder.Select(mediaService.GetById)); - - // Save Media with new sort order and update content xml in db accordingly - if (mediaService.Sort(sortedMedia) == false) - { - LogHelper.Warn("Media sorting failed, this was probably caused by an event being cancelled"); - return Request.CreateValidationErrorResponse("Media sorting failed, this was probably caused by an event being cancelled"); - } - return Request.CreateResponse(HttpStatusCode.OK); - } - catch (Exception ex) - { - LogHelper.Error("Could not update media sort order", ex); - throw; - } - } - - [EnsureUserPermissionForMedia("folder.ParentId")] - public MediaItemDisplay PostAddFolder(EntityBasic folder) - { - var mediaService = ApplicationContext.Services.MediaService; - var f = mediaService.CreateMedia(folder.Name, folder.ParentId, Constants.Conventions.MediaTypes.Folder); - mediaService.Save(f, Security.CurrentUser.Id); - - return Mapper.Map(f); - } - - /// - /// Used to submit a media file - /// - /// - /// - /// We cannot validate this request with attributes (nicely) due to the nature of the multi-part for data. - /// - [FileUploadCleanupFilter(false)] - public async Task PostAddFile() - { - if (Request.Content.IsMimeMultipartContent() == false) - { - throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); - } - - var root = IOHelper.MapPath("~/App_Data/TEMP/FileUploads"); - //ensure it exists - Directory.CreateDirectory(root); - var provider = new MultipartFormDataStreamProvider(root); - - var result = await Request.Content.ReadAsMultipartAsync(provider); - - //must have a file - if (result.FileData.Count == 0) - { - return Request.CreateResponse(HttpStatusCode.NotFound); - } - - //get the string json from the request - int parentId; - if (int.TryParse(result.FormData["currentFolder"], out parentId) == false) - { - return Request.CreateValidationErrorResponse("The request was not formatted correctly, the currentFolder is not an integer"); - } - - //ensure the user has access to this folder by parent id! - if (CheckPermissions( - new Dictionary(), - Security.CurrentUser, - Services.MediaService, parentId) == false) - { - return Request.CreateResponse( - HttpStatusCode.Unauthorized, - new SimpleNotificationModel(new Notification( - Services.TextService.Localize("speechBubbles/operationFailedHeader"), - Services.TextService.Localize("speechBubbles/invalidUserPermissionsText"), - SpeechBubbleIcon.Warning))); - } - - var tempFiles = new PostedFiles(); - var mediaService = ApplicationContext.Services.MediaService; - - - //in case we pass a path with a folder in it, we will create it and upload media to it. - if (result.FormData.ContainsKey("path")) - { - - var folders = result.FormData["path"].Split('/'); - - for (int i = 0; i < folders.Length - 1; i++) - { - var folderName = folders[i]; - IMedia folderMediaItem; - - //if uploading directly to media root and not a subfolder - if (parentId == -1) - { - //look for matching folder - folderMediaItem = - mediaService.GetRootMedia().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); - if (folderMediaItem == null) - { - //if null, create a folder - folderMediaItem = mediaService.CreateMedia(folderName, -1, Constants.Conventions.MediaTypes.Folder); - mediaService.Save(folderMediaItem); - } - } - else - { - //get current parent - var mediaRoot = mediaService.GetById(parentId); - - //if the media root is null, something went wrong, we'll abort - if (mediaRoot == null) - return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, - "The folder: " + folderName + " could not be used for storing images, its ID: " + parentId + - " returned null"); - - //look for matching folder - folderMediaItem = mediaRoot.Children().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); - if (folderMediaItem == null) - { - //if null, create a folder - folderMediaItem = mediaService.CreateMedia(folderName, mediaRoot, Constants.Conventions.MediaTypes.Folder); - mediaService.Save(folderMediaItem); - } - } - //set the media root to the folder id so uploaded files will end there. - parentId = folderMediaItem.Id; - } - } - - //get the files - foreach (var file in result.FileData) - { - var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }); - var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower(); - - if (UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext) == false) - { - var mediaType = Constants.Conventions.MediaTypes.File; - - if (UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.Contains(ext)) - mediaType = Constants.Conventions.MediaTypes.Image; - - //TODO: make the media item name "nice" since file names could be pretty ugly, we have - // string extensions to do much of this but we'll need: - // * Pascalcase the name (use string extensions) - // * strip the file extension - // * underscores to spaces - // * probably remove 'ugly' characters - let's discuss - // All of this logic should exist in a string extensions method and be unit tested - // http://issues.umbraco.org/issue/U4-5572 - var mediaItemName = fileName; - - var f = mediaService.CreateMedia(mediaItemName, parentId, mediaType, Security.CurrentUser.Id); - - var fileInfo = new FileInfo(file.LocalFileName); - var fs = fileInfo.OpenReadWithRetry(); - if (fs == null) throw new InvalidOperationException("Could not acquire file stream"); - using (fs) - { - f.SetValue(Constants.Conventions.Media.File, fileName, fs); - } - - var saveResult = mediaService.WithResult().Save(f, Security.CurrentUser.Id); - if (saveResult == false) - { - AddCancelMessage(tempFiles, - message: Services.TextService.Localize("speechBubbles/operationCancelledText") + " -- " + mediaItemName, - localizeMessage: false); - } - else - { - tempFiles.UploadedFiles.Add(new ContentItemFile - { - FileName = fileName, - PropertyAlias = Constants.Conventions.Media.File, - TempFilePath = file.LocalFileName - }); - } - } - else - { - tempFiles.Notifications.Add(new Notification( - Services.TextService.Localize("speechBubbles/operationFailedHeader"), - "Cannot upload file " + file.Headers.ContentDisposition.FileName + ", it is not an approved file type", - SpeechBubbleIcon.Warning)); - } - } - - //Different response if this is a 'blueimp' request - if (Request.GetQueryNameValuePairs().Any(x => x.Key == "origin")) - { - var origin = Request.GetQueryNameValuePairs().First(x => x.Key == "origin"); - if (origin.Value == "blueimp") - { - return Request.CreateResponse(HttpStatusCode.OK, - tempFiles, - //Don't output the angular xsrf stuff, blue imp doesn't like that - new JsonMediaTypeFormatter()); - } - } - - return Request.CreateResponse(HttpStatusCode.OK, tempFiles); - } - - /// - /// This is used for the response of PostAddFile so that we can analyze the response in a filter and remove the - /// temporary files that were created. - /// - [DataContract] - private class PostedFiles : IHaveUploadedFiles, INotificationModel - { - public PostedFiles() - { - UploadedFiles = new List(); - Notifications = new List(); - } - public List UploadedFiles { get; private set; } - - [DataMember(Name = "notifications")] - public List Notifications { get; private set; } - } - - /// - /// Ensures the item can be moved/copied to the new location - /// - /// - /// - private IMedia ValidateMoveOrCopy(MoveOrCopy model) - { - if (model == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var mediaService = Services.MediaService; - var toMove = mediaService.GetById(model.Id); - if (toMove == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - if (model.ParentId < 0) - { - //cannot move if the content item is not allowed at the root - if (toMove.ContentType.AllowedAsRoot == false) - { - var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"), ""); - throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); - } - } - else - { - var parent = mediaService.GetById(model.ParentId); - if (parent == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - //check if the item is allowed under this one - if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() - .Any(x => x.Value == toMove.ContentType.Id) == false) - { - var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByContentType"), ""); - throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); - } - - // Check on paths - if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) - { - var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByPath"), ""); - throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); - } - } - - return toMove; - } - - /// - /// Performs a permissions check for the user to check if it has access to the node based on - /// start node and/or permissions for the node - /// - /// The storage to add the content item to so it can be reused - /// - /// - /// The content to lookup, if the contentItem is not specified - /// Specifies the already resolved content item to check against, setting this ignores the nodeId - /// - internal static bool CheckPermissions(IDictionary storage, IUser user, IMediaService mediaService, int nodeId, IMedia media = null) - { - if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) - { - media = mediaService.GetById(nodeId); - //put the content item into storage so it can be retreived - // in the controller (saves a lookup) - storage[typeof(IMedia).ToString()] = media; - } - - if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - var hasPathAccess = (nodeId == Constants.System.Root) - ? UserExtensions.HasPathAccess( - Constants.System.Root.ToInvariantString(), - user.StartMediaId, - Constants.System.RecycleBinMedia) - : (nodeId == Constants.System.RecycleBinMedia) - ? UserExtensions.HasPathAccess( - Constants.System.RecycleBinMedia.ToInvariantString(), - user.StartMediaId, - Constants.System.RecycleBinMedia) - : user.HasPathAccess(media); - - return hasPathAccess; - } - } + /// + /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting + /// access to ALL of the methods on this controller will need access to the media application. + /// + [PluginController("UmbracoApi")] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Media)] + public class MediaController : ContentControllerBase + { + /// + /// Constructor + /// + public MediaController() + : this(UmbracoContext.Current) + { + } + + /// + /// Constructor + /// + /// + public MediaController(UmbracoContext umbracoContext) + : base(umbracoContext) + { + } + + /// + /// Gets an empty content item for the + /// + /// + /// + /// + [OutgoingEditorModelEvent] + public MediaItemDisplay GetEmpty(string contentTypeAlias, int parentId) + { + var contentType = Services.ContentTypeService.GetMediaType(contentTypeAlias); + if (contentType == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var emptyContent = Services.MediaService.CreateMedia("", parentId, contentType.Alias, UmbracoUser.Id); + var mapped = Mapper.Map(emptyContent); + + //remove this tab if it exists: umbContainerView + var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); + mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); + return mapped; + } + + /// + /// Returns an item to be used to display the recycle bin for media + /// + /// + public ContentItemDisplay GetRecycleBin() + { + var display = new ContentItemDisplay + { + Id = Constants.System.RecycleBinMedia, + Alias = "recycleBin", + ParentId = -1, + Name = Services.TextService.Localize("general/recycleBin"), + ContentTypeAlias = "recycleBin", + CreateDate = DateTime.Now, + IsContainer = true, + Path = "-1," + Constants.System.RecycleBinMedia + }; + + TabsAndPropertiesResolver.AddListView(display, "media", Services.DataTypeService, Services.TextService); + + return display; + } + + /// + /// Gets the content json for the content id + /// + /// + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForMedia("id")] + public MediaItemDisplay GetById(int id) + { + var foundContent = GetObjectFromRequest(() => Services.MediaService.GetById(id)); + + if (foundContent == null) + { + HandleContentNotFound(id); + //HandleContentNotFound will throw an exception + return null; + } + return Mapper.Map(foundContent); + } + + /// + /// Return media for the specified ids + /// + /// + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable))] + public IEnumerable GetByIds([FromUri]int[] ids) + { + var foundMedia = Services.MediaService.GetByIds(ids); + return foundMedia.Select(Mapper.Map); + } + + /// + /// Returns media items known to be a container of other media items + /// + /// + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] + public IEnumerable> GetChildFolders(int id = -1) + { + //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 children = (id < 0) ? Services.MediaService.GetRootMedia() : Services.MediaService.GetById(id).Children(); + return children.Where(x => folderTypes.Contains(x.ContentTypeId)).Select(Mapper.Map>); + } + + /// + /// Returns the root media objects + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] + public IEnumerable> GetRootMedia() + { + //TODO: Add permissions check! + + return Services.MediaService.GetRootMedia() + .Select(Mapper.Map>); + } + + /// + /// Returns the child media objects + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(int id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + int totalChildren; + IMedia[] children; + if (pageNumber > 0 && pageSize > 0) + { + children = Services.MediaService + .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren + , orderBy, orderDirection, orderBySystemField, filter).ToArray(); + } + else + { + children = Services.MediaService.GetChildren(id).ToArray(); + totalChildren = children.Length; + } + + if (totalChildren == 0) + { + return new PagedResult>(0, 0, 0); + } + + var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); + pagedResult.Items = children + .Select(Mapper.Map>); + + return pagedResult; + } + + /// + /// Moves an item to the recycle bin, if it is already there then it will permanently delete it + /// + /// + /// + [EnsureUserPermissionForMedia("id")] + [HttpPost] + public HttpResponseMessage DeleteById(int id) + { + var foundMedia = GetObjectFromRequest(() => Services.MediaService.GetById(id)); + + if (foundMedia == null) + { + return HandleContentNotFound(id, false); + } + + //if the current item is in the recycle bin + if (foundMedia.IsInRecycleBin() == false) + { + var moveResult = Services.MediaService.WithResult().MoveToRecycleBin(foundMedia, (int)Security.CurrentUser.Id); + if (moveResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } + } + else + { + var deleteResult = Services.MediaService.WithResult().Delete(foundMedia, (int)Security.CurrentUser.Id); + if (deleteResult == false) + { + //returning an object of INotificationModel will ensure that any pending + // notification messages are added to the response. + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } + } + + return Request.CreateResponse(HttpStatusCode.OK); + } + + /// + /// Change the sort order for media + /// + /// + /// + [EnsureUserPermissionForMedia("move.Id")] + public HttpResponseMessage PostMove(MoveOrCopy move) + { + var toMove = ValidateMoveOrCopy(move); + + Services.MediaService.Move(toMove, move.ParentId); + + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); + return response; + } + + /// + /// Saves content + /// + /// + [FileUploadCleanupFilter] + [MediaPostValidate] + public MediaItemDisplay PostSave( + [ModelBinder(typeof(MediaItemBinder))] + MediaItemSave contentItem) + { + //If we've reached here it means: + // * Our model has been bound + // * and validated + // * any file attachments have been saved to their temporary location for us to use + // * we have a reference to the DTO object and the persisted object + // * Permissions are valid + + MapPropertyValues(contentItem); + + //We need to manually check the validation results here because: + // * We still need to save the entity even if there are validation value errors + // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) + // then we cannot continue saving, we can only display errors + // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display + // a message indicating this + if (ModelState.IsValid == false) + { + if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) + && (contentItem.Action == ContentSaveAction.SaveNew)) + { + //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! + // add the modelstate to the outgoing object and throw validation response + var forDisplay = Mapper.Map(contentItem.PersistedContent); + forDisplay.Errors = ModelState.ToErrorDictionary(); + throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); + } + } + + //save the item + var saveStatus = Services.MediaService.WithResult().Save(contentItem.PersistedContent, (int)Security.CurrentUser.Id); + + //return the updated model + var display = Mapper.Map(contentItem.PersistedContent); + + //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 + HandleInvalidModelState(display); + + //put the correct msgs in + switch (contentItem.Action) + { + case ContentSaveAction.Save: + case ContentSaveAction.SaveNew: + if (saveStatus.Success) + { + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editMediaSaved"), + Services.TextService.Localize("speechBubbles/editMediaSavedText")); + } + else + { + AddCancelMessage(display); + + //If the item is new and the operation was cancelled, we need to return a different + // status code so the UI can handle it since it won't be able to redirect since there + // is no Id to redirect to! + if (saveStatus.Result.StatusType == OperationStatusType.FailedCancelledByEvent && IsCreatingAction(contentItem.Action)) + { + throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); + } + } + + break; + } + + return display; + } + + /// + /// Maps the property values to the persisted entity + /// + /// + protected override void MapPropertyValues(ContentBaseItemSave contentItem) + { + UpdateName(contentItem); + + //use the base method to map the rest of the properties + base.MapPropertyValues(contentItem); + } + + /// + /// Empties the recycle bin + /// + /// + [HttpDelete] + [HttpPost] + public HttpResponseMessage EmptyRecycleBin() + { + Services.MediaService.EmptyRecycleBin(); + return Request.CreateResponse(HttpStatusCode.OK); + } + + /// + /// Change the sort order for media + /// + /// + /// + [EnsureUserPermissionForMedia("sorted.ParentId")] + public HttpResponseMessage PostSort(ContentSortOrder sorted) + { + if (sorted == null) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + //if there's nothing to sort just return ok + if (sorted.IdSortOrder.Length == 0) + { + return Request.CreateResponse(HttpStatusCode.OK); + } + + var mediaService = base.ApplicationContext.Services.MediaService; + var sortedMedia = new List(); + try + { + sortedMedia.AddRange(sorted.IdSortOrder.Select(mediaService.GetById)); + + // Save Media with new sort order and update content xml in db accordingly + if (mediaService.Sort(sortedMedia) == false) + { + LogHelper.Warn("Media sorting failed, this was probably caused by an event being cancelled"); + return Request.CreateValidationErrorResponse("Media sorting failed, this was probably caused by an event being cancelled"); + } + return Request.CreateResponse(HttpStatusCode.OK); + } + catch (Exception ex) + { + LogHelper.Error("Could not update media sort order", ex); + throw; + } + } + + [EnsureUserPermissionForMedia("folder.ParentId")] + public MediaItemDisplay PostAddFolder(EntityBasic folder) + { + var mediaService = ApplicationContext.Services.MediaService; + var f = mediaService.CreateMedia(folder.Name, folder.ParentId, Constants.Conventions.MediaTypes.Folder); + mediaService.Save(f, Security.CurrentUser.Id); + + return Mapper.Map(f); + } + + /// + /// Used to submit a media file + /// + /// + /// + /// We cannot validate this request with attributes (nicely) due to the nature of the multi-part for data. + /// + [FileUploadCleanupFilter(false)] + public async Task PostAddFile() + { + if (Request.Content.IsMimeMultipartContent() == false) + { + throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); + } + + var root = IOHelper.MapPath("~/App_Data/TEMP/FileUploads"); + //ensure it exists + Directory.CreateDirectory(root); + var provider = new MultipartFormDataStreamProvider(root); + + var result = await Request.Content.ReadAsMultipartAsync(provider); + + //must have a file + if (result.FileData.Count == 0) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + //get the string json from the request + int parentId; + if (int.TryParse(result.FormData["currentFolder"], out parentId) == false) + { + return Request.CreateValidationErrorResponse("The request was not formatted correctly, the currentFolder is not an integer"); + } + + //ensure the user has access to this folder by parent id! + if (CheckPermissions( + new Dictionary(), + Security.CurrentUser, + Services.MediaService, parentId) == false) + { + return Request.CreateResponse( + HttpStatusCode.Unauthorized, + new SimpleNotificationModel(new Notification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + Services.TextService.Localize("speechBubbles/invalidUserPermissionsText"), + SpeechBubbleIcon.Warning))); + } + + var tempFiles = new PostedFiles(); + var mediaService = ApplicationContext.Services.MediaService; + + + //in case we pass a path with a folder in it, we will create it and upload media to it. + if (result.FormData.ContainsKey("path")) + { + + var folders = result.FormData["path"].Split('/'); + + for (int i = 0; i < folders.Length - 1; i++) + { + var folderName = folders[i]; + IMedia folderMediaItem; + + //if uploading directly to media root and not a subfolder + if (parentId == -1) + { + //look for matching folder + folderMediaItem = + mediaService.GetRootMedia().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); + if (folderMediaItem == null) + { + //if null, create a folder + folderMediaItem = mediaService.CreateMedia(folderName, -1, Constants.Conventions.MediaTypes.Folder); + mediaService.Save(folderMediaItem); + } + } + else + { + //get current parent + var mediaRoot = mediaService.GetById(parentId); + + //if the media root is null, something went wrong, we'll abort + if (mediaRoot == null) + return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, + "The folder: " + folderName + " could not be used for storing images, its ID: " + parentId + + " returned null"); + + //look for matching folder + folderMediaItem = mediaRoot.Children().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); + if (folderMediaItem == null) + { + //if null, create a folder + folderMediaItem = mediaService.CreateMedia(folderName, mediaRoot, Constants.Conventions.MediaTypes.Folder); + mediaService.Save(folderMediaItem); + } + } + //set the media root to the folder id so uploaded files will end there. + parentId = folderMediaItem.Id; + } + } + + //get the files + foreach (var file in result.FileData) + { + var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }); + var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower(); + + if (UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext) == false) + { + var mediaType = Constants.Conventions.MediaTypes.File; + + if (UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.Contains(ext)) + mediaType = Constants.Conventions.MediaTypes.Image; + + //TODO: make the media item name "nice" since file names could be pretty ugly, we have + // string extensions to do much of this but we'll need: + // * Pascalcase the name (use string extensions) + // * strip the file extension + // * underscores to spaces + // * probably remove 'ugly' characters - let's discuss + // All of this logic should exist in a string extensions method and be unit tested + // http://issues.umbraco.org/issue/U4-5572 + var mediaItemName = fileName; + + var f = mediaService.CreateMedia(mediaItemName, parentId, mediaType, Security.CurrentUser.Id); + + var fileInfo = new FileInfo(file.LocalFileName); + var fs = fileInfo.OpenReadWithRetry(); + if (fs == null) throw new InvalidOperationException("Could not acquire file stream"); + using (fs) + { + f.SetValue(Constants.Conventions.Media.File, fileName, fs); + } + + var saveResult = mediaService.WithResult().Save(f, Security.CurrentUser.Id); + if (saveResult == false) + { + AddCancelMessage(tempFiles, + message: Services.TextService.Localize("speechBubbles/operationCancelledText") + " -- " + mediaItemName, + localizeMessage: false); + } + else + { + tempFiles.UploadedFiles.Add(new ContentItemFile + { + FileName = fileName, + PropertyAlias = Constants.Conventions.Media.File, + TempFilePath = file.LocalFileName + }); + } + } + else + { + tempFiles.Notifications.Add(new Notification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + "Cannot upload file " + file.Headers.ContentDisposition.FileName + ", it is not an approved file type", + SpeechBubbleIcon.Warning)); + } + } + + //Different response if this is a 'blueimp' request + if (Request.GetQueryNameValuePairs().Any(x => x.Key == "origin")) + { + var origin = Request.GetQueryNameValuePairs().First(x => x.Key == "origin"); + if (origin.Value == "blueimp") + { + return Request.CreateResponse(HttpStatusCode.OK, + tempFiles, + //Don't output the angular xsrf stuff, blue imp doesn't like that + new JsonMediaTypeFormatter()); + } + } + + return Request.CreateResponse(HttpStatusCode.OK, tempFiles); + } + + /// + /// This is used for the response of PostAddFile so that we can analyze the response in a filter and remove the + /// temporary files that were created. + /// + [DataContract] + private class PostedFiles : IHaveUploadedFiles, INotificationModel + { + public PostedFiles() + { + UploadedFiles = new List(); + Notifications = new List(); + } + public List UploadedFiles { get; private set; } + + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } + } + + /// + /// Ensures the item can be moved/copied to the new location + /// + /// + /// + private IMedia ValidateMoveOrCopy(MoveOrCopy model) + { + if (model == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var mediaService = Services.MediaService; + var toMove = mediaService.GetById(model.Id); + if (toMove == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + if (model.ParentId < 0) + { + //cannot move if the content item is not allowed at the root + if (toMove.ContentType.AllowedAsRoot == false) + { + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"), ""); + throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); + } + } + else + { + var parent = mediaService.GetById(model.ParentId); + if (parent == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + //check if the item is allowed under this one + if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() + .Any(x => x.Value == toMove.ContentType.Id) == false) + { + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByContentType"), ""); + throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); + } + + // Check on paths + if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) + { + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddErrorNotification(Services.TextService.Localize("moveOrCopy/notAllowedByPath"), ""); + throw new HttpResponseException(Request.CreateValidationErrorResponse(notificationModel)); + } + } + + return toMove; + } + + /// + /// Performs a permissions check for the user to check if it has access to the node based on + /// start node and/or permissions for the node + /// + /// The storage to add the content item to so it can be reused + /// + /// + /// The content to lookup, if the contentItem is not specified + /// Specifies the already resolved content item to check against, setting this ignores the nodeId + /// + internal static bool CheckPermissions(IDictionary storage, IUser user, IMediaService mediaService, int nodeId, IMedia media = null) + { + if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) + { + media = mediaService.GetById(nodeId); + //put the content item into storage so it can be retreived + // in the controller (saves a lookup) + storage[typeof(IMedia).ToString()] = media; + } + + if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + var hasPathAccess = (nodeId == Constants.System.Root) + ? UserExtensions.HasPathAccess( + Constants.System.Root.ToInvariantString(), + user.StartMediaId, + Constants.System.RecycleBinMedia) + : (nodeId == Constants.System.RecycleBinMedia) + ? UserExtensions.HasPathAccess( + Constants.System.RecycleBinMedia.ToInvariantString(), + user.StartMediaId, + Constants.System.RecycleBinMedia) + : user.HasPathAccess(media); + + return hasPathAccess; + } + } } diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index a2f1090a63..2a3bb4399b 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -34,250 +34,250 @@ using Examine; namespace Umbraco.Web.Editors { - /// - /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting - /// access to ALL of the methods on this controller will need access to the member application. - /// - [PluginController("UmbracoApi")] - [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Members)] - [OutgoingNoHyphenGuidFormat] - public class MemberController : ContentControllerBase - { - /// - /// Constructor - /// - public MemberController() - : this(UmbracoContext.Current) - { - _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - } + /// + /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting + /// access to ALL of the methods on this controller will need access to the member application. + /// + [PluginController("UmbracoApi")] + [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Members)] + [OutgoingNoHyphenGuidFormat] + public class MemberController : ContentControllerBase + { + /// + /// Constructor + /// + public MemberController() + : this(UmbracoContext.Current) + { + _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + } - /// - /// Constructor - /// - /// - public MemberController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - } + /// + /// Constructor + /// + /// + public MemberController(UmbracoContext umbracoContext) + : base(umbracoContext) + { + _provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + } - private readonly MembershipProvider _provider; + private readonly MembershipProvider _provider; - /// - /// Returns the currently configured membership scenario for members in umbraco - /// - /// - protected MembershipScenario MembershipScenario - { - get { return Services.MemberService.GetMembershipScenario(); } - } + /// + /// Returns the currently configured membership scenario for members in umbraco + /// + /// + protected MembershipScenario MembershipScenario + { + get { return Services.MemberService.GetMembershipScenario(); } + } - public PagedResult GetPagedResults( - int pageNumber = 1, - int pageSize = 100, - string orderBy = "Name", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "", - string memberTypeAlias = null) - { + public PagedResult GetPagedResults( + int pageNumber = 1, + int pageSize = 100, + string orderBy = "Name", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "", + string memberTypeAlias = null) + { - if (pageNumber <= 0 || pageSize <= 0) - { - throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero"); - } + if (pageNumber <= 0 || pageSize <= 0) + { + throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero"); + } - if (MembershipScenario == MembershipScenario.NativeUmbraco) - { - long totalRecords; - var members = Services.MemberService -.GetAll((pageNumber - 1), pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField -, memberTypeAlias, filter).ToArray(); - if (totalRecords == 0) - { - return new PagedResult(0, 0, 0); - } - var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize); - pagedResult.Items = members - .Select(Mapper.Map); - return pagedResult; - } - else - { - int totalRecords; - var members = _provider.GetAllUsers((pageNumber - 1), pageSize, out totalRecords); - if (totalRecords == 0) - { - return new PagedResult(0, 0, 0); - } - var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize); - pagedResult.Items = members - .Cast() - .Select(Mapper.Map); - return pagedResult; - } + if (MembershipScenario == MembershipScenario.NativeUmbraco) + { + long totalRecords; + var members = Services.MemberService + .GetAll((pageNumber - 1), pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField + , memberTypeAlias, filter).ToArray(); + if (totalRecords == 0) + { + return new PagedResult(0, 0, 0); + } + var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize); + pagedResult.Items = members + .Select(Mapper.Map); + return pagedResult; + } + else + { + int totalRecords; + var members = _provider.GetAllUsers((pageNumber - 1), pageSize, out totalRecords); + if (totalRecords == 0) + { + return new PagedResult(0, 0, 0); + } + var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize); + pagedResult.Items = members + .Cast() + .Select(Mapper.Map); + return pagedResult; + } - } + } - /// - /// Returns a display node with a list view to render members - /// - /// - /// - public MemberListDisplay GetListNodeDisplay(string listName) - { - var display = new MemberListDisplay - { - ContentTypeAlias = listName, - ContentTypeName = listName, - Id = listName, - IsContainer = true, - Name = listName == Constants.Conventions.MemberTypes.AllMembersListId ? "All Members" : listName, - Path = "-1," + listName, - ParentId = -1 - }; + /// + /// Returns a display node with a list view to render members + /// + /// + /// + public MemberListDisplay GetListNodeDisplay(string listName) + { + var display = new MemberListDisplay + { + ContentTypeAlias = listName, + ContentTypeName = listName, + Id = listName, + IsContainer = true, + Name = listName == Constants.Conventions.MemberTypes.AllMembersListId ? "All Members" : listName, + Path = "-1," + listName, + ParentId = -1 + }; - TabsAndPropertiesResolver.AddListView(display, "member", Services.DataTypeService, Services.TextService); + TabsAndPropertiesResolver.AddListView(display, "member", Services.DataTypeService, Services.TextService); - return display; - } + return display; + } - /// - /// Gets the content json for the member - /// - /// - /// - [OutgoingEditorModelEvent] - public MemberDisplay GetByKey(Guid key) - { - MembershipUser foundMembershipMember; - MemberDisplay display; - IMember foundMember; - switch (MembershipScenario) - { - case MembershipScenario.NativeUmbraco: - foundMember = Services.MemberService.GetByKey(key); - if (foundMember == null) - { - HandleContentNotFound(key); - } - return Mapper.Map(foundMember); - case MembershipScenario.CustomProviderWithUmbracoLink: + /// + /// Gets the content json for the member + /// + /// + /// + [OutgoingEditorModelEvent] + public MemberDisplay GetByKey(Guid key) + { + MembershipUser foundMembershipMember; + MemberDisplay display; + IMember foundMember; + switch (MembershipScenario) + { + case MembershipScenario.NativeUmbraco: + foundMember = Services.MemberService.GetByKey(key); + if (foundMember == null) + { + HandleContentNotFound(key); + } + return Mapper.Map(foundMember); + case MembershipScenario.CustomProviderWithUmbracoLink: - //TODO: Support editing custom properties for members with a custom membership provider here. + //TODO: Support editing custom properties for members with a custom membership provider here. - //foundMember = Services.MemberService.GetByKey(key); - //if (foundMember == null) - //{ - // HandleContentNotFound(key); - //} - //foundMembershipMember = Membership.GetUser(key, false); - //if (foundMembershipMember == null) - //{ - // HandleContentNotFound(key); - //} + //foundMember = Services.MemberService.GetByKey(key); + //if (foundMember == null) + //{ + // HandleContentNotFound(key); + //} + //foundMembershipMember = Membership.GetUser(key, false); + //if (foundMembershipMember == null) + //{ + // HandleContentNotFound(key); + //} - //display = Mapper.Map(foundMembershipMember); - ////map the name over - //display.Name = foundMember.Name; - //return display; + //display = Mapper.Map(foundMembershipMember); + ////map the name over + //display.Name = foundMember.Name; + //return display; - case MembershipScenario.StandaloneCustomProvider: - default: - foundMembershipMember = _provider.GetUser(key, false); - if (foundMembershipMember == null) - { - HandleContentNotFound(key); - } - display = Mapper.Map(foundMembershipMember); - return display; - } - } + case MembershipScenario.StandaloneCustomProvider: + default: + foundMembershipMember = _provider.GetUser(key, false); + if (foundMembershipMember == null) + { + HandleContentNotFound(key); + } + display = Mapper.Map(foundMembershipMember); + return display; + } + } - /// - /// Gets an empty content item for the - /// - /// - /// - [OutgoingEditorModelEvent] - public MemberDisplay GetEmpty(string contentTypeAlias = null) - { - IMember emptyContent; - switch (MembershipScenario) - { - case MembershipScenario.NativeUmbraco: - if (contentTypeAlias == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } + /// + /// Gets an empty content item for the + /// + /// + /// + [OutgoingEditorModelEvent] + public MemberDisplay GetEmpty(string contentTypeAlias = null) + { + IMember emptyContent; + switch (MembershipScenario) + { + case MembershipScenario.NativeUmbraco: + if (contentTypeAlias == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } - var contentType = Services.MemberTypeService.Get(contentTypeAlias); - if (contentType == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } + var contentType = Services.MemberTypeService.Get(contentTypeAlias); + if (contentType == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } - emptyContent = new Member(contentType); - emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(Membership.MinRequiredPasswordLength, Membership.MinRequiredNonAlphanumericCharacters); - return Mapper.Map(emptyContent); - case MembershipScenario.CustomProviderWithUmbracoLink: - //TODO: Support editing custom properties for members with a custom membership provider here. + emptyContent = new Member(contentType); + emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(Membership.MinRequiredPasswordLength, Membership.MinRequiredNonAlphanumericCharacters); + return Mapper.Map(emptyContent); + case MembershipScenario.CustomProviderWithUmbracoLink: + //TODO: Support editing custom properties for members with a custom membership provider here. - case MembershipScenario.StandaloneCustomProvider: - default: - //we need to return a scaffold of a 'simple' member - basically just what a membership provider can edit - emptyContent = MemberService.CreateGenericMembershipProviderMember("", "", "", ""); - emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(Membership.MinRequiredPasswordLength, Membership.MinRequiredNonAlphanumericCharacters); - return Mapper.Map(emptyContent); - } - } + case MembershipScenario.StandaloneCustomProvider: + default: + //we need to return a scaffold of a 'simple' member - basically just what a membership provider can edit + emptyContent = MemberService.CreateGenericMembershipProviderMember("", "", "", ""); + emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(Membership.MinRequiredPasswordLength, Membership.MinRequiredNonAlphanumericCharacters); + return Mapper.Map(emptyContent); + } + } - /// - /// Saves member - /// - /// - [FileUploadCleanupFilter] - public MemberDisplay PostSave( - [ModelBinder(typeof(MemberBinder))] - MemberSave contentItem) - { + /// + /// Saves member + /// + /// + [FileUploadCleanupFilter] + public MemberDisplay PostSave( + [ModelBinder(typeof(MemberBinder))] + MemberSave contentItem) + { - //If we've reached here it means: - // * Our model has been bound - // * and validated - // * any file attachments have been saved to their temporary location for us to use - // * we have a reference to the DTO object and the persisted object - // * Permissions are valid + //If we've reached here it means: + // * Our model has been bound + // * and validated + // * any file attachments have been saved to their temporary location for us to use + // * we have a reference to the DTO object and the persisted object + // * Permissions are valid - //This is a special case for when we're not using the umbraco membership provider - when this is the case - // we will not have a ContentTypeAlias set which means the model state will be invalid but we don't care about that - // so we'll remove that model state value - if (MembershipScenario != MembershipScenario.NativeUmbraco) - { - ModelState.Remove("ContentTypeAlias"); + //This is a special case for when we're not using the umbraco membership provider - when this is the case + // we will not have a ContentTypeAlias set which means the model state will be invalid but we don't care about that + // so we'll remove that model state value + if (MembershipScenario != MembershipScenario.NativeUmbraco) + { + ModelState.Remove("ContentTypeAlias"); - //TODO: We're removing this because we are not displaying it but when we support the CustomProviderWithUmbracoLink scenario - // we will be able to have a real name associated so do not remove this state once that is implemented! - ModelState.Remove("Name"); - } + //TODO: We're removing this because we are not displaying it but when we support the CustomProviderWithUmbracoLink scenario + // we will be able to have a real name associated so do not remove this state once that is implemented! + ModelState.Remove("Name"); + } - //map the properties to the persisted entity - MapPropertyValues(contentItem); + //map the properties to the persisted entity + MapPropertyValues(contentItem); - //Unlike content/media - if there are errors for a member, we do NOT proceed to save them, we cannot so return the errors - if (ModelState.IsValid == false) - { - var forDisplay = Mapper.Map(contentItem.PersistedContent); - forDisplay.Errors = ModelState.ToErrorDictionary(); - throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); - } + //Unlike content/media - if there are errors for a member, we do NOT proceed to save them, we cannot so return the errors + if (ModelState.IsValid == false) + { + var forDisplay = Mapper.Map(contentItem.PersistedContent); + forDisplay.Errors = ModelState.ToErrorDictionary(); + throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); + } - //TODO: WE need to support this! - requires UI updates, etc... - if (_provider.RequiresQuestionAndAnswer) - { - throw new NotSupportedException("Currently the member editor does not support providers that have RequiresQuestionAndAnswer specified"); - } + //TODO: WE need to support this! - requires UI updates, etc... + if (_provider.RequiresQuestionAndAnswer) + { + throw new NotSupportedException("Currently the member editor does not support providers that have RequiresQuestionAndAnswer specified"); + } //We're gonna look up the current roles now because the below code can cause // events to be raised and developers could be manually adding roles to members in @@ -287,473 +287,473 @@ namespace Umbraco.Web.Editors //find the ones to remove and remove them var rolesToRemove = currRoles.Except(contentItem.Groups).ToArray(); - string generatedPassword = null; - //Depending on the action we need to first do a create or update using the membership provider - // this ensures that passwords are formatted correclty and also performs the validation on the provider itself. - switch (contentItem.Action) - { - case ContentSaveAction.Save: - generatedPassword = UpdateWithMembershipProvider(contentItem); - break; - case ContentSaveAction.SaveNew: - MembershipCreateStatus status; - CreateWithMembershipProvider(contentItem, out status); + string generatedPassword = null; + //Depending on the action we need to first do a create or update using the membership provider + // this ensures that passwords are formatted correclty and also performs the validation on the provider itself. + switch (contentItem.Action) + { + case ContentSaveAction.Save: + generatedPassword = UpdateWithMembershipProvider(contentItem); + break; + case ContentSaveAction.SaveNew: + MembershipCreateStatus status; + CreateWithMembershipProvider(contentItem, out status); - // save the ID of the creator - contentItem.PersistedContent.CreatorId = Security.CurrentUser.Id; - break; - default: - //we don't support anything else for members - throw new HttpResponseException(HttpStatusCode.NotFound); - } + // save the ID of the creator + contentItem.PersistedContent.CreatorId = Security.CurrentUser.Id; + break; + default: + //we don't support anything else for members + throw new HttpResponseException(HttpStatusCode.NotFound); + } - //If we've had problems creating/updating the user with the provider then return the error - if (ModelState.IsValid == false) - { - var forDisplay = Mapper.Map(contentItem.PersistedContent); - forDisplay.Errors = ModelState.ToErrorDictionary(); - throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); - } + //If we've had problems creating/updating the user with the provider then return the error + if (ModelState.IsValid == false) + { + var forDisplay = Mapper.Map(contentItem.PersistedContent); + forDisplay.Errors = ModelState.ToErrorDictionary(); + throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); + } - //save the IMember - - //TODO: When we support the CustomProviderWithUmbracoLink scenario, we'll need to save the custom properties for that here too - if (MembershipScenario == MembershipScenario.NativeUmbraco) - { - //save the item - //NOTE: We are setting the password to NULL - this indicates to the system to not actually save the password - // so it will not get overwritten! - contentItem.PersistedContent.RawPasswordValue = null; + //save the IMember - + //TODO: When we support the CustomProviderWithUmbracoLink scenario, we'll need to save the custom properties for that here too + if (MembershipScenario == MembershipScenario.NativeUmbraco) + { + //save the item + //NOTE: We are setting the password to NULL - this indicates to the system to not actually save the password + // so it will not get overwritten! + contentItem.PersistedContent.RawPasswordValue = null; - //create/save the IMember - Services.MemberService.Save(contentItem.PersistedContent); - } + //create/save the IMember + Services.MemberService.Save(contentItem.PersistedContent); + } - //Now let's do the role provider stuff - now that we've saved the content item (that is important since - // if we are changing the username, it must be persisted before looking up the member roles). + //Now let's do the role provider stuff - now that we've saved the content item (that is important since + // if we are changing the username, it must be persisted before looking up the member roles). if (rolesToRemove.Any()) - { + { Roles.RemoveUserFromRoles(contentItem.PersistedContent.Username, rolesToRemove); - } - //find the ones to add and add them + } + //find the ones to add and add them var toAdd = contentItem.Groups.Except(currRoles).ToArray(); - if (toAdd.Any()) - { - //add the ones submitted - Roles.AddUserToRoles(contentItem.PersistedContent.Username, toAdd); - } + if (toAdd.Any()) + { + //add the ones submitted + Roles.AddUserToRoles(contentItem.PersistedContent.Username, toAdd); + } - //set the generated password (if there was one) - in order to do this we'll chuck the gen'd password into the - // additional data of the IUmbracoEntity of the persisted item - then we can retrieve this in the model mapper and set - // the value to be given to the UI. Hooray for AdditionalData :) - contentItem.PersistedContent.AdditionalData["GeneratedPassword"] = generatedPassword; + //set the generated password (if there was one) - in order to do this we'll chuck the gen'd password into the + // additional data of the IUmbracoEntity of the persisted item - then we can retrieve this in the model mapper and set + // the value to be given to the UI. Hooray for AdditionalData :) + contentItem.PersistedContent.AdditionalData["GeneratedPassword"] = generatedPassword; - //return the updated model - var display = Mapper.Map(contentItem.PersistedContent); + //return the updated model + var display = Mapper.Map(contentItem.PersistedContent); - //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 - HandleInvalidModelState(display); + //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 + HandleInvalidModelState(display); - var localizedTextService = Services.TextService; - //put the correct msgs in - switch (contentItem.Action) - { - case ContentSaveAction.Save: - case ContentSaveAction.SaveNew: - display.AddSuccessNotification(localizedTextService.Localize("speechBubbles/editMemberSaved"), localizedTextService.Localize("speechBubbles/editMemberSaved")); - break; - } + var localizedTextService = Services.TextService; + //put the correct msgs in + switch (contentItem.Action) + { + case ContentSaveAction.Save: + case ContentSaveAction.SaveNew: + display.AddSuccessNotification(localizedTextService.Localize("speechBubbles/editMemberSaved"), localizedTextService.Localize("speechBubbles/editMemberSaved")); + break; + } - return display; - } + return display; + } - /// - /// Maps the property values to the persisted entity - /// - /// - private void MapPropertyValues(MemberSave contentItem) - { - UpdateName(contentItem); + /// + /// Maps the property values to the persisted entity + /// + /// + private void MapPropertyValues(MemberSave contentItem) + { + UpdateName(contentItem); - //map the custom properties - this will already be set for new entities in our member binder - contentItem.PersistedContent.Email = contentItem.Email; - contentItem.PersistedContent.Username = contentItem.Username; + //map the custom properties - this will already be set for new entities in our member binder + contentItem.PersistedContent.Email = contentItem.Email; + contentItem.PersistedContent.Username = contentItem.Username; - //use the base method to map the rest of the properties - base.MapPropertyValues(contentItem); - } + //use the base method to map the rest of the properties + base.MapPropertyValues(contentItem); + } - /// - /// Update the membership user using the membership provider (for things like email, etc...) - /// If a password change is detected then we'll try that too. - /// - /// - /// - /// If the password has been reset then this method will return the reset/generated password, otherwise will return null. - /// - private string UpdateWithMembershipProvider(MemberSave contentItem) - { - //Get the member from the provider + /// + /// Update the membership user using the membership provider (for things like email, etc...) + /// If a password change is detected then we'll try that too. + /// + /// + /// + /// If the password has been reset then this method will return the reset/generated password, otherwise will return null. + /// + private string UpdateWithMembershipProvider(MemberSave contentItem) + { + //Get the member from the provider - var membershipUser = _provider.GetUser(contentItem.PersistedContent.Key, false); - if (membershipUser == null) - { - //This should never happen! so we'll let it YSOD if it does. - throw new InvalidOperationException("Could not get member from membership provider " + _provider.Name + " with key " + contentItem.PersistedContent.Key); - } + var membershipUser = _provider.GetUser(contentItem.PersistedContent.Key, false); + if (membershipUser == null) + { + //This should never happen! so we'll let it YSOD if it does. + throw new InvalidOperationException("Could not get member from membership provider " + _provider.Name + " with key " + contentItem.PersistedContent.Key); + } - var shouldReFetchMember = false; - var providedUserName = contentItem.PersistedContent.Username; + var shouldReFetchMember = false; + var providedUserName = contentItem.PersistedContent.Username; - //Update the membership user if it has changed - try - { - var requiredUpdating = Members.UpdateMember(membershipUser, _provider, - contentItem.Email.Trim(), - contentItem.IsApproved, - comment: contentItem.Comments); + //Update the membership user if it has changed + try + { + var requiredUpdating = Members.UpdateMember(membershipUser, _provider, + contentItem.Email.Trim(), + contentItem.IsApproved, + comment: contentItem.Comments); - if (requiredUpdating.Success) - { - //re-map these values - shouldReFetchMember = true; - } - } - catch (Exception ex) - { - LogHelper.WarnWithException("Could not update member, the provider returned an error", ex); - ModelState.AddPropertyError( - //specify 'default' just so that it shows up as a notification - is not assigned to a property - new ValidationResult("Could not update member, the provider returned an error: " + ex.Message + " (see log for full details)"), "default"); - } + if (requiredUpdating.Success) + { + //re-map these values + shouldReFetchMember = true; + } + } + catch (Exception ex) + { + LogHelper.WarnWithException("Could not update member, the provider returned an error", ex); + ModelState.AddPropertyError( + //specify 'default' just so that it shows up as a notification - is not assigned to a property + new ValidationResult("Could not update member, the provider returned an error: " + ex.Message + " (see log for full details)"), "default"); + } - //if they were locked but now they are trying to be unlocked - if (membershipUser.IsLockedOut && contentItem.IsLockedOut == false) - { - try - { - var result = _provider.UnlockUser(membershipUser.UserName); - if (result == false) - { - //it wasn't successful - but it won't really tell us why. - ModelState.AddModelError("custom", "Could not unlock the user"); - } - else - { - shouldReFetchMember = true; - } - } - catch (Exception ex) - { - ModelState.AddModelError("custom", ex); - } - } - else if (membershipUser.IsLockedOut == false && contentItem.IsLockedOut) - { - //NOTE: This should not ever happen unless someone is mucking around with the request data. - //An admin cannot simply lock a user, they get locked out by password attempts, but an admin can un-approve them - ModelState.AddModelError("custom", "An admin cannot lock a user"); - } + //if they were locked but now they are trying to be unlocked + if (membershipUser.IsLockedOut && contentItem.IsLockedOut == false) + { + try + { + var result = _provider.UnlockUser(membershipUser.UserName); + if (result == false) + { + //it wasn't successful - but it won't really tell us why. + ModelState.AddModelError("custom", "Could not unlock the user"); + } + else + { + shouldReFetchMember = true; + } + } + catch (Exception ex) + { + ModelState.AddModelError("custom", ex); + } + } + else if (membershipUser.IsLockedOut == false && contentItem.IsLockedOut) + { + //NOTE: This should not ever happen unless someone is mucking around with the request data. + //An admin cannot simply lock a user, they get locked out by password attempts, but an admin can un-approve them + ModelState.AddModelError("custom", "An admin cannot lock a user"); + } - //password changes ? - if (contentItem.Password == null) - { - //If the provider has changed some values, these values need to be reflected in the member object - //that will get mapped to the display object - if (shouldReFetchMember) - { - RefetchMemberData(contentItem, LookupType.ByKey); - RestoreProvidedUserName(contentItem, providedUserName); - } + //password changes ? + if (contentItem.Password == null) + { + //If the provider has changed some values, these values need to be reflected in the member object + //that will get mapped to the display object + if (shouldReFetchMember) + { + RefetchMemberData(contentItem, LookupType.ByKey); + RestoreProvidedUserName(contentItem, providedUserName); + } - return null; - } + return null; + } - var passwordChangeResult = Members.ChangePassword(membershipUser.UserName, contentItem.Password, _provider); - if (passwordChangeResult.Success) - { - //If the provider has changed some values, these values need to be reflected in the member object - //that will get mapped to the display object - if (shouldReFetchMember) - { - RefetchMemberData(contentItem, LookupType.ByKey); - RestoreProvidedUserName(contentItem, providedUserName); - } + var passwordChangeResult = Members.ChangePassword(membershipUser.UserName, contentItem.Password, _provider); + if (passwordChangeResult.Success) + { + //If the provider has changed some values, these values need to be reflected in the member object + //that will get mapped to the display object + if (shouldReFetchMember) + { + RefetchMemberData(contentItem, LookupType.ByKey); + RestoreProvidedUserName(contentItem, providedUserName); + } - //even if we weren't resetting this, it is the correct value (null), otherwise if we were resetting then it will contain the new pword - return passwordChangeResult.Result.ResetPassword; - } + //even if we weren't resetting this, it is the correct value (null), otherwise if we were resetting then it will contain the new pword + return passwordChangeResult.Result.ResetPassword; + } - //it wasn't successful, so add the change error to the model state - ModelState.AddPropertyError( - passwordChangeResult.Result.ChangeError, - string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + //it wasn't successful, so add the change error to the model state + ModelState.AddPropertyError( + passwordChangeResult.Result.ChangeError, + string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - return null; - } + return null; + } - private enum LookupType - { - ByKey, - ByUserName - } + private enum LookupType + { + ByKey, + ByUserName + } - /// - /// Re-fetches the database data to map to the PersistedContent object and re-assigns the already mapped the posted properties so that the display object is up-to-date - /// - /// - /// - /// This is done during an update if the membership provider has changed some underlying data - we need to ensure that our model is consistent with that data - /// - private void RefetchMemberData(MemberSave contentItem, LookupType lookup) - { - var currProps = contentItem.PersistedContent.Properties.ToArray(); + /// + /// Re-fetches the database data to map to the PersistedContent object and re-assigns the already mapped the posted properties so that the display object is up-to-date + /// + /// + /// + /// This is done during an update if the membership provider has changed some underlying data - we need to ensure that our model is consistent with that data + /// + private void RefetchMemberData(MemberSave contentItem, LookupType lookup) + { + var currProps = contentItem.PersistedContent.Properties.ToArray(); - switch (MembershipScenario) - { - case MembershipScenario.NativeUmbraco: - switch (lookup) - { - case LookupType.ByKey: - //Go and re-fetch the persisted item - contentItem.PersistedContent = Services.MemberService.GetByKey(contentItem.Key); - break; - case LookupType.ByUserName: - contentItem.PersistedContent = Services.MemberService.GetByUsername(contentItem.Username.Trim()); - break; - } - break; - case MembershipScenario.CustomProviderWithUmbracoLink: - case MembershipScenario.StandaloneCustomProvider: - default: - var membershipUser = _provider.GetUser(contentItem.Key, false); - //Go and re-fetch the persisted item - contentItem.PersistedContent = Mapper.Map(membershipUser); - break; - } + switch (MembershipScenario) + { + case MembershipScenario.NativeUmbraco: + switch (lookup) + { + case LookupType.ByKey: + //Go and re-fetch the persisted item + contentItem.PersistedContent = Services.MemberService.GetByKey(contentItem.Key); + break; + case LookupType.ByUserName: + contentItem.PersistedContent = Services.MemberService.GetByUsername(contentItem.Username.Trim()); + break; + } + break; + case MembershipScenario.CustomProviderWithUmbracoLink: + case MembershipScenario.StandaloneCustomProvider: + default: + var membershipUser = _provider.GetUser(contentItem.Key, false); + //Go and re-fetch the persisted item + contentItem.PersistedContent = Mapper.Map(membershipUser); + break; + } - UpdateName(contentItem); + UpdateName(contentItem); - //re-assign the mapped values that are not part of the membership provider properties. - var builtInAliases = Constants.Conventions.Member.GetStandardPropertyTypeStubs().Select(x => x.Key).ToArray(); - foreach (var p in contentItem.PersistedContent.Properties) - { - var valueMapped = currProps.SingleOrDefault(x => x.Alias == p.Alias); - if (builtInAliases.Contains(p.Alias) == false && valueMapped != null) - { - p.Value = valueMapped.Value; - p.TagSupport.Behavior = valueMapped.TagSupport.Behavior; - p.TagSupport.Enable = valueMapped.TagSupport.Enable; - p.TagSupport.Tags = valueMapped.TagSupport.Tags; - } - } - } + //re-assign the mapped values that are not part of the membership provider properties. + var builtInAliases = Constants.Conventions.Member.GetStandardPropertyTypeStubs().Select(x => x.Key).ToArray(); + foreach (var p in contentItem.PersistedContent.Properties) + { + var valueMapped = currProps.SingleOrDefault(x => x.Alias == p.Alias); + if (builtInAliases.Contains(p.Alias) == false && valueMapped != null) + { + p.Value = valueMapped.Value; + p.TagSupport.Behavior = valueMapped.TagSupport.Behavior; + p.TagSupport.Enable = valueMapped.TagSupport.Enable; + p.TagSupport.Tags = valueMapped.TagSupport.Tags; + } + } + } - /// - /// Following a refresh of member data called during an update if the membership provider has changed some underlying data, - /// we don't want to lose the provided, and potentiallly changed, username - /// - /// - /// - private static void RestoreProvidedUserName(MemberSave contentItem, string providedUserName) - { - contentItem.PersistedContent.Username = providedUserName; - } + /// + /// Following a refresh of member data called during an update if the membership provider has changed some underlying data, + /// we don't want to lose the provided, and potentiallly changed, username + /// + /// + /// + private static void RestoreProvidedUserName(MemberSave contentItem, string providedUserName) + { + contentItem.PersistedContent.Username = providedUserName; + } - /// - /// This is going to create the user with the membership provider and check for validation - /// - /// - /// - /// - /// - /// Depending on if the Umbraco membership provider is active or not, the process differs slightly: - /// - /// * If the umbraco membership provider is used - we create the membership user first with the membership provider, since - /// it's the umbraco membership provider, this writes to the umbraco tables. When that is complete we re-fetch the IMember - /// model data from the db. In this case we don't care what the provider user key is. - /// * If we're using a non-umbraco membership provider - we check if there is a 'Member' member type - if so - /// we create an empty IMember instance first (of type 'Member'), this gives us a unique ID (GUID) - /// that we then use to create the member in the custom membership provider. This acts as the link between Umbraco data and - /// the custom membership provider data. This gives us the ability to eventually have custom membership properties but still use - /// a custom memberhip provider. If there is no 'Member' member type, then we will simply just create the membership provider member - /// with no link to our data. - /// - /// If this is successful, it will go and re-fetch the IMember from the db because it will now have an ID because the Umbraco provider - /// uses the umbraco data store - then of course we need to re-map it to the saved property values. - /// - private MembershipUser CreateWithMembershipProvider(MemberSave contentItem, out MembershipCreateStatus status) - { - MembershipUser membershipUser; + /// + /// This is going to create the user with the membership provider and check for validation + /// + /// + /// + /// + /// + /// Depending on if the Umbraco membership provider is active or not, the process differs slightly: + /// + /// * If the umbraco membership provider is used - we create the membership user first with the membership provider, since + /// it's the umbraco membership provider, this writes to the umbraco tables. When that is complete we re-fetch the IMember + /// model data from the db. In this case we don't care what the provider user key is. + /// * If we're using a non-umbraco membership provider - we check if there is a 'Member' member type - if so + /// we create an empty IMember instance first (of type 'Member'), this gives us a unique ID (GUID) + /// that we then use to create the member in the custom membership provider. This acts as the link between Umbraco data and + /// the custom membership provider data. This gives us the ability to eventually have custom membership properties but still use + /// a custom memberhip provider. If there is no 'Member' member type, then we will simply just create the membership provider member + /// with no link to our data. + /// + /// If this is successful, it will go and re-fetch the IMember from the db because it will now have an ID because the Umbraco provider + /// uses the umbraco data store - then of course we need to re-map it to the saved property values. + /// + private MembershipUser CreateWithMembershipProvider(MemberSave contentItem, out MembershipCreateStatus status) + { + MembershipUser membershipUser; - switch (MembershipScenario) - { - case MembershipScenario.NativeUmbraco: - //We are using the umbraco membership provider, create the member using the membership provider first. - var umbracoMembershipProvider = (UmbracoMembershipProviderBase)_provider; - //TODO: We are not supporting q/a - passing in empty here - membershipUser = umbracoMembershipProvider.CreateUser( - contentItem.ContentTypeAlias, contentItem.Username, - contentItem.Password.NewPassword, - contentItem.Email, "", "", - contentItem.IsApproved, - Guid.NewGuid(), //since it's the umbraco provider, the user key here doesn't make any difference - out status); + switch (MembershipScenario) + { + case MembershipScenario.NativeUmbraco: + //We are using the umbraco membership provider, create the member using the membership provider first. + var umbracoMembershipProvider = (UmbracoMembershipProviderBase)_provider; + //TODO: We are not supporting q/a - passing in empty here + membershipUser = umbracoMembershipProvider.CreateUser( + contentItem.ContentTypeAlias, contentItem.Username, + contentItem.Password.NewPassword, + contentItem.Email, "", "", + contentItem.IsApproved, + Guid.NewGuid(), //since it's the umbraco provider, the user key here doesn't make any difference + out status); - break; - case MembershipScenario.CustomProviderWithUmbracoLink: - //We are using a custom membership provider, we'll create an empty IMember first to get the unique id to use - // as the provider user key. - //create it - this persisted item has already been set in the MemberBinder based on the 'Member' member type: - Services.MemberService.Save(contentItem.PersistedContent); + break; + case MembershipScenario.CustomProviderWithUmbracoLink: + //We are using a custom membership provider, we'll create an empty IMember first to get the unique id to use + // as the provider user key. + //create it - this persisted item has already been set in the MemberBinder based on the 'Member' member type: + Services.MemberService.Save(contentItem.PersistedContent); - //TODO: We are not supporting q/a - passing in empty here - membershipUser = _provider.CreateUser( - contentItem.Username, - contentItem.Password.NewPassword, - contentItem.Email, - "TEMP", //some membership provider's require something here even if q/a is disabled! - "TEMP", //some membership provider's require something here even if q/a is disabled! - contentItem.IsApproved, - contentItem.PersistedContent.Key, //custom membership provider, we'll link that based on the IMember unique id (GUID) - out status); + //TODO: We are not supporting q/a - passing in empty here + membershipUser = _provider.CreateUser( + contentItem.Username, + contentItem.Password.NewPassword, + contentItem.Email, + "TEMP", //some membership provider's require something here even if q/a is disabled! + "TEMP", //some membership provider's require something here even if q/a is disabled! + contentItem.IsApproved, + contentItem.PersistedContent.Key, //custom membership provider, we'll link that based on the IMember unique id (GUID) + out status); - break; - case MembershipScenario.StandaloneCustomProvider: - // we don't have a member type to use so we will just create the basic membership user with the provider with no - // link back to the umbraco data + break; + case MembershipScenario.StandaloneCustomProvider: + // we don't have a member type to use so we will just create the basic membership user with the provider with no + // link back to the umbraco data - var newKey = Guid.NewGuid(); - //TODO: We are not supporting q/a - passing in empty here - membershipUser = _provider.CreateUser( - contentItem.Username, - contentItem.Password.NewPassword, - contentItem.Email, - "TEMP", //some membership provider's require something here even if q/a is disabled! - "TEMP", //some membership provider's require something here even if q/a is disabled! - contentItem.IsApproved, - newKey, - out status); + var newKey = Guid.NewGuid(); + //TODO: We are not supporting q/a - passing in empty here + membershipUser = _provider.CreateUser( + contentItem.Username, + contentItem.Password.NewPassword, + contentItem.Email, + "TEMP", //some membership provider's require something here even if q/a is disabled! + "TEMP", //some membership provider's require something here even if q/a is disabled! + contentItem.IsApproved, + newKey, + out status); - break; - default: - throw new ArgumentOutOfRangeException(); - } + break; + default: + throw new ArgumentOutOfRangeException(); + } - //TODO: Localize these! - switch (status) - { - case MembershipCreateStatus.Success: + //TODO: Localize these! + switch (status) + { + case MembershipCreateStatus.Success: - //map the key back - contentItem.Key = membershipUser.ProviderUserKey.TryConvertTo().Result; - contentItem.PersistedContent.Key = contentItem.Key; + //map the key back + contentItem.Key = membershipUser.ProviderUserKey.TryConvertTo().Result; + contentItem.PersistedContent.Key = contentItem.Key; - //if the comments are there then we need to save them - if (contentItem.Comments.IsNullOrWhiteSpace() == false) - { - membershipUser.Comment = contentItem.Comments; - _provider.UpdateUser(membershipUser); - } + //if the comments are there then we need to save them + if (contentItem.Comments.IsNullOrWhiteSpace() == false) + { + membershipUser.Comment = contentItem.Comments; + _provider.UpdateUser(membershipUser); + } - RefetchMemberData(contentItem, LookupType.ByUserName); + RefetchMemberData(contentItem, LookupType.ByUserName); - break; - case MembershipCreateStatus.InvalidUserName: - ModelState.AddPropertyError( - new ValidationResult("Invalid user name", new[] { "value" }), - string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - break; - case MembershipCreateStatus.InvalidPassword: - ModelState.AddPropertyError( - new ValidationResult("Invalid password", new[] { "value" }), - string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - break; - case MembershipCreateStatus.InvalidQuestion: - case MembershipCreateStatus.InvalidAnswer: - throw new NotSupportedException("Currently the member editor does not support providers that have RequiresQuestionAndAnswer specified"); - case MembershipCreateStatus.InvalidEmail: - ModelState.AddPropertyError( - new ValidationResult("Invalid email", new[] { "value" }), - string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - break; - case MembershipCreateStatus.DuplicateUserName: - ModelState.AddPropertyError( - new ValidationResult("Username is already in use", new[] { "value" }), - string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - break; - case MembershipCreateStatus.DuplicateEmail: - ModelState.AddPropertyError( - new ValidationResult("Email address is already in use", new[] { "value" }), - string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - break; - case MembershipCreateStatus.InvalidProviderUserKey: - ModelState.AddPropertyError( - //specify 'default' just so that it shows up as a notification - is not assigned to a property - new ValidationResult("Invalid provider user key"), "default"); - break; - case MembershipCreateStatus.DuplicateProviderUserKey: - ModelState.AddPropertyError( - //specify 'default' just so that it shows up as a notification - is not assigned to a property - new ValidationResult("Duplicate provider user key"), "default"); - break; - case MembershipCreateStatus.ProviderError: - case MembershipCreateStatus.UserRejected: - ModelState.AddPropertyError( - //specify 'default' just so that it shows up as a notification - is not assigned to a property - new ValidationResult("User could not be created (rejected by provider)"), "default"); - break; - default: - throw new ArgumentOutOfRangeException(); - } + break; + case MembershipCreateStatus.InvalidUserName: + ModelState.AddPropertyError( + new ValidationResult("Invalid user name", new[] { "value" }), + string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + case MembershipCreateStatus.InvalidPassword: + ModelState.AddPropertyError( + new ValidationResult("Invalid password", new[] { "value" }), + string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + case MembershipCreateStatus.InvalidQuestion: + case MembershipCreateStatus.InvalidAnswer: + throw new NotSupportedException("Currently the member editor does not support providers that have RequiresQuestionAndAnswer specified"); + case MembershipCreateStatus.InvalidEmail: + ModelState.AddPropertyError( + new ValidationResult("Invalid email", new[] { "value" }), + string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + case MembershipCreateStatus.DuplicateUserName: + ModelState.AddPropertyError( + new ValidationResult("Username is already in use", new[] { "value" }), + string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + case MembershipCreateStatus.DuplicateEmail: + ModelState.AddPropertyError( + new ValidationResult("Email address is already in use", new[] { "value" }), + string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + break; + case MembershipCreateStatus.InvalidProviderUserKey: + ModelState.AddPropertyError( + //specify 'default' just so that it shows up as a notification - is not assigned to a property + new ValidationResult("Invalid provider user key"), "default"); + break; + case MembershipCreateStatus.DuplicateProviderUserKey: + ModelState.AddPropertyError( + //specify 'default' just so that it shows up as a notification - is not assigned to a property + new ValidationResult("Duplicate provider user key"), "default"); + break; + case MembershipCreateStatus.ProviderError: + case MembershipCreateStatus.UserRejected: + ModelState.AddPropertyError( + //specify 'default' just so that it shows up as a notification - is not assigned to a property + new ValidationResult("User could not be created (rejected by provider)"), "default"); + break; + default: + throw new ArgumentOutOfRangeException(); + } - return membershipUser; - } + return membershipUser; + } - /// - /// Permanently deletes a member - /// - /// - /// - /// - [HttpPost] - public HttpResponseMessage DeleteByKey(Guid key) - { - IMember foundMember; - MembershipUser foundMembershipUser; - switch (MembershipScenario) - { - case MembershipScenario.NativeUmbraco: - foundMember = Services.MemberService.GetByKey(key); - if (foundMember == null) - { - return HandleContentNotFound(key, false); - } - Services.MemberService.Delete(foundMember); - break; - case MembershipScenario.CustomProviderWithUmbracoLink: - foundMember = Services.MemberService.GetByKey(key); - if (foundMember != null) - { - Services.MemberService.Delete(foundMember); - } - foundMembershipUser = _provider.GetUser(key, false); - if (foundMembershipUser != null) - { - _provider.DeleteUser(foundMembershipUser.UserName, true); - } - break; - case MembershipScenario.StandaloneCustomProvider: - foundMembershipUser = _provider.GetUser(key, false); - if (foundMembershipUser != null) - { - _provider.DeleteUser(foundMembershipUser.UserName, true); - } - break; - default: - throw new ArgumentOutOfRangeException(); - } + /// + /// Permanently deletes a member + /// + /// + /// + /// + [HttpPost] + public HttpResponseMessage DeleteByKey(Guid key) + { + IMember foundMember; + MembershipUser foundMembershipUser; + switch (MembershipScenario) + { + case MembershipScenario.NativeUmbraco: + foundMember = Services.MemberService.GetByKey(key); + if (foundMember == null) + { + return HandleContentNotFound(key, false); + } + Services.MemberService.Delete(foundMember); + break; + case MembershipScenario.CustomProviderWithUmbracoLink: + foundMember = Services.MemberService.GetByKey(key); + if (foundMember != null) + { + Services.MemberService.Delete(foundMember); + } + foundMembershipUser = _provider.GetUser(key, false); + if (foundMembershipUser != null) + { + _provider.DeleteUser(foundMembershipUser.UserName, true); + } + break; + case MembershipScenario.StandaloneCustomProvider: + foundMembershipUser = _provider.GetUser(key, false); + if (foundMembershipUser != null) + { + _provider.DeleteUser(foundMembershipUser.UserName, true); + } + break; + default: + throw new ArgumentOutOfRangeException(); + } - return Request.CreateResponse(HttpStatusCode.OK); - } - } + return Request.CreateResponse(HttpStatusCode.OK); + } + } } diff --git a/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs b/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs index 92709916ac..edd1792e0e 100644 --- a/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs @@ -1,54 +1,54 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Web; -using System.Web.Http.Controllers; -using System.Web.Http.Filters; -using Umbraco.Core; - -namespace Umbraco.Web.WebApi.Filters -{ - /// - /// Ensures that the request is not cached by the browser - /// - public class DisableBrowserCacheAttribute : ActionFilterAttribute - { - public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) - { - //See: http://stackoverflow.com/questions/17755239/how-to-stop-chrome-from-caching-rest-response-from-webapi - - base.OnActionExecuted(actionExecutedContext); - if (actionExecutedContext == null || actionExecutedContext.Response == null || - actionExecutedContext.Response.Headers == null) - { - return; - } - //NOTE: Until we upgraded to WebApi 2, this didn't work correctly and we had to revert to using - // HttpContext.Current responses. I've changed this back to what it should be now since it works - // and now with WebApi2, the HttpContext.Current responses dont! Anyways, all good now. - actionExecutedContext.Response.Headers.CacheControl = new CacheControlHeaderValue() - { - NoCache = true, - NoStore = true, - MaxAge = new TimeSpan(0), - MustRevalidate = true - }; - - actionExecutedContext.Response.Headers.Pragma.Add(new NameValueHeaderValue("no-cache")); - if (actionExecutedContext.Response.Content != null) - { - actionExecutedContext.Response.Content.Headers.Expires = - //Mon, 01 Jan 1990 00:00:00 GMT - new DateTimeOffset(1990, 1, 1, 0, 0, 0, TimeSpan.Zero); - } - - - - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Web; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; +using Umbraco.Core; + +namespace Umbraco.Web.WebApi.Filters +{ + /// + /// Ensures that the request is not cached by the browser + /// + public class DisableBrowserCacheAttribute : ActionFilterAttribute + { + public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) + { + //See: http://stackoverflow.com/questions/17755239/how-to-stop-chrome-from-caching-rest-response-from-webapi + + base.OnActionExecuted(actionExecutedContext); + if (actionExecutedContext == null || actionExecutedContext.Response == null || + actionExecutedContext.Response.Headers == null) + { + return; + } + //NOTE: Until we upgraded to WebApi 2, this didn't work correctly and we had to revert to using + // HttpContext.Current responses. I've changed this back to what it should be now since it works + // and now with WebApi2, the HttpContext.Current responses dont! Anyways, all good now. + actionExecutedContext.Response.Headers.CacheControl = new CacheControlHeaderValue() + { + NoCache = true, + NoStore = true, + MaxAge = new TimeSpan(0), + MustRevalidate = true + }; + + actionExecutedContext.Response.Headers.Pragma.Add(new NameValueHeaderValue("no-cache")); + if (actionExecutedContext.Response.Content != null) + { + actionExecutedContext.Response.Content.Headers.Expires = + //Mon, 01 Jan 1990 00:00:00 GMT + new DateTimeOffset(1990, 1, 1, 0, 0, 0, TimeSpan.Zero); + } + + + + } + } +} diff --git a/src/UmbracoExamine/UmbracoMemberIndexer.cs b/src/UmbracoExamine/UmbracoMemberIndexer.cs index 8edc470bfa..4a460e0f38 100644 --- a/src/UmbracoExamine/UmbracoMemberIndexer.cs +++ b/src/UmbracoExamine/UmbracoMemberIndexer.cs @@ -17,207 +17,207 @@ using Lucene.Net.Analysis; namespace UmbracoExamine { - /// - /// Custom indexer for members - /// - public class UmbracoMemberIndexer : UmbracoContentIndexer - { + /// + /// Custom indexer for members + /// + public class UmbracoMemberIndexer : UmbracoContentIndexer + { - private readonly IMemberService _memberService; - private readonly IDataTypeService _dataTypeService; + private readonly IMemberService _memberService; + private readonly IDataTypeService _dataTypeService; - /// - /// Default constructor - /// - public UmbracoMemberIndexer() : base() - { - _dataTypeService = ApplicationContext.Current.Services.DataTypeService; - _memberService = ApplicationContext.Current.Services.MemberService; - } + /// + /// Default constructor + /// + public UmbracoMemberIndexer() : base() + { + _dataTypeService = ApplicationContext.Current.Services.DataTypeService; + _memberService = ApplicationContext.Current.Services.MemberService; + } - /// - /// Constructor to allow for creating an indexer at runtime - /// - /// - /// - /// - /// - [Obsolete("Use the overload that specifies the Umbraco services")] - public UmbracoMemberIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, Analyzer analyzer, bool async) - : base(indexerData, indexPath, dataService, analyzer, async) - { - _dataTypeService = ApplicationContext.Current.Services.DataTypeService; - _memberService = ApplicationContext.Current.Services.MemberService; - } + /// + /// Constructor to allow for creating an indexer at runtime + /// + /// + /// + /// + /// + [Obsolete("Use the overload that specifies the Umbraco services")] + public UmbracoMemberIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, Analyzer analyzer, bool async) + : base(indexerData, indexPath, dataService, analyzer, async) + { + _dataTypeService = ApplicationContext.Current.Services.DataTypeService; + _memberService = ApplicationContext.Current.Services.MemberService; + } - /// - /// Constructor to allow for creating an indexer at runtime - /// - /// - /// - /// - /// - /// - /// - /// - public UmbracoMemberIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, - IDataTypeService dataTypeService, - IMemberService memberService, - Analyzer analyzer, bool async) - : base(indexerData, indexPath, dataService, analyzer, async) - { - _dataTypeService = dataTypeService; - _memberService = memberService; - } + /// + /// Constructor to allow for creating an indexer at runtime + /// + /// + /// + /// + /// + /// + /// + /// + public UmbracoMemberIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, + IDataTypeService dataTypeService, + IMemberService memberService, + Analyzer analyzer, bool async) + : base(indexerData, indexPath, dataService, analyzer, async) + { + _dataTypeService = dataTypeService; + _memberService = memberService; + } - /// - /// Ensures that the'_searchEmail' is added to the user fields so that it is indexed - without having to modify the config - /// - /// - /// - protected override IIndexCriteria GetIndexerData(IndexSet indexSet) - { - var indexerData = base.GetIndexerData(indexSet); + /// + /// Ensures that the'_searchEmail' is added to the user fields so that it is indexed - without having to modify the config + /// + /// + /// + protected override IIndexCriteria GetIndexerData(IndexSet indexSet) + { + var indexerData = base.GetIndexerData(indexSet); - if (CanInitialize()) - { - //If the fields are missing a custom _searchEmail, then add it + if (CanInitialize()) + { + //If the fields are missing a custom _searchEmail, then add it - if (indexerData.UserFields.Any(x => x.Name == "_searchEmail") == false) - { - var field = new IndexField { Name = "_searchEmail" }; - var policy = IndexFieldPolicies.FirstOrDefault(x => x.Name == "_searchEmail"); - if (policy != null) - { - field.Type = policy.Type; - field.EnableSorting = policy.EnableSorting; - } + if (indexerData.UserFields.Any(x => x.Name == "_searchEmail") == false) + { + var field = new IndexField { Name = "_searchEmail" }; + var policy = IndexFieldPolicies.FirstOrDefault(x => x.Name == "_searchEmail"); + if (policy != null) + { + field.Type = policy.Type; + field.EnableSorting = policy.EnableSorting; + } - return new IndexCriteria( - indexerData.StandardFields, - indexerData.UserFields.Concat(new[] { field }), - indexerData.IncludeNodeTypes, - indexerData.ExcludeNodeTypes, - indexerData.ParentNodeId - ); - } - } + return new IndexCriteria( + indexerData.StandardFields, + indexerData.UserFields.Concat(new[] { field }), + indexerData.IncludeNodeTypes, + indexerData.ExcludeNodeTypes, + indexerData.ParentNodeId + ); + } + } - return indexerData; - } + return indexerData; + } - /// - /// The supported types for this indexer - /// - protected override IEnumerable SupportedTypes - { - get - { - return new string[] { IndexTypes.Member }; - } - } + /// + /// The supported types for this indexer + /// + protected override IEnumerable SupportedTypes + { + get + { + return new string[] { IndexTypes.Member }; + } + } - /// - /// Reindex all members - /// - /// - protected override void PerformIndexAll(string type) - { - //This only supports members - if (SupportedTypes.Contains(type) == false) - return; + /// + /// Reindex all members + /// + /// + protected override void PerformIndexAll(string type) + { + //This only supports members + if (SupportedTypes.Contains(type) == false) + return; - const int pageSize = 1000; - var pageIndex = 0; + const int pageSize = 1000; + var pageIndex = 0; - IMember[] members; + IMember[] members; - if (IndexerData.IncludeNodeTypes.Any()) - { - //if there are specific node types then just index those - foreach (var nodeType in IndexerData.IncludeNodeTypes) - { - do - { - long total; - members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName" - , Direction.Ascending, true, nodeType).ToArray(); + if (IndexerData.IncludeNodeTypes.Any()) + { + //if there are specific node types then just index those + foreach (var nodeType in IndexerData.IncludeNodeTypes) + { + do + { + long total; + members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName" + , Direction.Ascending, true, nodeType).ToArray(); - AddNodesToIndex(GetSerializedMembers(members), type); + AddNodesToIndex(GetSerializedMembers(members), type); - pageIndex++; - } while (members.Length == pageSize); - } - } - else - { - //no node types specified, do all members - do - { - int total; - members = _memberService.GetAll(pageIndex, pageSize, out total).ToArray(); + pageIndex++; + } while (members.Length == pageSize); + } + } + else + { + //no node types specified, do all members + do + { + int total; + members = _memberService.GetAll(pageIndex, pageSize, out total).ToArray(); - AddNodesToIndex(GetSerializedMembers(members), type); + AddNodesToIndex(GetSerializedMembers(members), type); - pageIndex++; - } while (members.Length == pageSize); - } - } + pageIndex++; + } while (members.Length == pageSize); + } + } - private IEnumerable GetSerializedMembers(IEnumerable members) - { - var serializer = new EntityXmlSerializer(); - return members.Select(member => serializer.Serialize(_dataTypeService, member)); - } + private IEnumerable GetSerializedMembers(IEnumerable members) + { + var serializer = new EntityXmlSerializer(); + return members.Select(member => serializer.Serialize(_dataTypeService, member)); + } - protected override XDocument GetXDocument(string xPath, string type) - { - throw new NotSupportedException(); - } + protected override XDocument GetXDocument(string xPath, string type) + { + throw new NotSupportedException(); + } - protected override Dictionary GetSpecialFieldsToIndex(Dictionary allValuesForIndexing) - { - var fields = base.GetSpecialFieldsToIndex(allValuesForIndexing); + protected override Dictionary GetSpecialFieldsToIndex(Dictionary allValuesForIndexing) + { + var fields = base.GetSpecialFieldsToIndex(allValuesForIndexing); - //adds the special path property to the index - fields.Add("__key", allValuesForIndexing["__key"]); + //adds the special path property to the index + fields.Add("__key", allValuesForIndexing["__key"]); - return fields; + return fields; - } + } - /// - /// Add the special __key and _searchEmail fields - /// - /// - protected override void OnGatheringNodeData(IndexingNodeDataEventArgs e) - { - base.OnGatheringNodeData(e); + /// + /// Add the special __key and _searchEmail fields + /// + /// + protected override void OnGatheringNodeData(IndexingNodeDataEventArgs e) + { + base.OnGatheringNodeData(e); - if (e.Node.Attribute("key") != null) - { - if (e.Fields.ContainsKey("__key") == false) - e.Fields.Add("__key", e.Node.Attribute("key").Value); - } + if (e.Node.Attribute("key") != null) + { + if (e.Fields.ContainsKey("__key") == false) + e.Fields.Add("__key", e.Node.Attribute("key").Value); + } - if (e.Node.Attribute("email") != null) - { - //NOTE: the single underscore = it's not a 'special' field which means it will be indexed normally - if (e.Fields.ContainsKey("_searchEmail") == false) - e.Fields.Add("_searchEmail", e.Node.Attribute("email").Value.Replace(".", " ").Replace("@", " ")); - } + if (e.Node.Attribute("email") != null) + { + //NOTE: the single underscore = it's not a 'special' field which means it will be indexed normally + if (e.Fields.ContainsKey("_searchEmail") == false) + e.Fields.Add("_searchEmail", e.Node.Attribute("email").Value.Replace(".", " ").Replace("@", " ")); + } - if (e.Fields.ContainsKey(IconFieldName) == false) - e.Fields.Add(IconFieldName, (string)e.Node.Attribute("icon")); - } + if (e.Fields.ContainsKey(IconFieldName) == false) + e.Fields.Add(IconFieldName, (string)e.Node.Attribute("icon")); + } - private static XElement GetMemberItem(int nodeId) - { - //TODO: Change this so that it is not using the LegacyLibrary, just serialize manually! - var nodes = LegacyLibrary.GetMember(nodeId); - return XElement.Parse(nodes.Current.OuterXml); - } - } + private static XElement GetMemberItem(int nodeId) + { + //TODO: Change this so that it is not using the LegacyLibrary, just serialize manually! + var nodes = LegacyLibrary.GetMember(nodeId); + return XElement.Parse(nodes.Current.OuterXml); + } + } } From bd2a40d214a8ccb04e6f13b4b58d6f8e2ded4f2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ferreira?= Date: Fri, 26 Feb 2016 14:30:32 +0000 Subject: [PATCH 048/101] U4-6003 List View - Order By Custom Property Fix Original work done on https://github.com/umbraco/Umbraco-CMS/pull/711 but ported to the latest version Content below for reference With the current implementation of the list view you can only sort by system columns (Name, SortOrder etc.) and not custom columns you have added to your document types. This PR allows that. The crux of it is a sub-query added to the ORDER BY clause when we are ordering by a custom field. This looks up the field's value from the most recent content version. Provided here and not in the previous pull request is: MySQL support Have done some performance testing. On a local laptop with 1000 nodes in a list view, it's sorting in around 220-250ms. It's a little slower that sorting on native properties like node name, but still perfectly usable - there's no significant delay you see in use. Please note also: GetPagedResultsByQuery() in VersionableRepositoryBase was previously doing an ORDER BY in SQL and then repeating this via LINQ to Objects. I couldn't see that this second ordering was necessary so removed it, but wanted to flag here in case I've missed something around why this was necessary. The PR also includes small amends to fix or hide sorting on a couple of the default columns for the member amd media list views. --- .../Repositories/ContentRepository.cs | 9 +- .../Interfaces/IContentRepository.cs | 3 +- .../Interfaces/IMediaRepository.cs | 5 +- .../Interfaces/IMemberRepository.cs | 17 +- .../Repositories/MediaRepository.cs | 9 +- .../Repositories/MemberRepository.cs | 53 +- .../Repositories/VersionableRepositoryBase.cs | 110 +- .../SqlSyntax/ISqlSyntaxProvider.cs | 4 + .../SqlSyntax/MySqlSyntaxProvider.cs | 17 +- .../SqlSyntax/SqlSyntaxProviderBase.cs | 12 +- src/Umbraco.Core/Services/ContentService.cs | 40 +- src/Umbraco.Core/Services/IContentService.cs | 16 +- src/Umbraco.Core/Services/IMediaService.cs | 12 +- src/Umbraco.Core/Services/IMemberService.cs | 19 +- src/Umbraco.Core/Services/MediaService.cs | 34 +- src/Umbraco.Core/Services/MemberService.cs | 102 +- .../Repositories/ContentRepositoryTest.cs | 22 +- .../Repositories/MediaRepositoryTest.cs | 28 +- .../UmbracoExamine/IndexInitializer.cs | 134 +- .../components/umbtable.directive.js | 142 +- .../src/common/resources/content.resource.js | 1160 +++++++++-------- .../src/common/resources/media.resource.js | 842 ++++++------ .../src/common/resources/member.resource.js | 400 +++--- .../src/views/components/umb-table.html | 136 +- .../list/list.listviewlayout.controller.js | 151 +-- .../listview/listview.controller.js | 911 ++++++------- src/Umbraco.Web/Editors/ContentController.cs | 184 +-- src/Umbraco.Web/Editors/MediaController.cs | 121 +- src/Umbraco.Web/Editors/MemberController.cs | 71 +- .../Filters/DisableBrowserCacheAttribute.cs | 104 +- src/UmbracoExamine/UmbracoMemberIndexer.cs | 115 +- 31 files changed, 2526 insertions(+), 2457 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 7d3557a81d..3ef1df3bc5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -241,8 +241,8 @@ namespace Umbraco.Core.Persistence.Repositories // see: http://issues.umbraco.org/issue/U4-6322 & http://issues.umbraco.org/issue/U4-5982 var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, new Tuple("cmsDocument", "nodeId"), - ProcessQuery, "Path", Direction.Ascending); - + ProcessQuery, "Path", Direction.Ascending, orderBySystemField: true); + var xmlItems = (from descendant in descendants let xml = serializer(descendant) select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray(); @@ -766,10 +766,11 @@ namespace Umbraco.Core.Persistence.Repositories /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") { //NOTE: This uses the GetBaseQuery method but that does not take into account the required 'newest' field which is @@ -788,7 +789,7 @@ namespace Umbraco.Core.Persistence.Repositories return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsDocument", "nodeId"), - ProcessQuery, orderBy, orderDirection, + ProcessQuery, orderBy, orderDirection, orderBySystemField, filterCallback); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index 9d3fcbb40b..ab176df6d0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -84,9 +84,10 @@ namespace Umbraco.Core.Persistence.Repositories /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string filter = ""); + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs index 46bce6a03c..907f9b62c5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Repositories { public interface IMediaRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository { - + /// /// Used to add/update published xml for the media item /// @@ -33,9 +33,10 @@ namespace Umbraco.Core.Persistence.Repositories /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string filter = ""); + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs index 9cb74d1806..a24116f0e2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberRepository.cs @@ -44,16 +44,17 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Gets paged member results /// - /// - /// - /// - /// - /// - /// - /// + /// The query. + /// Index of the page. + /// Size of the page. + /// The total records. + /// The order by column + /// The order direction. + /// Flag to indicate when ordering by system field + /// Search query /// IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string filter = ""); + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = ""); //IEnumerable GetPagedResultsByQuery( // Sql sql, int pageIndex, int pageSize, out int totalRecords, diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 162ad88ca0..bb54eebeec 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -232,7 +232,7 @@ namespace Umbraco.Core.Persistence.Repositories var processed = 0; do { - var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending); + var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending, orderBySystemField: true); var xmlItems = (from descendant in descendants let xml = serializer(descendant) @@ -442,10 +442,11 @@ namespace Umbraco.Core.Persistence.Repositories /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") { var args = new List(); var sbWhere = new StringBuilder(); @@ -459,7 +460,7 @@ namespace Umbraco.Core.Persistence.Repositories return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsContentVersion", "contentId"), - ProcessQuery, orderBy, orderDirection, + ProcessQuery, orderBy, orderDirection, orderBySystemField, filterCallback); } @@ -468,7 +469,7 @@ namespace Umbraco.Core.Persistence.Repositories { //NOTE: This doesn't allow properties to be part of the query var dtos = Database.Fetch(sql); - + var ids = dtos.Select(x => x.ContentDto.ContentTypeId).ToArray(); //content types diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index ac5529b523..e0718d0356 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -74,7 +74,7 @@ namespace Umbraco.Core.Persistence.Repositories } return ProcessQuery(sql); - + } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -95,7 +95,7 @@ namespace Umbraco.Core.Persistence.Repositories baseQuery.Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)) .OrderBy(x => x.SortOrder); - return ProcessQuery(baseQuery); + return ProcessQuery(baseQuery); } else { @@ -103,7 +103,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = translator.Translate() .OrderBy(x => x.SortOrder); - return ProcessQuery(sql); + return ProcessQuery(sql); } } @@ -118,7 +118,7 @@ namespace Umbraco.Core.Persistence.Repositories sql.Select(isCount ? "COUNT(*)" : "*") .From() .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) + .On(left => left.NodeId, right => right.NodeId) .InnerJoin() .On(left => left.NodeId, right => right.NodeId) //We're joining the type so we can do a query against the member type - not sure if this adds much overhead or not? @@ -269,7 +269,7 @@ namespace Umbraco.Core.Persistence.Repositories //Ensure that strings don't contain characters that are invalid in XML entity.SanitizeEntityPropertiesForXmlStorage(); - var dirtyEntity = (ICanBeDirty) entity; + var dirtyEntity = (ICanBeDirty)entity; //Look up parent to get and set the correct Path and update SortOrder if ParentId has changed if (dirtyEntity.IsPropertyDirty("ParentId")) @@ -308,9 +308,9 @@ namespace Umbraco.Core.Persistence.Repositories //Updates the current version - cmsContentVersion //Assumes a Version guid exists and Version date (modified date) has been set/updated Database.Update(dto.ContentVersionDto); - + //Updates the cmsMember entry if it has changed - + //NOTE: these cols are the REAL column names in the db var changedCols = new List(); @@ -330,13 +330,13 @@ namespace Umbraco.Core.Persistence.Repositories //only update the changed cols if (changedCols.Count > 0) { - Database.Update(dto, changedCols); + Database.Update(dto, changedCols); } //TODO ContentType for the Member entity //Create the PropertyData for this version - cmsPropertyData - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); + var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); var keyDictionary = new Dictionary(); //Add Properties @@ -447,7 +447,7 @@ namespace Umbraco.Core.Persistence.Repositories var processed = 0; do { - var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending); + var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending, orderBySystemField: true); var xmlItems = (from descendant in descendants let xml = serializer(descendant) @@ -559,7 +559,7 @@ namespace Umbraco.Core.Persistence.Repositories var grpQry = new Query().Where(group => group.Name.Equals(groupName)); var memberGroup = _memberGroupRepository.GetByQuery(grpQry).FirstOrDefault(); if (memberGroup == null) return Enumerable.Empty(); - + var subQuery = new Sql().Select("Member").From().Where(dto => dto.MemberGroup == memberGroup.Id); var sql = GetBaseQuery(false) @@ -568,7 +568,7 @@ namespace Umbraco.Core.Persistence.Repositories .Append(new Sql("WHERE umbracoNode.id IN (" + subQuery.SQL + ")", subQuery.Arguments)) .OrderByDescending(x => x.VersionDate) .OrderBy(x => x.SortOrder); - + return ProcessQuery(sql); } @@ -593,7 +593,7 @@ namespace Umbraco.Core.Persistence.Repositories //get the COUNT base query var fullSql = GetBaseQuery(true) .Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)); - + return Database.ExecuteScalar(fullSql); } @@ -603,18 +603,19 @@ namespace Umbraco.Core.Persistence.Repositories /// /// The where clause, if this is null all records are queried /// - /// - /// - /// - /// - /// - /// + /// Index of the page. + /// Size of the page. + /// The total records. + /// The order by column + /// The order direction. + /// Flag to indicate when ordering by system field + /// Search query /// /// /// The query supplied will ONLY work with data specifically on the cmsMember table because we are using PetaPoco paging (SQL paging) /// public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "") { var args = new List(); var sbWhere = new StringBuilder(); @@ -625,14 +626,14 @@ namespace Umbraco.Core.Persistence.Repositories "OR (cmsMember.LoginName LIKE @0" + args.Count + "))"); args.Add("%" + filter + "%"); filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray()); - } + } return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsMember", "nodeId"), - ProcessQuery, orderBy, orderDirection, + ProcessQuery, orderBy, orderDirection, orderBySystemField, filterCallback); } - + public void AddOrUpdateContentXml(IMember content, Func xml) { _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(content, xml)); @@ -682,7 +683,7 @@ namespace Umbraco.Core.Persistence.Repositories var dtosWithContentTypes = dtos //This select into and null check are required because we don't have a foreign damn key on the contentType column // http://issues.umbraco.org/issue/U4-5503 - .Select(x => new {dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentVersionDto.ContentDto.ContentTypeId)}) + .Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentVersionDto.ContentDto.ContentTypeId) }) .Where(x => x.contentType != null) .ToArray(); @@ -694,12 +695,12 @@ namespace Umbraco.Core.Persistence.Repositories d.dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, d.contentType)); - var propertyData = GetPropertyCollection(sql, docDefs); + var propertyData = GetPropertyCollection(sql, docDefs); return dtosWithContentTypes.Select(d => CreateMemberFromDto( d.dto, contentTypes.First(ct => ct.Id == d.dto.ContentVersionDto.ContentDto.ContentTypeId), - propertyData[d.dto.NodeId])); + propertyData[d.dto.NodeId])); } /// diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 56b0b63ad5..acff02c892 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -25,6 +25,7 @@ using Umbraco.Core.IO; namespace Umbraco.Core.Persistence.Repositories { + using SqlSyntax; internal abstract class VersionableRepositoryBase : PetaPocoRepositoryBase where TEntity : class, IAggregateRoot { @@ -61,11 +62,11 @@ namespace Umbraco.Core.Persistence.Repositories public virtual void DeleteVersion(Guid versionId) { var dto = Database.FirstOrDefault("WHERE versionId = @VersionId", new { VersionId = versionId }); - if(dto == null) return; + if (dto == null) return; //Ensure that the lastest version is not deleted var latestVersionDto = Database.FirstOrDefault("WHERE ContentId = @Id ORDER BY VersionDate DESC", new { Id = dto.NodeId }); - if(latestVersionDto.VersionId == dto.VersionId) + if (latestVersionDto.VersionId == dto.VersionId) return; using (var transaction = Database.GetTransaction()) @@ -83,7 +84,7 @@ namespace Umbraco.Core.Persistence.Repositories var list = Database.Fetch( "WHERE versionId <> @VersionId AND (ContentId = @Id AND VersionDate < @VersionDate)", - new {VersionId = latestVersionDto.VersionId, Id = id, VersionDate = versionDate}); + new { VersionId = latestVersionDto.VersionId, Id = id, VersionDate = versionDate }); if (list.Any() == false) return; using (var transaction = Database.GetTransaction()) @@ -244,23 +245,57 @@ namespace Umbraco.Core.Persistence.Repositories return filteredSql; } - private Sql GetSortedSqlForPagedResults(Sql sql, Direction orderDirection, string orderBy) + private Sql GetSortedSqlForPagedResults(Sql sql, Direction orderDirection, string orderBy, bool orderBySystemField) { //copy to var so that the original isn't changed var sortedSql = new Sql(sql.SQL, sql.Arguments); - // Apply order according to parameters - if (string.IsNullOrEmpty(orderBy) == false) + + if (orderBySystemField) { - var orderByParams = new[] { GetDatabaseFieldNameForOrderBy(orderBy) }; - if (orderDirection == Direction.Ascending) + // Apply order according to parameters + if (string.IsNullOrEmpty(orderBy) == false) { - sortedSql.OrderBy(orderByParams); + var orderByParams = new[] { GetDatabaseFieldNameForOrderBy(orderBy) }; + if (orderDirection == Direction.Ascending) + { + sortedSql.OrderBy(orderByParams); + } + else + { + sortedSql.OrderByDescending(orderByParams); + } } - else + } + else + { + // Sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through valie + // from most recent content version for the given order by field + var sortedInt = string.Format(SqlSyntaxContext.SqlSyntaxProvider.ConvertIntegerToOrderableString, "dataInt"); + var sortedDate = string.Format(SqlSyntaxContext.SqlSyntaxProvider.ConvertDateToOrderableString, "dataDate"); + var sortedString = string.Format(SqlSyntaxContext.SqlSyntaxProvider.IsNull, "dataNvarchar", "''"); + + var orderBySql = string.Format(@"ORDER BY ( + SELECT CASE + WHEN dataInt Is Not Null THEN {0} + WHEN dataDate Is Not Null THEN {1} + ELSE {2} + END + FROM cmsContent c + INNER JOIN cmsContentVersion cv ON cv.ContentId = c.nodeId AND VersionDate = ( + SELECT Max(VersionDate) + FROM cmsContentVersion + WHERE ContentId = c.nodeId + ) + INNER JOIN cmsPropertyData cpd ON cpd.contentNodeId = c.nodeId + AND cpd.versionId = cv.VersionId + INNER JOIN cmsPropertyType cpt ON cpt.Id = cpd.propertytypeId + WHERE c.nodeId = umbracoNode.Id and cpt.Alias = @0)", sortedInt, sortedDate, sortedString); + + sortedSql.Append(orderBySql, orderBy); + if (orderDirection == Direction.Descending) { - sortedSql.OrderByDescending(orderByParams); + sortedSql.Append(" DESC"); } - return sortedSql; } return sortedSql; } @@ -279,13 +314,15 @@ namespace Umbraco.Core.Persistence.Repositories /// A callback to process the query result /// The order by column /// The order direction. + /// Flag to indicate when ordering by system field /// /// orderBy protected IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, Tuple nodeIdSelect, Func> processQuery, - string orderBy, + string orderBy, Direction orderDirection, + bool orderBySystemField, Func> defaultFilter = null) where TContentBase : class, IAggregateRoot, TEntity { @@ -297,18 +334,18 @@ namespace Umbraco.Core.Persistence.Repositories if (query == null) query = new Query(); var translator = new SqlTranslator(sqlBase, query); var sqlQuery = translator.Translate(); - + // Note we can't do multi-page for several DTOs like we can multi-fetch and are doing in PerformGetByQuery, // but actually given we are doing a Get on each one (again as in PerformGetByQuery), we only need the node Id. // So we'll modify the SQL. var sqlNodeIds = new Sql( - sqlQuery.SQL.Replace("SELECT *", string.Format("SELECT {0}.{1}",nodeIdSelect.Item1, nodeIdSelect.Item2)), + sqlQuery.SQL.Replace("SELECT *", string.Format("SELECT {0}.{1}", nodeIdSelect.Item1, nodeIdSelect.Item2)), sqlQuery.Arguments); - + //get sorted and filtered sql var sqlNodeIdsWithSort = GetSortedSqlForPagedResults( GetFilteredSqlForPagedResults(sqlNodeIds, defaultFilter), - orderDirection, orderBy); + orderDirection, orderBy, orderBySystemField); // Get page of results and total count IEnumerable result; @@ -324,7 +361,7 @@ namespace Umbraco.Core.Persistence.Repositories var args = sqlNodeIdsWithSort.Arguments; string sqlStringCount, sqlStringPage; Database.BuildPageQueries(pageIndex * pageSize, pageSize, sqlNodeIdsWithSort.SQL, ref args, out sqlStringCount, out sqlStringPage); - + //if this is for sql server, the sqlPage will start with a SELECT * but we don't want that, we only want to return the nodeId sqlStringPage = sqlStringPage .Replace("SELECT *", @@ -333,7 +370,7 @@ namespace Umbraco.Core.Persistence.Repositories "SELECT " + nodeIdSelect.Item2); //We need to make this an inner join on the paged query - var splitQuery = sqlQuery.SQL.Split(new[] {"WHERE "}, StringSplitOptions.None); + var splitQuery = sqlQuery.SQL.Split(new[] { "WHERE " }, StringSplitOptions.None); var withInnerJoinSql = new Sql(splitQuery[0]) .Append("INNER JOIN (") //join the paged query with the paged query arguments @@ -345,23 +382,9 @@ namespace Umbraco.Core.Persistence.Repositories //get sorted and filtered sql var fullQuery = GetSortedSqlForPagedResults( - GetFilteredSqlForPagedResults(withInnerJoinSql, defaultFilter), - orderDirection, orderBy); - - var content = processQuery(fullQuery) - .Cast() - .AsQueryable(); - - // Now we need to ensure this result is also ordered by the same order by clause - var orderByProperty = GetEntityPropertyNameForOrderBy(orderBy); - if (orderDirection == Direction.Ascending) - { - result = content.OrderBy(orderByProperty); - } - else - { - result = content.OrderByDescending(orderByProperty); - } + GetFilteredSqlForPagedResults(withInnerJoinSql, defaultFilter), + orderDirection, orderBy, orderBySystemField); + return processQuery(fullQuery); } else { @@ -394,9 +417,9 @@ INNER JOIN (" + string.Format(parsedOriginalSql, "cmsContent.nodeId, cmsContentVersion.VersionId") + @") as docData ON cmsPropertyData.versionId = docData.VersionId AND cmsPropertyData.contentNodeId = docData.nodeId LEFT OUTER JOIN cmsDataTypePreValues -ON cmsPropertyType.dataTypeId = cmsDataTypePreValues.datatypeNodeId", docSql.Arguments); +ON cmsPropertyType.dataTypeId = cmsDataTypePreValues.datatypeNodeId", docSql.Arguments); - var allPropertyData = Database.Fetch(propSql); + var allPropertyData = Database.Fetch(propSql); //This is a lazy access call to get all prevalue data for the data types that make up all of these properties which we use // below if any property requires tag support @@ -414,7 +437,7 @@ WHERE EXISTS( ON cmsPropertyType.contentTypeId = docData.contentType WHERE a.id = b.id)", docSql.Arguments); - return Database.Fetch(preValsSql); + return Database.Fetch(preValsSql); }); var result = new Dictionary(); @@ -432,8 +455,8 @@ WHERE EXISTS( var propertyDataDtos = allPropertyData.Where(x => x.NodeId == def.Id).Distinct(); var propertyFactory = new PropertyFactory(compositionProperties, def.Version, def.Id, def.CreateDate, def.VersionDate); - var properties = propertyFactory.BuildEntity(propertyDataDtos.ToArray()).ToArray(); - + var properties = propertyFactory.BuildEntity(propertyDataDtos.ToArray()).ToArray(); + var newProperties = properties.Where(x => x.HasIdentity == false && x.PropertyType.HasIdentity); foreach (var property in newProperties) @@ -481,7 +504,7 @@ WHERE EXISTS( Logger.Warn>("The query returned multiple property sets for document definition " + def.Id + ", " + def.Composition.Name); } result[def.Id] = new PropertyCollection(properties); - } + } } return result; @@ -521,6 +544,9 @@ WHERE EXISTS( case "OWNER": //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter return "umbracoNode.nodeUser"; + // Members only + case "USERNAME": + return "cmsMember.LoginName"; default: //ensure invalid SQL cannot be submitted return Regex.Replace(orderBy, @"[^\w\.,`\[\]@-]", ""); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index 056d9c1d9a..e663c8e64b 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -70,6 +70,10 @@ namespace Umbraco.Core.Persistence.SqlSyntax bool SupportsIdentityInsert(); bool? SupportsCaseInsensitiveQueries(Database db); + string IsNull { get; } + string ConvertIntegerToOrderableString { get; } + string ConvertDateToOrderableString { get; } + IEnumerable GetTablesInSchema(Database db); IEnumerable GetColumnsInSchema(Database db); IEnumerable> GetConstraintsPerTable(Database db); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index c18a5f8477..c45aade707 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -18,7 +18,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax public MySqlSyntaxProvider(ILogger logger) { _logger = logger; - + AutoIncrementDefinition = "AUTO_INCREMENT"; IntColumnDefinition = "int(11)"; BoolColumnDefinition = "tinyint(1)"; @@ -26,7 +26,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax TimeColumnDefinition = "time"; DecimalColumnDefinition = "decimal(38,6)"; GuidColumnDefinition = "char(36)"; - + DefaultValueFormat = "DEFAULT {0}"; InitColumnTypeMap(); @@ -326,13 +326,13 @@ ORDER BY TABLE_NAME, INDEX_NAME", { case SystemMethods.NewGuid: return null; // NOT SUPPORTED! - //return "NEWID()"; + //return "NEWID()"; case SystemMethods.CurrentDateTime: return "CURRENT_TIMESTAMP"; - //case SystemMethods.NewSequentialId: - // return "NEWSEQUENTIALID()"; - //case SystemMethods.CurrentUTCDateTime: - // return "GETUTCDATE()"; + //case SystemMethods.NewSequentialId: + // return "NEWSEQUENTIALID()"; + //case SystemMethods.CurrentUTCDateTime: + // return "GETUTCDATE()"; } return null; @@ -360,6 +360,9 @@ ORDER BY TABLE_NAME, INDEX_NAME", public override string DropIndex { get { return "DROP INDEX {0} ON {1}"; } } public override string RenameColumn { get { return "ALTER TABLE {0} CHANGE {1} {2}"; } } + public override string IsNull { get { return "IFNULL({0},{1})"; } } + public override string ConvertIntegerToOrderableString { get { return "LPAD({0}, 8, '0')"; } } + public override string ConvertDateToOrderableString { get { return "DATE_FORMAT({0}, '%Y%m%d')"; } } public override bool? SupportsCaseInsensitiveQueries(Database db) { diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index c2b81aa753..35c133ce6e 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -322,7 +322,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax GetQuotedColumnName(foreignKey.ForeignColumns.First()), GetQuotedTableName(foreignKey.PrimaryTable), GetQuotedColumnName(foreignKey.PrimaryColumns.First()), - FormatCascade("DELETE", foreignKey.OnDelete), + FormatCascade("DELETE", foreignKey.OnDelete), FormatCascade("UPDATE", foreignKey.OnUpdate)); } @@ -331,7 +331,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax var sb = new StringBuilder(); foreach (var column in columns) { - sb.Append(Format(column) +",\n"); + sb.Append(Format(column) + ",\n"); } return sb.ToString().TrimEnd(",\n"); } @@ -431,11 +431,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax return GetSpecialDbType(column.DbType); } - Type type = column.Type.HasValue + Type type = column.Type.HasValue ? DbTypeMap.ColumnDbTypeMap.First(x => x.Value == column.Type.Value).Key : column.PropertyType; - if (type == typeof (string)) + if (type == typeof(string)) { var valueOrDefault = column.Size != default(int) ? column.Size : DefaultStringLength; return string.Format(StringLengthColumnDefinitionFormat, valueOrDefault); @@ -536,5 +536,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax public virtual string CreateConstraint { get { return "ALTER TABLE {0} ADD CONSTRAINT {1} {2} ({3})"; } } public virtual string DeleteConstraint { get { return "ALTER TABLE {0} DROP CONSTRAINT {1}"; } } public virtual string CreateForeignKeyConstraint { get { return "ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4}){5}{6}"; } } + + public virtual string IsNull { get { return "ISNULL({0},{1})"; } } + public virtual string ConvertIntegerToOrderableString { get { return "RIGHT('00000000' + CAST({0} AS varchar(8)),8)"; } } + public virtual string ConvertDateToOrderableString { get { return "CONVERT(varchar, {0}, 102)"; } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index e7298a5c6e..773d26a455 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -231,7 +231,7 @@ namespace Umbraco.Core.Services public IContent CreateContentWithIdentity(string name, int parentId, string contentTypeAlias, int userId = 0) { var contentType = FindContentTypeByAlias(contentTypeAlias); - var content = new Content(name, parentId, contentType); + 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. @@ -485,7 +485,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, - string orderBy, Direction orderDirection, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); @@ -499,7 +499,7 @@ namespace Umbraco.Core.Services query.Where(x => x.ParentId == id); } long total; - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, orderBySystemField, filter); totalChildren = Convert.ToInt32(total); return contents; } @@ -514,10 +514,11 @@ namespace Umbraco.Core.Services /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - string orderBy, Direction orderDirection, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); @@ -530,7 +531,7 @@ namespace Umbraco.Core.Services { query.Where(x => x.ParentId == id); } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, filter); + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); return contents; } @@ -538,7 +539,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); @@ -552,7 +553,7 @@ namespace Umbraco.Core.Services query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); } long total; - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, orderBySystemField, filter); totalChildren = Convert.ToInt32(total); return contents; } @@ -567,9 +568,10 @@ namespace Umbraco.Core.Services /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); @@ -582,7 +584,7 @@ namespace Umbraco.Core.Services { query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, filter); + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); return contents; } @@ -906,7 +908,7 @@ namespace Umbraco.Core.Services { var originalPath = content.Path; - if (Trashing.IsRaisedEventCancelled( + if (Trashing.IsRaisedEventCancelled( new MoveEventArgs(evtMsgs, new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent)), this)) { @@ -1022,7 +1024,7 @@ namespace Umbraco.Core.Services /// True if unpublishing succeeded, otherwise False public bool UnPublish(IContent content, int userId = 0) { - return ((IContentServiceOperations) this).UnPublish(content, userId).Success; + return ((IContentServiceOperations)this).UnPublish(content, userId).Success; } /// @@ -1143,7 +1145,7 @@ namespace Umbraco.Core.Services using (new WriteLock(Locker)) { - if (Deleting.IsRaisedEventCancelled( + if (Deleting.IsRaisedEventCancelled( new DeleteEventArgs(content, evtMsgs), this)) { @@ -1168,10 +1170,10 @@ namespace Umbraco.Core.Services { repository.Delete(content); uow.Commit(); - + var args = new DeleteEventArgs(content, false, evtMsgs); Deleted.RaiseEvent(args, this); - + //remove any flagged media files repository.DeleteMediaFiles(args.MediaFilesToDelete); } @@ -1350,7 +1352,7 @@ namespace Umbraco.Core.Services /// Optional Id of the User deleting the Content public void MoveToRecycleBin(IContent content, int userId = 0) { - ((IContentServiceOperations) this).MoveToRecycleBin(content, userId); + ((IContentServiceOperations)this).MoveToRecycleBin(content, userId); } /// @@ -1654,7 +1656,7 @@ namespace Umbraco.Core.Services //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); } - + Audit(AuditType.Sort, "Sorting content performed by user", userId, 0); @@ -1902,13 +1904,13 @@ namespace Umbraco.Core.Services content = newest; 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)); @@ -2046,7 +2048,7 @@ namespace Umbraco.Core.Services if (raiseEvents) { - if (Saving.IsRaisedEventCancelled( + if (Saving.IsRaisedEventCancelled( new SaveEventArgs(content, evtMsgs), this)) { diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index c686cf4891..95333bee85 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -203,7 +203,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); /// /// Gets a collection of objects by Parent Id @@ -214,15 +214,16 @@ namespace Umbraco.Core.Services /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); /// /// Gets a collection of objects by Parent Id @@ -233,11 +234,12 @@ namespace Umbraco.Core.Services /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); - + string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + /// /// Gets a collection of an objects versions by its Id /// @@ -268,7 +270,7 @@ namespace Umbraco.Core.Services /// /// An Enumerable list of objects IEnumerable GetContentInRecycleBin(); - + /// /// Saves a single object /// @@ -467,7 +469,7 @@ namespace Umbraco.Core.Services /// Optional boolean indicating whether or not to raise save events. /// True if publishing succeeded, otherwise False Attempt SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true); - + /// /// Permanently deletes an object. /// diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index d104b95ddc..51c01703d5 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -121,7 +121,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); /// /// Gets a collection of objects by Parent Id @@ -132,15 +132,16 @@ namespace Umbraco.Core.Services /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); /// /// Gets a collection of objects by Parent Id @@ -151,10 +152,11 @@ namespace Umbraco.Core.Services /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); /// /// Gets descendants of a object by its Id @@ -220,7 +222,7 @@ namespace Umbraco.Core.Services /// The to delete /// Id of the User deleting the Media void Delete(IMedia media, int userId = 0); - + /// /// Saves a single object /// diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 20eef54b3c..3756ca246d 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords, - string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = ""); + string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = ""); /// /// Gets a list of paged objects @@ -34,14 +34,15 @@ namespace Umbraco.Core.Services /// Current page index /// Size of the page /// Total number of records found (out) - /// - /// + /// Field to order by + /// Direction to order by + /// Flag to indicate when ordering by system field /// - /// + /// Search text filter /// IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = ""); - + string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = ""); + /// /// Creates an object without persisting it /// @@ -91,7 +92,7 @@ namespace Umbraco.Core.Services /// MemberType the Member should be based on /// IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType); - + /// /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method /// @@ -115,7 +116,7 @@ namespace Umbraco.Core.Services /// Id of the Member /// True if the Member exists otherwise False bool Exists(int id); - + /// /// Gets a Member by the unique key /// @@ -160,7 +161,7 @@ namespace Umbraco.Core.Services /// Optional list of Member Ids /// IEnumerable GetAllMembers(params int[] ids); - + /// /// Delete Members of the specified MemberType id /// diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index accbebfe0b..3bbf8860ea 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -35,7 +35,7 @@ namespace Umbraco.Core.Services private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); private readonly IDataTypeService _dataTypeService; private readonly IUserService _userService; - + public MediaService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IDataTypeService dataTypeService, IUserService userService) : base(provider, repositoryFactory, logger, eventMessagesFactory) { @@ -395,7 +395,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, - string orderBy, Direction orderDirection, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); @@ -403,9 +403,9 @@ namespace Umbraco.Core.Services { var query = Query.Builder; query.Where(x => x.ParentId == id); - + long total; - var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); + var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, orderBySystemField, filter); totalChildren = Convert.ToInt32(total); return medias; @@ -421,10 +421,11 @@ namespace Umbraco.Core.Services /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - string orderBy, Direction orderDirection, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); @@ -432,8 +433,8 @@ namespace Umbraco.Core.Services { var query = Query.Builder; query.Where(x => x.ParentId == id); - - var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, filter); + + var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); return medias; } @@ -441,7 +442,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); @@ -455,7 +456,7 @@ namespace Umbraco.Core.Services query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); } long total; - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, orderBySystemField, filter); totalChildren = Convert.ToInt32(total); return contents; } @@ -470,9 +471,10 @@ namespace Umbraco.Core.Services /// Total records query would return without paging /// Field to order by /// Direction to order by + /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); @@ -485,7 +487,7 @@ namespace Umbraco.Core.Services { query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); } - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, filter); + var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); return contents; } @@ -727,7 +729,7 @@ namespace Umbraco.Core.Services /// Id of the User deleting the Media public void MoveToRecycleBin(IMedia media, int userId = 0) { - ((IMediaServiceOperations) this).MoveToRecycleBin(media, userId); + ((IMediaServiceOperations)this).MoveToRecycleBin(media, userId); } /// @@ -744,7 +746,7 @@ namespace Umbraco.Core.Services //TODO: IT would be much nicer to mass delete all in one trans in the repo level! var evtMsgs = EventMessagesFactory.Get(); - if (Deleting.IsRaisedEventCancelled( + if (Deleting.IsRaisedEventCancelled( new DeleteEventArgs(media, evtMsgs), this)) { return OperationStatus.Cancelled(evtMsgs); @@ -1025,7 +1027,7 @@ namespace Umbraco.Core.Services ((IMediaServiceOperations)this).Delete(media, userId); } - + /// /// Permanently deletes versions from an object prior to a specific date. @@ -1081,7 +1083,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Delete, "Delete Media by version performed by user", userId, -1); } - + /// /// Saves a single object /// @@ -1090,7 +1092,7 @@ namespace Umbraco.Core.Services /// Optional boolean indicating whether or not to raise events. public void Save(IMedia media, int userId = 0, bool raiseEvents = true) { - ((IMediaServiceOperations)this).Save (media, userId, raiseEvents); + ((IMediaServiceOperations)this).Save(media, userId, raiseEvents); } /// diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 4f0647dbf7..9991583719 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Services private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); private readonly IDataTypeService _dataTypeService; private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); - + public MemberService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService, IDataTypeService dataTypeService) : base(provider, repositoryFactory, logger, eventMessagesFactory) { @@ -52,7 +52,7 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateMemberTypeRepository(UowProvider.GetUnitOfWork())) { - var types = repository.GetAll(new int[]{}).Select(x => x.Alias).ToArray(); + var types = repository.GetAll(new int[] { }).Select(x => x.Alias).ToArray(); if (types.Any() == false) { @@ -289,7 +289,7 @@ namespace Umbraco.Core.Services throw new ArgumentOutOfRangeException("matchType"); } - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Name", Direction.Ascending); + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); } } @@ -340,7 +340,7 @@ namespace Umbraco.Core.Services throw new ArgumentOutOfRangeException("matchType"); } - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Email", Direction.Ascending); + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Email", Direction.Ascending, orderBySystemField: true); } } @@ -391,7 +391,7 @@ namespace Umbraco.Core.Services throw new ArgumentOutOfRangeException("matchType"); } - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending); + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, orderBySystemField: true); } } @@ -688,33 +688,33 @@ namespace Umbraco.Core.Services var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMemberRepository(uow)) { - return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending); + return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, orderBySystemField: true); } } [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords, - string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = "") { long total; - var result = GetAll(Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, memberTypeAlias, filter); + var result = GetAll(Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, orderBySystemField, memberTypeAlias, filter); totalRecords = Convert.ToInt32(total); return result; } public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = "") { var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMemberRepository(uow)) { if (memberTypeAlias == null) { - return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filter); + return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, filter); } var query = new Query().Where(x => x.ContentTypeAlias == memberTypeAlias); - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filter); + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, filter); } } @@ -1253,52 +1253,52 @@ namespace Umbraco.Core.Services var memType = new MemberType(-1); var propGroup = new PropertyGroup - { - Name = "Membership", - Id = --identity - }; + { + Name = "Membership", + Id = --identity + }; propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext, Constants.Conventions.Member.Comments) - { - Name = Constants.Conventions.Member.CommentsLabel, - SortOrder = 0, - Id = --identity, - Key = identity.ToGuid() - }); + { + Name = Constants.Conventions.Member.CommentsLabel, + SortOrder = 0, + Id = --identity, + Key = identity.ToGuid() + }); propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer, Constants.Conventions.Member.IsApproved) - { - Name = Constants.Conventions.Member.IsApprovedLabel, - SortOrder = 3, - Id = --identity, - Key = identity.ToGuid() - }); + { + Name = Constants.Conventions.Member.IsApprovedLabel, + SortOrder = 3, + Id = --identity, + Key = identity.ToGuid() + }); propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer, Constants.Conventions.Member.IsLockedOut) - { - Name = Constants.Conventions.Member.IsLockedOutLabel, - SortOrder = 4, - Id = --identity, - Key = identity.ToGuid() - }); + { + Name = Constants.Conventions.Member.IsLockedOutLabel, + SortOrder = 4, + Id = --identity, + Key = identity.ToGuid() + }); propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastLockoutDate) - { - Name = Constants.Conventions.Member.LastLockoutDateLabel, - SortOrder = 5, - Id = --identity, - Key = identity.ToGuid() - }); + { + Name = Constants.Conventions.Member.LastLockoutDateLabel, + SortOrder = 5, + Id = --identity, + Key = identity.ToGuid() + }); propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastLoginDate) - { - Name = Constants.Conventions.Member.LastLoginDateLabel, - SortOrder = 6, - Id = --identity, - Key = identity.ToGuid() - }); + { + Name = Constants.Conventions.Member.LastLoginDateLabel, + SortOrder = 6, + Id = --identity, + Key = identity.ToGuid() + }); propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastPasswordChangeDate) - { - Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, - SortOrder = 7, - Id = --identity, - Key = identity.ToGuid() - }); + { + Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, + SortOrder = 7, + Id = --identity, + Key = identity.ToGuid() + }); memType.PropertyGroups.Add(propGroup); diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index c8ccecdb95..e256e4082a 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -72,7 +72,7 @@ namespace Umbraco.Tests.Persistence.Repositories //create 100 non published for (var i = 0; i < 100; i++) - { + { var c1 = MockedContent.CreateSimpleContent(contentType1); repository.AddOrUpdate(c1); allCreated.Add(c1); @@ -176,7 +176,7 @@ namespace Umbraco.Tests.Persistence.Repositories for (var i = 0; i < 30; i++) { //These will be non-published so shouldn't show up - var c1 = MockedContent.CreateSimpleContent(contentType1); + var c1 = MockedContent.CreateSimpleContent(contentType1); repository.AddOrUpdate(c1); allCreated.Add(c1); } @@ -275,7 +275,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Assert Assert.That(contentType.HasIdentity, Is.True); Assert.That(textpage.HasIdentity, Is.True); - + } } @@ -299,7 +299,7 @@ namespace Umbraco.Tests.Persistence.Repositories Content textpage = MockedContent.CreateSimpleContent(contentType); // Act - + contentTypeRepository.AddOrUpdate(contentType); repository.AddOrUpdate(textpage); unitOfWork.Commit(); @@ -520,7 +520,7 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) - { + { var result = repository.GetAll().ToArray(); foreach (var content in result) { @@ -555,7 +555,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -576,7 +576,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "Name", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -597,7 +597,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "Name", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -618,7 +618,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Descending); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Descending, orderBySystemField: true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -639,7 +639,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, "Page 2"); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, "Page 2"); // Assert Assert.That(totalRecords, Is.EqualTo(1)); @@ -660,7 +660,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, "Page"); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true, "Page"); // Assert Assert.That(totalRecords, Is.EqualTo(2)); diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index 60c49f89cd..32258a9961 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -27,7 +27,7 @@ namespace Umbraco.Tests.Persistence.Repositories [SetUp] public override void Initialize() - { + { base.Initialize(); CreateTestData(); @@ -51,12 +51,12 @@ namespace Umbraco.Tests.Persistence.Repositories { var mediaType = mediaTypeRepository.Get(1032); - + for (var i = 0; i < 100; i++) { var image = MockedMedia.CreateMediaImage(mediaType, -1); repository.AddOrUpdate(image); - } + } unitOfWork.Commit(); //delete all xml @@ -81,7 +81,7 @@ namespace Umbraco.Tests.Persistence.Repositories var imageMediaType = mediaTypeRepository.Get(1032); var fileMediaType = mediaTypeRepository.Get(1033); var folderMediaType = mediaTypeRepository.Get(1031); - + for (var i = 0; i < 30; i++) { var image = MockedMedia.CreateMediaImage(imageMediaType, -1); @@ -103,12 +103,12 @@ namespace Umbraco.Tests.Persistence.Repositories unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); - repository.RebuildXmlStructures(media => new XElement("test"), 10, contentTypeIds: new[] {1032, 1033}); + repository.RebuildXmlStructures(media => new XElement("test"), 10, contentTypeIds: new[] { 1032, 1033 }); Assert.AreEqual(62, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); } } - + [Test] public void Can_Perform_Add_On_MediaRepository() { @@ -207,7 +207,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var media = repository.Get(NodeDto.NodeIdSeed + 1); - bool dirty = ((ICanBeDirty) media).IsDirty(); + bool dirty = ((ICanBeDirty)media).IsDirty(); // Assert Assert.That(dirty, Is.False); @@ -319,7 +319,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, orderBySystemField: true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -340,7 +340,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "SortOrder", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "SortOrder", Direction.Ascending, orderBySystemField: true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -361,7 +361,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "SortOrder", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "SortOrder", Direction.Ascending, orderBySystemField: true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -382,7 +382,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Descending); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Descending, orderBySystemField: true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -403,7 +403,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, orderBySystemField: true); // Assert Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); @@ -424,7 +424,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, "File"); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true, "File"); // Assert Assert.That(totalRecords, Is.EqualTo(1)); @@ -445,7 +445,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, "Test"); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "SortOrder", Direction.Ascending, true, "Test"); // Assert Assert.That(totalRecords, Is.EqualTo(2)); diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index 6c56c76ef7..26f29b581b 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -20,33 +20,33 @@ using Version = Lucene.Net.Util.Version; namespace Umbraco.Tests.UmbracoExamine { - /// - /// Used internally by test classes to initialize a new index from the template - /// - internal static class IndexInitializer - { - public static UmbracoContentIndexer GetUmbracoIndexer( - Directory luceneDir, - Analyzer analyzer = null, - IDataService dataService = null, - IContentService contentService = null, - IMediaService mediaService = null, - IDataTypeService dataTypeService = null, - IMemberService memberService = null, - IUserService userService = null) - { + /// + /// Used internally by test classes to initialize a new index from the template + /// + internal static class IndexInitializer + { + public static UmbracoContentIndexer GetUmbracoIndexer( + Directory luceneDir, + Analyzer analyzer = null, + IDataService dataService = null, + IContentService contentService = null, + IMediaService mediaService = null, + IDataTypeService dataTypeService = null, + IMemberService memberService = null, + IUserService userService = null) + { if (dataService == null) { dataService = new TestDataService(); } - if (contentService == null) - { + if (contentService == null) + { contentService = Mock.Of(); - } - if (userService == null) - { - userService = Mock.Of(x => x.GetProfileById(It.IsAny()) == Mock.Of(p => p.Id == (object)0 && p.Name == "admin")); - } + } + if (userService == null) + { + userService = Mock.Of(x => x.GetProfileById(It.IsAny()) == Mock.Of(p => p.Id == (object)0 && p.Name == "admin")); + } if (mediaService == null) { long totalRecs; @@ -56,25 +56,25 @@ namespace Umbraco.Tests.UmbracoExamine .Elements() .Select(x => Mock.Of( m => - m.Id == (int) x.Attribute("id") && - m.ParentId == (int) x.Attribute("parentID") && - m.Level == (int) x.Attribute("level") && + m.Id == (int)x.Attribute("id") && + m.ParentId == (int)x.Attribute("parentID") && + m.Level == (int)x.Attribute("level") && m.CreatorId == 0 && - m.SortOrder == (int) x.Attribute("sortOrder") && - m.CreateDate == (DateTime) x.Attribute("createDate") && - m.UpdateDate == (DateTime) x.Attribute("updateDate") && - m.Name == (string) x.Attribute("nodeName") && - m.Path == (string) x.Attribute("path") && + m.SortOrder == (int)x.Attribute("sortOrder") && + m.CreateDate == (DateTime)x.Attribute("createDate") && + m.UpdateDate == (DateTime)x.Attribute("updateDate") && + m.Name == (string)x.Attribute("nodeName") && + m.Path == (string)x.Attribute("path") && m.Properties == new PropertyCollection() && m.ContentType == Mock.Of(mt => - mt.Alias == (string) x.Attribute("nodeTypeAlias") && - mt.Id == (int) x.Attribute("nodeType")))) + mt.Alias == (string)x.Attribute("nodeTypeAlias") && + mt.Id == (int)x.Attribute("nodeType")))) .ToArray(); - + mediaService = Mock.Of( x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny()) + It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()) == allRecs); } @@ -82,7 +82,7 @@ namespace Umbraco.Tests.UmbracoExamine { dataTypeService = Mock.Of(); } - + if (memberService == null) { memberService = Mock.Of(); @@ -93,51 +93,51 @@ namespace Umbraco.Tests.UmbracoExamine analyzer = new StandardAnalyzer(Version.LUCENE_29); } - var indexSet = new IndexSet(); + var indexSet = new IndexSet(); var indexCriteria = indexSet.ToIndexCriteria(dataService, UmbracoContentIndexer.IndexFieldPolicies); - var i = new UmbracoContentIndexer(indexCriteria, - luceneDir, //custom lucene directory - dataService, - contentService, - mediaService, - dataTypeService, - userService, - analyzer, - false); + var i = new UmbracoContentIndexer(indexCriteria, + luceneDir, //custom lucene directory + dataService, + contentService, + mediaService, + dataTypeService, + userService, + analyzer, + false); - //i.IndexSecondsInterval = 1; + //i.IndexSecondsInterval = 1; - i.IndexingError += IndexingError; + i.IndexingError += IndexingError; - return i; - } + return i; + } public static UmbracoExamineSearcher GetUmbracoSearcher(Directory luceneDir, Analyzer analyzer = null) - { + { if (analyzer == null) { analyzer = new StandardAnalyzer(Version.LUCENE_29); } return new UmbracoExamineSearcher(luceneDir, analyzer); - } - - public static LuceneSearcher GetLuceneSearcher(Directory luceneDir) - { - return new LuceneSearcher(luceneDir, new StandardAnalyzer(Version.LUCENE_29)); - } - - public static MultiIndexSearcher GetMultiSearcher(Directory pdfDir, Directory simpleDir, Directory conventionDir, Directory cwsDir) - { - var i = new MultiIndexSearcher(new[] { pdfDir, simpleDir, conventionDir, cwsDir }, new StandardAnalyzer(Version.LUCENE_29)); - return i; - } + } + + public static LuceneSearcher GetLuceneSearcher(Directory luceneDir) + { + return new LuceneSearcher(luceneDir, new StandardAnalyzer(Version.LUCENE_29)); + } + + public static MultiIndexSearcher GetMultiSearcher(Directory pdfDir, Directory simpleDir, Directory conventionDir, Directory cwsDir) + { + var i = new MultiIndexSearcher(new[] { pdfDir, simpleDir, conventionDir, cwsDir }, new StandardAnalyzer(Version.LUCENE_29)); + return i; + } - internal static void IndexingError(object sender, IndexingErrorEventArgs e) - { - throw new ApplicationException(e.Message, e.InnerException); - } + internal static void IndexingError(object sender, IndexingErrorEventArgs e) + { + throw new ApplicationException(e.Message, e.InnerException); + } - } + } } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js index fe80d915da..bf593397b6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbtable.directive.js @@ -1,71 +1,71 @@ -(function() { - 'use strict'; - - function TableDirective() { - - function link(scope, el, attr, ctrl) { - - scope.clickItem = function(item, $event) { - if(scope.onClick) { - scope.onClick(item); - $event.stopPropagation(); - } - }; - - scope.selectItem = function(item, $index, $event) { - if(scope.onSelect) { - scope.onSelect(item, $index, $event); - $event.stopPropagation(); - } - }; - - scope.selectAll = function($event) { - if(scope.onSelectAll) { - scope.onSelectAll($event); - } - }; - - scope.isSelectedAll = function() { - if(scope.onSelectedAll && scope.items && scope.items.length > 0) { - return scope.onSelectedAll(); - } - }; - - scope.isSortDirection = function (col, direction) { - if (scope.onSortingDirection) { - return scope.onSortingDirection(col, direction); - } - }; - - scope.sort = function(field, allow) { - if(scope.onSort) { - scope.onSort(field, allow); - } - }; - - } - - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-table.html', - scope: { - items: '=', - itemProperties: '=', - allowSelectAll: '=', - onSelect: '=', - onClick: '=', - onSelectAll: '=', - onSelectedAll: '=', - onSortingDirection: '=', - onSort: '=' - }, - link: link - }; - - return directive; - } - - angular.module('umbraco.directives').directive('umbTable', TableDirective); - -})(); +(function () { + 'use strict'; + + function TableDirective() { + + function link(scope, el, attr, ctrl) { + + scope.clickItem = function (item, $event) { + if (scope.onClick) { + scope.onClick(item); + $event.stopPropagation(); + } + }; + + scope.selectItem = function (item, $index, $event) { + if (scope.onSelect) { + scope.onSelect(item, $index, $event); + $event.stopPropagation(); + } + }; + + scope.selectAll = function ($event) { + if (scope.onSelectAll) { + scope.onSelectAll($event); + } + }; + + scope.isSelectedAll = function () { + if (scope.onSelectedAll && scope.items && scope.items.length > 0) { + return scope.onSelectedAll(); + } + }; + + scope.isSortDirection = function (col, direction) { + if (scope.onSortingDirection) { + return scope.onSortingDirection(col, direction); + } + }; + + scope.sort = function (field, allow, isSystem) { + if (scope.onSort) { + scope.onSort(field, allow, isSystem); + } + }; + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-table.html', + scope: { + items: '=', + itemProperties: '=', + allowSelectAll: '=', + onSelect: '=', + onClick: '=', + onSelectAll: '=', + onSelectedAll: '=', + onSortingDirection: '=', + onSort: '=' + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbTable', TableDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 2cf7127707..60ecd16f19 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -25,634 +25,636 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { - /** internal method process the saving of data and post processing the result */ - function saveContentItem(content, action, files) { - return umbRequestHelper.postSaveContent({ - restApiUrl: umbRequestHelper.getApiUrl( + /** internal method process the saving of data and post processing the result */ + function saveContentItem(content, action, files) { + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl( "contentApiBaseUrl", "PostSave"), - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatContentPostData(c, a); - } - }); - } + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatContentPostData(c, a); + } + }); + } - return { - - getRecycleBin: function() { - return umbRequestHelper.resourcePromise( + return { + + getRecycleBin: function () { + return umbRequestHelper.resourcePromise( $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetRecycleBin")), + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetRecycleBin")), 'Failed to retrieve data for content recycle bin'); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#sort - * @methodOf umbraco.resources.contentResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
      -         * var ids = [123,34533,2334,23434];
      -         * contentResource.sort({ parentId: 1244, sortedIds: ids })
      -         *    .then(function() {
      -         *        $scope.complete = true;
      -         *    });
      -         * 
      - * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ - sort: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.sortedIds) { - throw "args.sortedIds cannot be null"; - } + /** + * @ngdoc method + * @name umbraco.resources.contentResource#sort + * @methodOf umbraco.resources.contentResource + * + * @description + * Sorts all children below a given parent node id, based on a collection of node-ids + * + * ##usage + *
      +        * var ids = [123,34533,2334,23434];
      +        * contentResource.sort({ parentId: 1244, sortedIds: ids })
      +        *    .then(function() {
      +        *        $scope.complete = true;
      +        *    });
      +        * 
      + * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ + sort: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.sortedIds) { + throw "args.sortedIds cannot be null"; + } - return umbRequestHelper.resourcePromise( + return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), + { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), 'Failed to sort content'); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#move - * @methodOf umbraco.resources.contentResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
      -         * contentResource.move({ parentId: 1244, id: 123 })
      -         *    .then(function() {
      -         *        alert("node was moved");
      -         *    }, function(err){
      -         *      alert("node didnt move:" + err.data.Message); 
      -         *    });
      -         * 
      - * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } + /** + * @ngdoc method + * @name umbraco.resources.contentResource#move + * @methodOf umbraco.resources.contentResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
      +        * contentResource.move({ parentId: 1244, id: 123 })
      +        *    .then(function() {
      +        *        alert("node was moved");
      +        *    }, function(err){
      +        *      alert("node didnt move:" + err.data.Message); 
      +        *    });
      +        * 
      + * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } - return umbRequestHelper.resourcePromise( + return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), + { + parentId: args.parentId, + id: args.id + }), 'Failed to move content'); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#copy - * @methodOf umbraco.resources.contentResource - * - * @description - * Copies a node underneath a new parentId - * - * ##usage - *
      -         * contentResource.copy({ parentId: 1244, id: 123 })
      -         *    .then(function() {
      -         *        alert("node was copied");
      -         *    }, function(err){
      -         *      alert("node wasnt copy:" + err.data.Message); 
      -         *    });
      -         * 
      - * @param {Object} args arguments object - * @param {Int} args.id the ID of the node to copy - * @param {Int} args.parentId the ID of the parent node to copy to - * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api - * @returns {Promise} resourcePromise object. - * - */ - copy: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } + /** + * @ngdoc method + * @name umbraco.resources.contentResource#copy + * @methodOf umbraco.resources.contentResource + * + * @description + * Copies a node underneath a new parentId + * + * ##usage + *
      +        * contentResource.copy({ parentId: 1244, id: 123 })
      +        *    .then(function() {
      +        *        alert("node was copied");
      +        *    }, function(err){
      +        *      alert("node wasnt copy:" + err.data.Message); 
      +        *    });
      +        * 
      + * @param {Object} args arguments object + * @param {Int} args.id the ID of the node to copy + * @param {Int} args.parentId the ID of the parent node to copy to + * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api + * @returns {Promise} resourcePromise object. + * + */ + copy: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } - return umbRequestHelper.resourcePromise( + return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"), - args), + args), 'Failed to copy content'); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#unPublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Unpublishes a content item with a given Id - * - * ##usage - *
      -         * contentResource.unPublish(1234)
      -         *    .then(function() {
      -         *        alert("node was unpulished");
      -         *    }, function(err){
      -         *      alert("node wasnt unpublished:" + err.data.Message); 
      -         *    });
      -         * 
      - * @param {Int} id the ID of the node to unpublish - * @returns {Promise} resourcePromise object. - * - */ - unPublish: function (id) { - if (!id) { - throw "id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostUnPublish", - [{ id: id }])), - 'Failed to publish content with id ' + id); - }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#emptyRecycleBin - * @methodOf umbraco.resources.contentResource - * - * @description - * Empties the content recycle bin - * - * ##usage - *
      -         * contentResource.emptyRecycleBin()
      -         *    .then(function() {
      -         *        alert('its empty!');
      -         *    });
      -         * 
      - * - * @returns {Promise} resourcePromise object. - * - */ - emptyRecycleBin: function() { - return umbRequestHelper.resourcePromise( + /** + * @ngdoc method + * @name umbraco.resources.contentResource#unPublish + * @methodOf umbraco.resources.contentResource + * + * @description + * Unpublishes a content item with a given Id + * + * ##usage + *
      +        * contentResource.unPublish(1234)
      +        *    .then(function() {
      +        *        alert("node was unpulished");
      +        *    }, function(err){
      +        *      alert("node wasnt unpublished:" + err.data.Message); 
      +        *    });
      +        * 
      + * @param {Int} id the ID of the node to unpublish + * @returns {Promise} resourcePromise object. + * + */ + unPublish: function (id) { + if (!id) { + throw "id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostUnPublish", + [{ id: id }])), + 'Failed to publish content with id ' + id); + }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#emptyRecycleBin + * @methodOf umbraco.resources.contentResource + * + * @description + * Empties the content recycle bin + * + * ##usage + *
      +        * contentResource.emptyRecycleBin()
      +        *    .then(function() {
      +        *        alert('its empty!');
      +        *    });
      +        * 
      + * + * @returns {Promise} resourcePromise object. + * + */ + emptyRecycleBin: function () { + return umbRequestHelper.resourcePromise( $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "EmptyRecycleBin")), + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "EmptyRecycleBin")), 'Failed to empty the recycle bin'); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#deleteById - * @methodOf umbraco.resources.contentResource - * - * @description - * Deletes a content item with a given id - * - * ##usage - *
      -         * contentResource.deleteById(1234)
      -         *    .then(function() {
      -         *        alert('its gone!');
      -         *    });
      -         * 
      - * - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteById: function(id) { - return umbRequestHelper.resourcePromise( + /** + * @ngdoc method + * @name umbraco.resources.contentResource#deleteById + * @methodOf umbraco.resources.contentResource + * + * @description + * Deletes a content item with a given id + * + * ##usage + *
      +        * contentResource.deleteById(1234)
      +        *    .then(function() {
      +        *        alert('its gone!');
      +        *    });
      +        * 
      + * + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { + return umbRequestHelper.resourcePromise( $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "DeleteById", - [{ id: id }])), + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "DeleteById", + [{ id: id }])), 'Failed to delete item ' + id); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getById - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets a content item with a given id - * - * ##usage - *
      -         * contentResource.getById(1234)
      -         *    .then(function(content) {
      -         *        var myDoc = content; 
      -         *        alert('its here!');
      -         *    });
      -         * 
      - * - * @param {Int} id id of content item to return - * @returns {Promise} resourcePromise object containing the content item. - * - */ - getById: function (id) { - return umbRequestHelper.resourcePromise( + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getById + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets a content item with a given id + * + * ##usage + *
      +        * contentResource.getById(1234)
      +        *    .then(function(content) {
      +        *        var myDoc = content; 
      +        *        alert('its here!');
      +        *    });
      +        * 
      + * + * @param {Int} id id of content item to return + * @returns {Promise} resourcePromise object containing the content item. + * + */ + getById: function (id) { + return umbRequestHelper.resourcePromise( $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetById", - [{ id: id }])), + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetById", + [{ id: id }])), 'Failed to retrieve data for content id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getByIds - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets an array of content items, given a collection of ids - * - * ##usage - *
      -         * contentResource.getByIds( [1234,2526,28262])
      -         *    .then(function(contentArray) {
      -         *        var myDoc = contentArray; 
      -         *        alert('they are here!');
      -         *    });
      -         * 
      - * - * @param {Array} ids ids of content items to return as an array - * @returns {Promise} resourcePromise object containing the content items array. - * - */ - getByIds: function (ids) { - - var idQuery = ""; - _.each(ids, function(item) { - idQuery += "ids=" + item + "&"; - }); + }, - return umbRequestHelper.resourcePromise( + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getByIds + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets an array of content items, given a collection of ids + * + * ##usage + *
      +        * contentResource.getByIds( [1234,2526,28262])
      +        *    .then(function(contentArray) {
      +        *        var myDoc = contentArray; 
      +        *        alert('they are here!');
      +        *    });
      +        * 
      + * + * @param {Array} ids ids of content items to return as an array + * @returns {Promise} resourcePromise object containing the content items array. + * + */ + getByIds: function (ids) { + + var idQuery = ""; + _.each(ids, function (item) { + idQuery += "ids=" + item + "&"; + }); + + return umbRequestHelper.resourcePromise( $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetByIds", - idQuery)), + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetByIds", + idQuery)), 'Failed to retrieve data for content with multiple ids'); - }, + }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getScaffold - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. - * - * - Parent Id must be provided so umbraco knows where to store the content - * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold - * - * The scaffold is used to build editors for content that has not yet been populated with data. - * - * ##usage - *
      -         * contentResource.getScaffold(1234, 'homepage')
      -         *    .then(function(scaffold) {
      -         *        var myDoc = scaffold;
      -         *        myDoc.name = "My new document"; 
      -         *
      -         *        contentResource.publish(myDoc, true)
      -         *            .then(function(content){
      -         *                alert("Retrieved, updated and published again");
      -         *            });
      -         *    });
      -         * 
      - * - * @param {Int} parentId id of content item to return - * @param {String} alias contenttype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the content scaffold. - * - */ - getScaffold: function (parentId, alias) { - - return umbRequestHelper.resourcePromise( + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getScaffold + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. + * + * - Parent Id must be provided so umbraco knows where to store the content + * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold + * + * The scaffold is used to build editors for content that has not yet been populated with data. + * + * ##usage + *
      +        * contentResource.getScaffold(1234, 'homepage')
      +        *    .then(function(scaffold) {
      +        *        var myDoc = scaffold;
      +        *        myDoc.name = "My new document"; 
      +        *
      +        *        contentResource.publish(myDoc, true)
      +        *            .then(function(content){
      +        *                alert("Retrieved, updated and published again");
      +        *            });
      +        *    });
      +        * 
      + * + * @param {Int} parentId id of content item to return + * @param {String} alias contenttype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the content scaffold. + * + */ + getScaffold: function (parentId, alias) { + + return umbRequestHelper.resourcePromise( $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), 'Failed to retrieve data for empty content item type ' + alias); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getNiceUrl - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a url, given a node ID - * - * ##usage - *
      -         * contentResource.getNiceUrl(id)
      -         *    .then(function(url) {
      -         *        alert('its here!');
      -         *    });
      -         * 
      - * - * @param {Int} id Id of node to return the public url to - * @returns {Promise} resourcePromise object containing the url. - * - */ - getNiceUrl: function (id) { - return umbRequestHelper.resourcePromise( + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getNiceUrl + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a url, given a node ID + * + * ##usage + *
      +        * contentResource.getNiceUrl(id)
      +        *    .then(function(url) {
      +        *        alert('its here!');
      +        *    });
      +        * 
      + * + * @param {Int} id Id of node to return the public url to + * @returns {Promise} resourcePromise object containing the url. + * + */ + getNiceUrl: function (id) { + return umbRequestHelper.resourcePromise( $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetNiceUrl",[{id: id}])), + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetNiceUrl", [{ id: id }])), 'Failed to retrieve url for id:' + id); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getChildren - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets children of a content item with a given id - * - * ##usage - *
      -         * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
      -         *    .then(function(contentArray) {
      -         *        var children = contentArray; 
      -         *        alert('they are here!');
      -         *    });
      -         * 
      - * - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getChildren: function (parentId, options) { + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getChildren + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets children of a content item with a given id + * + * ##usage + *
      +        * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
      +        *    .then(function(contentArray) {
      +        *        var children = contentArray; 
      +        *        alert('they are here!');
      +        *    });
      +        * 
      + * + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getChildren: function (parentId, options) { - var defaults = { - pageSize: 0, - pageNumber: 0, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder" - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } + var defaults = { + pageSize: 0, + pageNumber: 0, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + orderBySystemField: true + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } - return umbRequestHelper.resourcePromise( + return umbRequestHelper.resourcePromise( $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetChildren", - [ - { id: parentId }, - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { filter: options.filter } - ])), + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetChildren", + [ + { id: parentId }, + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: options.orderBySystemField }, + { filter: options.filter } + ])), 'Failed to retrieve children for content item ' + parentId); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#hasPermission - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns true/false given a permission char to check against a nodeID - * for the current user - * - * ##usage - *
      -         * contentResource.hasPermission('p',1234)
      -         *    .then(function() {
      -         *        alert('You are allowed to publish this item');
      -         *    });
      -         * 
      - * - * @param {String} permission char representing the permission to check - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ - checkPermission: function(permission, id) { - return umbRequestHelper.resourcePromise( + /** + * @ngdoc method + * @name umbraco.resources.contentResource#hasPermission + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns true/false given a permission char to check against a nodeID + * for the current user + * + * ##usage + *
      +        * contentResource.hasPermission('p',1234)
      +        *    .then(function() {
      +        *        alert('You are allowed to publish this item');
      +        *    });
      +        * 
      + * + * @param {String} permission char representing the permission to check + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ + checkPermission: function (permission, id) { + return umbRequestHelper.resourcePromise( $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "HasPermission", - [{ permissionToCheck: permission },{ nodeId: id }])), + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "HasPermission", + [{ permissionToCheck: permission }, { nodeId: id }])), 'Failed to check permission for item ' + id); - }, + }, - getPermissions: function (nodeIds) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetPermissions"), - nodeIds), - 'Failed to get permissions'); - }, + getPermissions: function (nodeIds) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetPermissions"), + nodeIds), + 'Failed to get permissions'); + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#save - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
      -         * contentResource.getById(1234)
      -         *    .then(function(content) {
      -         *          content.name = "I want a new name!";
      -         *          contentResource.save(content, false)
      -         *            .then(function(content){
      -         *                alert("Retrieved, updated and saved again");
      -         *            });
      -         *    });
      -         * 
      - * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - save: function (content, isNew, files) { - return saveContentItem(content, "save" + (isNew ? "New" : ""), files); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#save + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
      +        * contentResource.getById(1234)
      +        *    .then(function(content) {
      +        *          content.name = "I want a new name!";
      +        *          contentResource.save(content, false)
      +        *            .then(function(content){
      +        *                alert("Retrieved, updated and saved again");
      +        *            });
      +        *    });
      +        * 
      + * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + save: function (content, isNew, files) { + return saveContentItem(content, "save" + (isNew ? "New" : ""), files); + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#publish - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
      -         * contentResource.getById(1234)
      -         *    .then(function(content) {
      -         *          content.name = "I want a new name, and be published!";
      -         *          contentResource.publish(content, false)
      -         *            .then(function(content){
      -         *                alert("Retrieved, updated and published again");
      -         *            });
      -         *    });
      -         * 
      - * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - publish: function (content, isNew, files) { - return saveContentItem(content, "publish" + (isNew ? "New" : ""), files); - }, - - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#sendToPublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves changes made to a content item, and notifies any subscribers about a pending publication - * - * ##usage - *
      -         * contentResource.getById(1234)
      -         *    .then(function(content) {
      -         *          content.name = "I want a new name, and be published!";
      -         *          contentResource.sendToPublish(content, false)
      -         *            .then(function(content){
      -         *                alert("Retrieved, updated and notication send off");
      -         *            });
      -         *    });
      -         * 
      - * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - sendToPublish: function (content, isNew, files) { - return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#publishByid - * @methodOf umbraco.resources.contentResource - * - * @description - * Publishes a content item with a given ID - * - * ##usage - *
      -         * contentResource.publishById(1234)
      -         *    .then(function(content) {
      -         *        alert("published");
      -         *    });
      -         * 
      - * - * @param {Int} id The ID of the conten to publish - * @returns {Promise} resourcePromise object containing the published content item. - * - */ - publishById: function(id){ - - if (!id) { - throw "id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostPublishById", - [{ id: id }])), - 'Failed to publish content with id ' + id); - - } + /** + * @ngdoc method + * @name umbraco.resources.contentResource#publish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
      +        * contentResource.getById(1234)
      +        *    .then(function(content) {
      +        *          content.name = "I want a new name, and be published!";
      +        *          contentResource.publish(content, false)
      +        *            .then(function(content){
      +        *                alert("Retrieved, updated and published again");
      +        *            });
      +        *    });
      +        * 
      + * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + publish: function (content, isNew, files) { + return saveContentItem(content, "publish" + (isNew ? "New" : ""), files); + }, - }; + /** + * @ngdoc method + * @name umbraco.resources.contentResource#sendToPublish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item, and notifies any subscribers about a pending publication + * + * ##usage + *
      +        * contentResource.getById(1234)
      +        *    .then(function(content) {
      +        *          content.name = "I want a new name, and be published!";
      +        *          contentResource.sendToPublish(content, false)
      +        *            .then(function(content){
      +        *                alert("Retrieved, updated and notication send off");
      +        *            });
      +        *    });
      +        * 
      + * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + sendToPublish: function (content, isNew, files) { + return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#publishByid + * @methodOf umbraco.resources.contentResource + * + * @description + * Publishes a content item with a given ID + * + * ##usage + *
      +        * contentResource.publishById(1234)
      +        *    .then(function(content) {
      +        *        alert("published");
      +        *    });
      +        * 
      + * + * @param {Int} id The ID of the conten to publish + * @returns {Promise} resourcePromise object containing the published content item. + * + */ + publishById: function (id) { + + if (!id) { + throw "id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostPublishById", + [{ id: id }])), + 'Failed to publish content with id ' + id); + + } + + + }; } angular.module('umbraco.resources').factory('contentResource', contentResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index 9a2310299f..a85b2cc104 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -4,470 +4,472 @@ * @description Loads in data for media **/ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { - - /** internal method process the saving of data and post processing the result */ - function saveMediaItem(content, action, files) { - return umbRequestHelper.postSaveContent({ - restApiUrl: umbRequestHelper.getApiUrl( + + /** internal method process the saving of data and post processing the result */ + function saveMediaItem(content, action, files) { + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl( "mediaApiBaseUrl", "PostSave"), - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatMediaPostData(c, a); - } - }); - } + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatMediaPostData(c, a); + } + }); + } - return { - - getRecycleBin: function () { - return umbRequestHelper.resourcePromise( + return { + + getRecycleBin: function () { + return umbRequestHelper.resourcePromise( $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRecycleBin")), + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRecycleBin")), 'Failed to retrieve data for media recycle bin'); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#sort - * @methodOf umbraco.resources.mediaResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
      -         * var ids = [123,34533,2334,23434];
      -         * mediaResource.sort({ sortedIds: ids })
      -         *    .then(function() {
      -         *        $scope.complete = true;
      -         *    });
      -         * 
      - * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ - sort: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.sortedIds) { - throw "args.sortedIds cannot be null"; - } + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#sort + * @methodOf umbraco.resources.mediaResource + * + * @description + * Sorts all children below a given parent node id, based on a collection of node-ids + * + * ##usage + *
      +        * var ids = [123,34533,2334,23434];
      +        * mediaResource.sort({ sortedIds: ids })
      +        *    .then(function() {
      +        *        $scope.complete = true;
      +        *    });
      +        * 
      + * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ + sort: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.sortedIds) { + throw "args.sortedIds cannot be null"; + } - return umbRequestHelper.resourcePromise( + return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), + { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), 'Failed to sort media'); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#move - * @methodOf umbraco.resources.mediaResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
      -         * mediaResource.move({ parentId: 1244, id: 123 })
      -         *    .then(function() {
      -         *        alert("node was moved");
      -         *    }, function(err){
      -         *      alert("node didnt move:" + err.data.Message); 
      -         *    });
      -         * 
      - * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#move + * @methodOf umbraco.resources.mediaResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
      +        * mediaResource.move({ parentId: 1244, id: 123 })
      +        *    .then(function() {
      +        *        alert("node was moved");
      +        *    }, function(err){
      +        *      alert("node didnt move:" + err.data.Message); 
      +        *    });
      +        * 
      + * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } - return umbRequestHelper.resourcePromise( + return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), + { + parentId: args.parentId, + id: args.id + }), 'Failed to move media'); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets a media item with a given id - * - * ##usage - *
      -         * mediaResource.getById(1234)
      -         *    .then(function(media) {
      -         *        var myMedia = media; 
      -         *        alert('its here!');
      -         *    });
      -         * 
      - * - * @param {Int} id id of media item to return - * @returns {Promise} resourcePromise object containing the media item. - * - */ - getById: function (id) { - - return umbRequestHelper.resourcePromise( + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets a media item with a given id + * + * ##usage + *
      +        * mediaResource.getById(1234)
      +        *    .then(function(media) {
      +        *        var myMedia = media; 
      +        *        alert('its here!');
      +        *    });
      +        * 
      + * + * @param {Int} id id of media item to return + * @returns {Promise} resourcePromise object containing the media item. + * + */ + getById: function (id) { + + return umbRequestHelper.resourcePromise( $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetById", - [{ id: id }])), + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetById", + [{ id: id }])), 'Failed to retrieve data for media id ' + id); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#deleteById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Deletes a media item with a given id - * - * ##usage - *
      -         * mediaResource.deleteById(1234)
      -         *    .then(function() {
      -         *        alert('its gone!');
      -         *    });
      -         * 
      - * - * @param {Int} id id of media item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteById: function(id) { - return umbRequestHelper.resourcePromise( + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#deleteById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Deletes a media item with a given id + * + * ##usage + *
      +        * mediaResource.deleteById(1234)
      +        *    .then(function() {
      +        *        alert('its gone!');
      +        *    });
      +        * 
      + * + * @param {Int} id id of media item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { + return umbRequestHelper.resourcePromise( $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "DeleteById", - [{ id: id }])), + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "DeleteById", + [{ id: id }])), 'Failed to delete item ' + id); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getByIds - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets an array of media items, given a collection of ids - * - * ##usage - *
      -         * mediaResource.getByIds( [1234,2526,28262])
      -         *    .then(function(mediaArray) {
      -         *        var myDoc = contentArray; 
      -         *        alert('they are here!');
      -         *    });
      -         * 
      - * - * @param {Array} ids ids of media items to return as an array - * @returns {Promise} resourcePromise object containing the media items array. - * - */ - getByIds: function (ids) { - - var idQuery = ""; - _.each(ids, function(item) { - idQuery += "ids=" + item + "&"; - }); + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getByIds + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets an array of media items, given a collection of ids + * + * ##usage + *
      +        * mediaResource.getByIds( [1234,2526,28262])
      +        *    .then(function(mediaArray) {
      +        *        var myDoc = contentArray; 
      +        *        alert('they are here!');
      +        *    });
      +        * 
      + * + * @param {Array} ids ids of media items to return as an array + * @returns {Promise} resourcePromise object containing the media items array. + * + */ + getByIds: function (ids) { - return umbRequestHelper.resourcePromise( + var idQuery = ""; + _.each(ids, function (item) { + idQuery += "ids=" + item + "&"; + }); + + return umbRequestHelper.resourcePromise( $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetByIds", - idQuery)), + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetByIds", + idQuery)), 'Failed to retrieve data for media ids ' + ids); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getScaffold - * @methodOf umbraco.resources.mediaResource - * - * @description - * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. - * - * - Parent Id must be provided so umbraco knows where to store the media - * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold - * - * The scaffold is used to build editors for media that has not yet been populated with data. - * - * ##usage - *
      -         * mediaResource.getScaffold(1234, 'folder')
      -         *    .then(function(scaffold) {
      -         *        var myDoc = scaffold;
      -         *        myDoc.name = "My new media item"; 
      -         *
      -         *        mediaResource.save(myDoc, true)
      -         *            .then(function(media){
      -         *                alert("Retrieved, updated and saved again");
      -         *            });
      -         *    });
      -         * 
      - * - * @param {Int} parentId id of media item to return - * @param {String} alias mediatype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the media scaffold. - * - */ - getScaffold: function (parentId, alias) { - - return umbRequestHelper.resourcePromise( + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getScaffold + * @methodOf umbraco.resources.mediaResource + * + * @description + * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. + * + * - Parent Id must be provided so umbraco knows where to store the media + * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold + * + * The scaffold is used to build editors for media that has not yet been populated with data. + * + * ##usage + *
      +        * mediaResource.getScaffold(1234, 'folder')
      +        *    .then(function(scaffold) {
      +        *        var myDoc = scaffold;
      +        *        myDoc.name = "My new media item"; 
      +        *
      +        *        mediaResource.save(myDoc, true)
      +        *            .then(function(media){
      +        *                alert("Retrieved, updated and saved again");
      +        *            });
      +        *    });
      +        * 
      + * + * @param {Int} parentId id of media item to return + * @param {String} alias mediatype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the media scaffold. + * + */ + getScaffold: function (parentId, alias) { + + return umbRequestHelper.resourcePromise( $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), 'Failed to retrieve data for empty media item type ' + alias); - }, + }, - rootMedia: function () { - - return umbRequestHelper.resourcePromise( + rootMedia: function () { + + return umbRequestHelper.resourcePromise( $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRootMedia")), + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRootMedia")), 'Failed to retrieve data for root media'); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildren - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets children of a media item with a given id - * - * ##usage - *
      -         * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
      -         *    .then(function(contentArray) {
      -         *        var children = contentArray; 
      -         *        alert('they are here!');
      -         *    });
      -         * 
      - * - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getChildren: function (parentId, options) { + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildren + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets children of a media item with a given id + * + * ##usage + *
      +        * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
      +        *    .then(function(contentArray) {
      +        *        var children = contentArray; 
      +        *        alert('they are here!');
      +        *    });
      +        * 
      + * + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getChildren: function (parentId, options) { - var defaults = { - pageSize: 0, - pageNumber: 0, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder" - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } + var defaults = { + pageSize: 0, + pageNumber: 0, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + orderBySystemField: true + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } - return umbRequestHelper.resourcePromise( + return umbRequestHelper.resourcePromise( $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildren", - [ - { id: parentId }, - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { filter: options.filter } - ])), + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildren", + [ + { id: parentId }, + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: options.orderBySystemField }, + { filter: options.filter } + ])), 'Failed to retrieve children for media item ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#save - * @methodOf umbraco.resources.mediaResource - * - * @description - * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation - * if the media item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
      -         * mediaResource.getById(1234)
      -         *    .then(function(media) {
      -         *          media.name = "I want a new name!";
      -         *          mediaResource.save(media, false)
      -         *            .then(function(media){
      -         *                alert("Retrieved, updated and saved again");
      -         *            });
      -         *    });
      -         * 
      - * - * @param {Object} media The media item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the media item - * @returns {Promise} resourcePromise object containing the saved media item. - * - */ - save: function (media, isNew, files) { - return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#addFolder - * @methodOf umbraco.resources.mediaResource - * - * @description - * Shorthand for adding a media item of the type "Folder" under a given parent ID - * - * ##usage - *
      -         * mediaResource.addFolder("My gallery", 1234)
      -         *    .then(function(folder) {
      -         *        alert('New folder');
      -         *    });
      -         * 
      - * - * @param {string} name Name of the folder to create - * @param {int} parentId Id of the media item to create the folder underneath - * @returns {Promise} resourcePromise object. - * - */ - addFolder: function(name, parentId){ - return umbRequestHelper.resourcePromise( + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#save + * @methodOf umbraco.resources.mediaResource + * + * @description + * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation + * if the media item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
      +        * mediaResource.getById(1234)
      +        *    .then(function(media) {
      +        *          media.name = "I want a new name!";
      +        *          mediaResource.save(media, false)
      +        *            .then(function(media){
      +        *                alert("Retrieved, updated and saved again");
      +        *            });
      +        *    });
      +        * 
      + * + * @param {Object} media The media item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the media item + * @returns {Promise} resourcePromise object containing the saved media item. + * + */ + save: function (media, isNew, files) { + return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#addFolder + * @methodOf umbraco.resources.mediaResource + * + * @description + * Shorthand for adding a media item of the type "Folder" under a given parent ID + * + * ##usage + *
      +        * mediaResource.addFolder("My gallery", 1234)
      +        *    .then(function(folder) {
      +        *        alert('New folder');
      +        *    });
      +        * 
      + * + * @param {string} name Name of the folder to create + * @param {int} parentId Id of the media item to create the folder underneath + * @returns {Promise} resourcePromise object. + * + */ + addFolder: function (name, parentId) { + return umbRequestHelper.resourcePromise( $http.post(umbRequestHelper - .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), - { - name: name, - parentId: parentId - }), + .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), + { + name: name, + parentId: parentId + }), 'Failed to add folder'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildFolders - * @methodOf umbraco.resources.mediaResource - * - * @description - * Retrieves all media children with types used as folders. - * Uses the convention of looking for media items with mediaTypes ending in - * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, - * - * ##usage - *
      -         * mediaResource.getChildFolders(1234)
      -         *    .then(function(data) {
      -         *        alert('folders');
      -         *    });
      -         * 
      - * - * @param {int} parentId Id of the media item to query for child folders - * @returns {Promise} resourcePromise object. - * - */ - getChildFolders: function(parentId){ - if(!parentId){ - parentId = -1; - } - - return umbRequestHelper.resourcePromise( + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildFolders + * @methodOf umbraco.resources.mediaResource + * + * @description + * Retrieves all media children with types used as folders. + * Uses the convention of looking for media items with mediaTypes ending in + * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, + * + * ##usage + *
      +        * mediaResource.getChildFolders(1234)
      +        *    .then(function(data) {
      +        *        alert('folders');
      +        *    });
      +        * 
      + * + * @param {int} parentId Id of the media item to query for child folders + * @returns {Promise} resourcePromise object. + * + */ + getChildFolders: function (parentId) { + if (!parentId) { + parentId = -1; + } + + return umbRequestHelper.resourcePromise( $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildFolders", - [ - { id: parentId } - ])), + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildFolders", + [ + { id: parentId } + ])), 'Failed to retrieve child folders for media item ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#emptyRecycleBin - * @methodOf umbraco.resources.mediaResource - * - * @description - * Empties the media recycle bin - * - * ##usage - *
      -         * mediaResource.emptyRecycleBin()
      -         *    .then(function() {
      -         *        alert('its empty!');
      -         *    });
      -         * 
      - * - * @returns {Promise} resourcePromise object. - * - */ - emptyRecycleBin: function() { - return umbRequestHelper.resourcePromise( + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#emptyRecycleBin + * @methodOf umbraco.resources.mediaResource + * + * @description + * Empties the media recycle bin + * + * ##usage + *
      +        * mediaResource.emptyRecycleBin()
      +        *    .then(function() {
      +        *        alert('its empty!');
      +        *    });
      +        * 
      + * + * @returns {Promise} resourcePromise object. + * + */ + emptyRecycleBin: function () { + return umbRequestHelper.resourcePromise( $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "EmptyRecycleBin")), + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "EmptyRecycleBin")), 'Failed to empty the recycle bin'); - } - }; + } + }; } angular.module('umbraco.resources').factory('mediaResource', mediaResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js index 42db4f6366..3ec9258f98 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js @@ -4,230 +4,232 @@ * @description Loads in data for members **/ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { - - /** internal method process the saving of data and post processing the result */ - function saveMember(content, action, files) { - - return umbRequestHelper.postSaveContent({ - restApiUrl: umbRequestHelper.getApiUrl( + + /** internal method process the saving of data and post processing the result */ + function saveMember(content, action, files) { + + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl( "memberApiBaseUrl", "PostSave"), - content: content, - action: action, - files: files, - dataFormatter: function(c, a) { - return umbDataFormatter.formatMemberPostData(c, a); - } - }); - } + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatMemberPostData(c, a); + } + }); + } - return { - - getPagedResults: function (memberTypeAlias, options) { + return { - if (memberTypeAlias === 'all-members') { - memberTypeAlias = null; - } + getPagedResults: function (memberTypeAlias, options) { - var defaults = { - pageSize: 25, - pageNumber: 1, - filter: '', - orderDirection: "Ascending", - orderBy: "LoginName" - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } + if (memberTypeAlias === 'all-members') { + memberTypeAlias = null; + } - var params = [ + var defaults = { + pageSize: 25, + pageNumber: 1, + filter: '', + orderDirection: "Ascending", + orderBy: "LoginName", + orderBySystemField: true + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + var params = [ { pageNumber: options.pageNumber }, { pageSize: options.pageSize }, { orderBy: options.orderBy }, { orderDirection: options.orderDirection }, + { orderBySystemField: options.orderBySystemField }, { filter: options.filter } - ]; - if (memberTypeAlias != null) { - params.push({ memberTypeAlias: memberTypeAlias }); - } + ]; + if (memberTypeAlias != null) { + params.push({ memberTypeAlias: memberTypeAlias }); + } - return umbRequestHelper.resourcePromise( + return umbRequestHelper.resourcePromise( $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetPagedResults", - params)), + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetPagedResults", + params)), 'Failed to retrieve member paged result'); - }, - - getListNode: function (listName) { + }, - return umbRequestHelper.resourcePromise( + getListNode: function (listName) { + + return umbRequestHelper.resourcePromise( $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetListNodeDisplay", - [{ listName: listName }])), + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetListNodeDisplay", + [{ listName: listName }])), 'Failed to retrieve data for member list ' + listName); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.memberResource#getByKey - * @methodOf umbraco.resources.memberResource - * - * @description - * Gets a member item with a given key - * - * ##usage - *
      -         * memberResource.getByKey("0000-0000-000-00000-000")
      -         *    .then(function(member) {
      -         *        var mymember = member; 
      -         *        alert('its here!');
      -         *    });
      -         * 
      - * - * @param {Guid} key key of member item to return - * @returns {Promise} resourcePromise object containing the member item. - * - */ - getByKey: function (key) { - - return umbRequestHelper.resourcePromise( + /** + * @ngdoc method + * @name umbraco.resources.memberResource#getByKey + * @methodOf umbraco.resources.memberResource + * + * @description + * Gets a member item with a given key + * + * ##usage + *
      +        * memberResource.getByKey("0000-0000-000-00000-000")
      +        *    .then(function(member) {
      +        *        var mymember = member; 
      +        *        alert('its here!');
      +        *    });
      +        * 
      + * + * @param {Guid} key key of member item to return + * @returns {Promise} resourcePromise object containing the member item. + * + */ + getByKey: function (key) { + + return umbRequestHelper.resourcePromise( $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetByKey", - [{ key: key }])), + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetByKey", + [{ key: key }])), 'Failed to retrieve data for member id ' + key); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.memberResource#deleteByKey - * @methodOf umbraco.resources.memberResource - * - * @description - * Deletes a member item with a given key - * - * ##usage - *
      -         * memberResource.deleteByKey("0000-0000-000-00000-000")
      -         *    .then(function() {
      -         *        alert('its gone!');
      -         *    });
      -         * 
      - * - * @param {Guid} key id of member item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteByKey: function (key) { - return umbRequestHelper.resourcePromise( + /** + * @ngdoc method + * @name umbraco.resources.memberResource#deleteByKey + * @methodOf umbraco.resources.memberResource + * + * @description + * Deletes a member item with a given key + * + * ##usage + *
      +        * memberResource.deleteByKey("0000-0000-000-00000-000")
      +        *    .then(function() {
      +        *        alert('its gone!');
      +        *    });
      +        * 
      + * + * @param {Guid} key id of member item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteByKey: function (key) { + return umbRequestHelper.resourcePromise( $http.post( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "DeleteByKey", - [{ key: key }])), + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "DeleteByKey", + [{ key: key }])), 'Failed to delete item ' + key); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.memberResource#getScaffold - * @methodOf umbraco.resources.memberResource - * - * @description - * Returns a scaffold of an empty member item, given the id of the member item to place it underneath and the member type alias. - * - * - Member Type alias must be provided so umbraco knows which properties to put on the member scaffold - * - * The scaffold is used to build editors for member that has not yet been populated with data. - * - * ##usage - *
      -         * memberResource.getScaffold('client')
      -         *    .then(function(scaffold) {
      -         *        var myDoc = scaffold;
      -         *        myDoc.name = "My new member item"; 
      -         *
      -         *        memberResource.save(myDoc, true)
      -         *            .then(function(member){
      -         *                alert("Retrieved, updated and saved again");
      -         *            });
      -         *    });
      -         * 
      - * - * @param {String} alias membertype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the member scaffold. - * - */ - getScaffold: function (alias) { - - if (alias) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }])), - 'Failed to retrieve data for empty member item type ' + alias); - } - else { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetEmpty")), - 'Failed to retrieve data for empty member item type ' + alias); - } + /** + * @ngdoc method + * @name umbraco.resources.memberResource#getScaffold + * @methodOf umbraco.resources.memberResource + * + * @description + * Returns a scaffold of an empty member item, given the id of the member item to place it underneath and the member type alias. + * + * - Member Type alias must be provided so umbraco knows which properties to put on the member scaffold + * + * The scaffold is used to build editors for member that has not yet been populated with data. + * + * ##usage + *
      +        * memberResource.getScaffold('client')
      +        *    .then(function(scaffold) {
      +        *        var myDoc = scaffold;
      +        *        myDoc.name = "My new member item"; 
      +        *
      +        *        memberResource.save(myDoc, true)
      +        *            .then(function(member){
      +        *                alert("Retrieved, updated and saved again");
      +        *            });
      +        *    });
      +        * 
      + * + * @param {String} alias membertype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the member scaffold. + * + */ + getScaffold: function (alias) { - }, - - /** - * @ngdoc method - * @name umbraco.resources.memberResource#save - * @methodOf umbraco.resources.memberResource - * - * @description - * Saves changes made to a member, if the member is new, the isNew paramater must be passed to force creation - * if the member needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
      -         * memberResource.getBykey("23234-sd8djsd-3h8d3j-sdh8d")
      -         *    .then(function(member) {
      -         *          member.name = "Bob";
      -         *          memberResource.save(member, false)
      -         *            .then(function(member){
      -         *                alert("Retrieved, updated and saved again");
      -         *            });
      -         *    });
      -         * 
      - * - * @param {Object} media The member item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the media item - * @returns {Promise} resourcePromise object containing the saved media item. - * - */ - save: function (member, isNew, files) { - return saveMember(member, "save" + (isNew ? "New" : ""), files); - } - }; + if (alias) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }])), + 'Failed to retrieve data for empty member item type ' + alias); + } + else { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetEmpty")), + 'Failed to retrieve data for empty member item type ' + alias); + } + + }, + + /** + * @ngdoc method + * @name umbraco.resources.memberResource#save + * @methodOf umbraco.resources.memberResource + * + * @description + * Saves changes made to a member, if the member is new, the isNew paramater must be passed to force creation + * if the member needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
      +        * memberResource.getBykey("23234-sd8djsd-3h8d3j-sdh8d")
      +        *    .then(function(member) {
      +        *          member.name = "Bob";
      +        *          memberResource.save(member, false)
      +        *            .then(function(member){
      +        *                alert("Retrieved, updated and saved again");
      +        *            });
      +        *    });
      +        * 
      + * + * @param {Object} media The member item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the media item + * @returns {Promise} resourcePromise object containing the saved media item. + * + */ + save: function (member, isNew, files) { + return saveMember(member, "save" + (isNew ? "New" : ""), files); + } + }; } angular.module('umbraco.resources').factory('memberResource', memberResource); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html index fdd7dd23c9..8d9b45e6be 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html @@ -1,76 +1,60 @@ -
      - -
      - - -
      -
      - -
      - -
      - - - - - -
      -
      - - -
      -
      - - -
      - - -
      - -
      - - -
      - -
      - {{item[column.alias]}} -
      - -
      -
      - -
      - - - - There are no items show in the list. - - -
      +
      +
      + +
      +
      +
      + +
      + + +
      +
      + +
      +
      + +
      + + +
      +
      + + +
      +
      + {{item[column.alias]}} +
      +
      +
      +
      + + + There are no items show in the list. + +
      diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js index 1d2d361f28..b396b66564 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js @@ -1,75 +1,76 @@ -(function() { - "use strict"; - - function ListViewListLayoutController($scope, listViewHelper, $location, mediaHelper) { - - var vm = this; - - vm.nodeId = $scope.contentId; - //vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); - //instead of passing in a whitelist, we pass in a blacklist by adding ! to the ext - vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles).replace(/./g, "!."); - vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; - vm.activeDrag = false; - vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; - - vm.selectItem = selectItem; - vm.clickItem = clickItem; - vm.selectAll = selectAll; - vm.isSelectedAll = isSelectedAll; - vm.isSortDirection = isSortDirection; - vm.sort = sort; - vm.dragEnter = dragEnter; - vm.dragLeave = dragLeave; - vm.onFilesQueue = onFilesQueue; - vm.onUploadComplete = onUploadComplete; - - function selectAll($event) { - listViewHelper.selectAllItems($scope.items, $scope.selection, $event); - } - - function isSelectedAll() { - return listViewHelper.isSelectedAll($scope.items, $scope.selection); - } - - function selectItem(selectedItem, $index, $event) { - listViewHelper.selectHandler(selectedItem, $index, $scope.items, $scope.selection, $event); - } - - function clickItem(item) { - $location.path($scope.entityType + '/' + $scope.entityType + '/edit/' + item.id); - } - - function isSortDirection(col, direction) { - return listViewHelper.setSortingDirection(col, direction, $scope.options); - } - - function sort(field, allow) { - if (allow) { - listViewHelper.setSorting(field, allow, $scope.options); - $scope.getContent($scope.contentId); - } - } - - // Dropzone upload functions - function dragEnter(el, event) { - vm.activeDrag = true; - } - - function dragLeave(el, event) { - vm.activeDrag = false; - } - - function onFilesQueue() { - vm.activeDrag = false; - } - - function onUploadComplete() { - $scope.getContent($scope.contentId); - } - - } - - angular.module("umbraco").controller("Umbraco.PropertyEditors.ListView.ListLayoutController", ListViewListLayoutController); - -})(); +(function () { + "use strict"; + + function ListViewListLayoutController($scope, listViewHelper, $location, mediaHelper) { + + var vm = this; + + vm.nodeId = $scope.contentId; + //vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); + //instead of passing in a whitelist, we pass in a blacklist by adding ! to the ext + vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles).replace(/./g, "!."); + vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; + vm.activeDrag = false; + vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; + + vm.selectItem = selectItem; + vm.clickItem = clickItem; + vm.selectAll = selectAll; + vm.isSelectedAll = isSelectedAll; + vm.isSortDirection = isSortDirection; + vm.sort = sort; + vm.dragEnter = dragEnter; + vm.dragLeave = dragLeave; + vm.onFilesQueue = onFilesQueue; + vm.onUploadComplete = onUploadComplete; + + function selectAll($event) { + listViewHelper.selectAllItems($scope.items, $scope.selection, $event); + } + + function isSelectedAll() { + return listViewHelper.isSelectedAll($scope.items, $scope.selection); + } + + function selectItem(selectedItem, $index, $event) { + listViewHelper.selectHandler(selectedItem, $index, $scope.items, $scope.selection, $event); + } + + function clickItem(item) { + $location.path($scope.entityType + '/' +$scope.entityType + '/edit/' +item.id); + } + + function isSortDirection(col, direction) { + return listViewHelper.setSortingDirection(col, direction, $scope.options); + } + + function sort(field, allow, isSystem) { + if (allow) { + $scope.options.orderBySystemField = isSystem; + listViewHelper.setSorting(field, allow, $scope.options); + $scope.getContent($scope.contentId); + } + } + + // Dropzone upload functions + function dragEnter(el, event) { + vm.activeDrag = true; + } + + function dragLeave(el, event) { + vm.activeDrag = false; + } + + function onFilesQueue() { + vm.activeDrag = false; + } + + function onUploadComplete() { + $scope.getContent($scope.contentId); + } + + } + +angular.module("umbraco").controller("Umbraco.PropertyEditors.ListView.ListLayoutController", ListViewListLayoutController); + +}) (); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 59ea03aa91..0d5451274c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -1,382 +1,393 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookieStore, notificationsService, iconHelper, dialogService, editorState, localizationService, $location, appState, $timeout, $q, mediaResource, listViewHelper, userService) { - //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content - // that isn't created yet, if we continue this will use the parent id in the route params which isn't what - // we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove - // the list view tab entirely when it's new. - if ($routeParams.create) { - $scope.isNew = true; - return; - } + //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content + // that isn't created yet, if we continue this will use the parent id in the route params which isn't what + // we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove + // the list view tab entirely when it's new. + if ($routeParams.create) { + $scope.isNew = true; + return; + } - //Now we need to check if this is for media, members or content because that will depend on the resources we use - var contentResource, getContentTypesCallback, getListResultsCallback, deleteItemCallback, getIdCallback, createEditUrlCallback; + //Now we need to check if this is for media, members or content because that will depend on the resources we use + var contentResource, getContentTypesCallback, getListResultsCallback, deleteItemCallback, getIdCallback, createEditUrlCallback; - //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) - if (($scope.model.config.entityType && $scope.model.config.entityType === "member") || (appState.getSectionState("currentSection") === "member")) { - $scope.entityType = "member"; - contentResource = $injector.get('memberResource'); - getContentTypesCallback = $injector.get('memberTypeResource').getTypes; - getListResultsCallback = contentResource.getPagedResults; - deleteItemCallback = contentResource.deleteByKey; - getIdCallback = function(selected) { - var selectedKey = getItemKey(selected.id); - return selectedKey; - }; - createEditUrlCallback = function(item) { - return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.key + "?page=" + $scope.options.pageNumber + "&listName=" + $scope.contentId; - }; - } - else { - //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) - if (($scope.model.config.entityType && $scope.model.config.entityType === "media") || (appState.getSectionState("currentSection") === "media")) { - $scope.entityType = "media"; - contentResource = $injector.get('mediaResource'); - getContentTypesCallback = $injector.get('mediaTypeResource').getAllowedTypes; - } - else { - $scope.entityType = "content"; - contentResource = $injector.get('contentResource'); - getContentTypesCallback = $injector.get('contentTypeResource').getAllowedTypes; - } - getListResultsCallback = contentResource.getChildren; - deleteItemCallback = contentResource.deleteById; - getIdCallback = function(selected) { - return selected.id; - }; - createEditUrlCallback = function(item) { - return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.id + "?page=" + $scope.options.pageNumber; - }; - } + //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) + if (($scope.model.config.entityType && $scope.model.config.entityType === "member") || (appState.getSectionState("currentSection") === "member")) { + $scope.entityType = "member"; + contentResource = $injector.get('memberResource'); + getContentTypesCallback = $injector.get('memberTypeResource').getTypes; + getListResultsCallback = contentResource.getPagedResults; + deleteItemCallback = contentResource.deleteByKey; + getIdCallback = function (selected) { + var selectedKey = getItemKey(selected.id); + return selectedKey; + }; + createEditUrlCallback = function (item) { + return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.key + "?page=" + $scope.options.pageNumber + "&listName=" + $scope.contentId; + }; + } + else { + //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals) + if (($scope.model.config.entityType && $scope.model.config.entityType === "media") || (appState.getSectionState("currentSection") === "media")) { + $scope.entityType = "media"; + contentResource = $injector.get('mediaResource'); + getContentTypesCallback = $injector.get('mediaTypeResource').getAllowedTypes; + } + else { + $scope.entityType = "content"; + contentResource = $injector.get('contentResource'); + getContentTypesCallback = $injector.get('contentTypeResource').getAllowedTypes; + } + getListResultsCallback = contentResource.getChildren; + deleteItemCallback = contentResource.deleteById; + getIdCallback = function (selected) { + return selected.id; + }; + createEditUrlCallback = function (item) { + return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.id + "?page=" + $scope.options.pageNumber; + }; + } - $scope.pagination = []; - $scope.isNew = false; - $scope.actionInProgress = false; - $scope.selection = []; - $scope.folders = []; - $scope.listViewResultSet = { - totalPages: 0, - items: [] - }; - - $scope.currentNodePermissions = {} + $scope.pagination = []; + $scope.isNew = false; + $scope.actionInProgress = false; + $scope.selection = []; + $scope.folders = []; + $scope.listViewResultSet = { + totalPages: 0, + items: [] + }; - //Just ensure we do have an editorState - if (editorState.current) { - //Fetch current node allowed actions for the current user - //This is the current node & not each individual child node in the list - var currentUserPermissions = editorState.current.allowedActions; + $scope.currentNodePermissions = {} - //Create a nicer model rather than the funky & hard to remember permissions strings - $scope.currentNodePermissions = { - "canCopy": _.contains(currentUserPermissions, 'O'), //Magic Char = O - "canCreate": _.contains(currentUserPermissions, 'C'), //Magic Char = C - "canDelete": _.contains(currentUserPermissions, 'D'), //Magic Char = D - "canMove": _.contains(currentUserPermissions, 'M'), //Magic Char = M - "canPublish": _.contains(currentUserPermissions, 'U'), //Magic Char = U - "canUnpublish": _.contains(currentUserPermissions, 'U'), //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish) - }; - } + //Just ensure we do have an editorState + if (editorState.current) { + //Fetch current node allowed actions for the current user + //This is the current node & not each individual child node in the list + var currentUserPermissions = editorState.current.allowedActions; - //when this is null, we don't check permissions - $scope.buttonPermissions = null; + //Create a nicer model rather than the funky & hard to remember permissions strings + $scope.currentNodePermissions = { + "canCopy": _.contains(currentUserPermissions, 'O'), //Magic Char = O + "canCreate": _.contains(currentUserPermissions, 'C'), //Magic Char = C + "canDelete": _.contains(currentUserPermissions, 'D'), //Magic Char = D + "canMove": _.contains(currentUserPermissions, 'M'), //Magic Char = M + "canPublish": _.contains(currentUserPermissions, 'U'), //Magic Char = U + "canUnpublish": _.contains(currentUserPermissions, 'U'), //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish) + }; + } - //When we are dealing with 'content', we need to deal with permissions on child nodes. - // Currently there is no real good way to - if ($scope.entityType === "content") { + //when this is null, we don't check permissions + $scope.buttonPermissions = null; - var idsWithPermissions = null; + //When we are dealing with 'content', we need to deal with permissions on child nodes. + // Currently there is no real good way to + if ($scope.entityType === "content") { - $scope.buttonPermissions = { - canCopy: true, - canCreate: true, - canDelete: true, - canMove: true, - canPublish: true, - canUnpublish: true - }; + var idsWithPermissions = null; - $scope.$watch(function() { - return $scope.selection.length; - }, function(newVal, oldVal) { + $scope.buttonPermissions = { + canCopy: true, + canCreate: true, + canDelete: true, + canMove: true, + canPublish: true, + canUnpublish: true + }; - if ((idsWithPermissions == null && newVal > 0) || (idsWithPermissions != null)) { - - //get all of the selected ids - var ids = _.map($scope.selection, function(i) { - return i.id.toString(); - }); + $scope.$watch(function () { + return $scope.selection.length; + }, function (newVal, oldVal) { - //remove the dictionary items that don't have matching ids - var filtered = {}; - _.each(idsWithPermissions, function (value, key, list) { - if (_.contains(ids, key)) { - filtered[key] = value; - } - }); - idsWithPermissions = filtered; + if ((idsWithPermissions == null && newVal > 0) || (idsWithPermissions != null)) { - //find all ids that we haven't looked up permissions for - var existingIds = _.keys(idsWithPermissions); - var missingLookup = _.map(_.difference(ids, existingIds), function (i) { - return Number(i); - }); - - if (missingLookup.length > 0) { - contentResource.getPermissions(missingLookup).then(function(p) { - $scope.buttonPermissions = listViewHelper.getButtonPermissions(p, idsWithPermissions); - }); - } - else { - $scope.buttonPermissions = listViewHelper.getButtonPermissions({}, idsWithPermissions); - } - } - }); - - } - - $scope.options = { - displayAtTabNumber: $scope.model.config.displayAtTabNumber ? $scope.model.config.displayAtTabNumber : 1, - pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10, - pageNumber: ($routeParams.page && Number($routeParams.page) != NaN && Number($routeParams.page) > 0) ? $routeParams.page : 1, - filter: '', - orderBy: ($scope.model.config.orderBy ? $scope.model.config.orderBy : 'VersionDate').trim(), - orderDirection: $scope.model.config.orderDirection ? $scope.model.config.orderDirection.trim() : "desc", - includeProperties: $scope.model.config.includeProperties ? $scope.model.config.includeProperties : [ - { alias: 'updateDate', header: 'Last edited', isSystem: 1 }, - { alias: 'updater', header: 'Last edited by', isSystem: 1 } - ], - layout: { - layouts: $scope.model.config.layouts, - activeLayout: listViewHelper.getLayout($routeParams.id, $scope.model.config.layouts) - }, - allowBulkPublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkPublish, - allowBulkUnpublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkUnpublish, - allowBulkCopy: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkCopy, - allowBulkMove: $scope.model.config.bulkActionPermissions.allowBulkMove, - allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete - }; - - //update all of the system includeProperties to enable sorting - _.each($scope.options.includeProperties, function(e, i) { - - if (e.isSystem) { - - //NOTE: special case for contentTypeAlias, it's a system property that cannot be sorted - // to do that, we'd need to update the base query for content to include the content type alias column - // which requires another join and would be slower. BUT We are doing this for members so not sure it makes a diff? - if (e.alias != "contentTypeAlias") { - e.allowSorting = true; - } - - //localize the header - var key = getLocalizedKey(e.alias); - localizationService.localize(key).then(function (v) { - e.header = v; + //get all of the selected ids + var ids = _.map($scope.selection, function (i) { + return i.id.toString(); }); - } - }); - $scope.selectLayout = function(selectedLayout) { - $scope.options.layout.activeLayout = listViewHelper.setLayout($routeParams.id, selectedLayout, $scope.model.config.layouts); - }; + //remove the dictionary items that don't have matching ids + var filtered = {}; + _.each(idsWithPermissions, function (value, key, list) { + if (_.contains(ids, key)) { + filtered[key] = value; + } + }); + idsWithPermissions = filtered; - function showNotificationsAndReset(err, reload, successMsg) { + //find all ids that we haven't looked up permissions for + var existingIds = _.keys(idsWithPermissions); + var missingLookup = _.map(_.difference(ids, existingIds), function (i) { + return Number(i); + }); - //check if response is ysod - if(err.status && err.status >= 500) { - - // Open ysod overlay - $scope.ysodOverlay = { - view : "ysod", - error : err, - show : true - }; - } - - $timeout(function() { - $scope.bulkStatus = ""; - $scope.actionInProgress = false; - }, 500); - - if (reload === true) { - $scope.reloadView($scope.contentId); - } - - if (err.data && angular.isArray(err.data.notifications)) { - for (var i = 0; i < err.data.notifications.length; i++) { - notificationsService.showNotification(err.data.notifications[i]); + if (missingLookup.length > 0) { + contentResource.getPermissions(missingLookup).then(function (p) { + $scope.buttonPermissions = listViewHelper.getButtonPermissions(p, idsWithPermissions); + }); } - } - else if (successMsg) { - notificationsService.success("Done", successMsg); - } - } - - $scope.next = function(pageNumber) { - $scope.options.pageNumber = pageNumber; - $scope.reloadView($scope.contentId); - }; - - $scope.goToPage = function(pageNumber) { - $scope.options.pageNumber = pageNumber; - $scope.reloadView($scope.contentId); - }; - - $scope.prev = function(pageNumber) { - $scope.options.pageNumber = pageNumber; - $scope.reloadView($scope.contentId); - }; - - - /*Loads the search results, based on parameters set in prev,next,sort and so on*/ - /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state - with simple values */ - - $scope.reloadView = function(id) { - - $scope.viewLoaded = false; - - listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); - - getListResultsCallback(id, $scope.options).then(function(data) { - - $scope.actionInProgress = false; - $scope.listViewResultSet = data; - - //update all values for display - if ($scope.listViewResultSet.items) { - _.each($scope.listViewResultSet.items, function(e, index) { - setPropertyValues(e); - }); + else { + $scope.buttonPermissions = listViewHelper.getButtonPermissions({}, idsWithPermissions); } + } + }); - if ($scope.entityType === 'media') { + } - mediaResource.getChildFolders($scope.contentId) - .then(function(folders) { - $scope.folders = folders; - $scope.viewLoaded = true; + $scope.options = { + displayAtTabNumber: $scope.model.config.displayAtTabNumber ? $scope.model.config.displayAtTabNumber : 1, + pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10, + pageNumber: ($routeParams.page && Number($routeParams.page) != NaN && Number($routeParams.page) > 0) ? $routeParams.page : 1, + filter: '', + orderBy: ($scope.model.config.orderBy ? $scope.model.config.orderBy : 'VersionDate').trim(), + orderDirection: $scope.model.config.orderDirection ? $scope.model.config.orderDirection.trim() : "desc", + orderBySystemField: true, + includeProperties: $scope.model.config.includeProperties ? $scope.model.config.includeProperties : [ + { alias: 'updateDate', header: 'Last edited', isSystem: 1 }, + { alias: 'updater', header: 'Last edited by', isSystem: 1 } + ], + layout: { + layouts: $scope.model.config.layouts, + activeLayout: listViewHelper.getLayout($routeParams.id, $scope.model.config.layouts) + }, + allowBulkPublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkPublish, + allowBulkUnpublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkUnpublish, + allowBulkCopy: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkCopy, + allowBulkMove: $scope.model.config.bulkActionPermissions.allowBulkMove, + allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete + }; + + //update all of the system includeProperties to enable sorting + _.each($scope.options.includeProperties, function (e, i) { + + + //NOTE: special case for contentTypeAlias, it's a system property that cannot be sorted + // to do that, we'd need to update the base query for content to include the content type alias column + // which requires another join and would be slower. BUT We are doing this for members so not sure it makes a diff? + if (e.alias != "contentTypeAlias") { + e.allowSorting = true; + } + + // Another special case for lasted edited data/update date for media, again this field isn't available on the base table so we can't sort by it + if (e.isSystem && $scope.entityType == "media") { + e.allowSorting = e.alias != 'updateDate'; + } + + // Another special case for members, only fields on the base table (cmsMember) can be used for sorting + if (e.isSystem && $scope.entityType == "member") { + e.allowSorting = e.alias == 'username' || e.alias == 'email'; + } + if (e.isSystem) { + + //localize the header + var key = getLocalizedKey(e.alias); + localizationService.localize(key).then(function (v) { + e.header = v; + }); + } + }); + + $scope.selectLayout = function (selectedLayout) { + $scope.options.layout.activeLayout = listViewHelper.setLayout($routeParams.id, selectedLayout, $scope.model.config.layouts); + }; + + function showNotificationsAndReset(err, reload, successMsg) { + + //check if response is ysod + if (err.status && err.status >= 500) { + + // Open ysod overlay + $scope.ysodOverlay = { + view: "ysod", + error: err, + show: true + }; + } + + $timeout(function () { + $scope.bulkStatus = ""; + $scope.actionInProgress = false; + }, 500); + + if (reload === true) { + $scope.reloadView($scope.contentId); + } + + if (err.data && angular.isArray(err.data.notifications)) { + for (var i = 0; i < err.data.notifications.length; i++) { + notificationsService.showNotification(err.data.notifications[i]); + } + } + else if (successMsg) { + notificationsService.success("Done", successMsg); + } + } + + $scope.next = function (pageNumber) { + $scope.options.pageNumber = pageNumber; + $scope.reloadView($scope.contentId); + }; + + $scope.goToPage = function (pageNumber) { + $scope.options.pageNumber = pageNumber; + $scope.reloadView($scope.contentId); + }; + + $scope.prev = function (pageNumber) { + $scope.options.pageNumber = pageNumber; + $scope.reloadView($scope.contentId); + }; + + + /*Loads the search results, based on parameters set in prev,next,sort and so on*/ + /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state + with simple values */ + + $scope.reloadView = function (id) { + + $scope.viewLoaded = false; + + listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); + + getListResultsCallback(id, $scope.options).then(function (data) { + + $scope.actionInProgress = false; + $scope.listViewResultSet = data; + + //update all values for display + if ($scope.listViewResultSet.items) { + _.each($scope.listViewResultSet.items, function (e, index) { + setPropertyValues(e); + }); + } + + if ($scope.entityType === 'media') { + + mediaResource.getChildFolders($scope.contentId) + .then(function (folders) { + $scope.folders = folders; + $scope.viewLoaded = true; }); - } else { - $scope.viewLoaded = true; - } + } else { + $scope.viewLoaded = true; + } - //NOTE: This might occur if we are requesting a higher page number than what is actually available, for example - // if you have more than one page and you delete all items on the last page. In this case, we need to reset to the last - // available page and then re-load again - if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) { - $scope.options.pageNumber = $scope.listViewResultSet.totalPages; + //NOTE: This might occur if we are requesting a higher page number than what is actually available, for example + // if you have more than one page and you delete all items on the last page. In this case, we need to reset to the last + // available page and then re-load again + if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) { + $scope.options.pageNumber = $scope.listViewResultSet.totalPages; - //reload! - $scope.reloadView(id); - } + //reload! + $scope.reloadView(id); + } - }); - }; + }); + }; - var searchListView = _.debounce(function(){ - $scope.$apply(function() { + var searchListView = _.debounce(function () { + $scope.$apply(function () { + makeSearch(); + }); + }, 500); + + $scope.forceSearch = function (ev) { + //13: enter + switch (ev.keyCode) { + case 13: makeSearch(); - }); - }, 500); + break; + } + }; - $scope.forceSearch = function (ev) { - //13: enter - switch (ev.keyCode) { - case 13: - makeSearch(); - break; - } - }; + $scope.enterSearch = function () { + $scope.viewLoaded = false; + searchListView(); + }; - $scope.enterSearch = function() { - $scope.viewLoaded = false; - searchListView(); - }; + function makeSearch() { + if ($scope.options.filter !== null && $scope.options.filter !== undefined) { + $scope.options.pageNumber = 1; + //$scope.actionInProgress = true; + $scope.reloadView($scope.contentId); + } + } - function makeSearch() { - if ($scope.options.filter !== null && $scope.options.filter !== undefined) { - $scope.options.pageNumber = 1; - //$scope.actionInProgress = true; - $scope.reloadView($scope.contentId); - } - } + $scope.isAnythingSelected = function () { + if ($scope.selection.length === 0) { + return false; + } else { + return true; + } + }; - $scope.isAnythingSelected = function() { - if ($scope.selection.length === 0) { - return false; - } else { - return true; - } - }; - - $scope.selectedItemsCount = function() { + $scope.selectedItemsCount = function () { return $scope.selection.length; - }; + }; - $scope.clearSelection = function() { + $scope.clearSelection = function () { listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection); - }; + }; - $scope.getIcon = function(entry) { - return iconHelper.convertFromLegacyIcon(entry.icon); - }; + $scope.getIcon = function (entry) { + return iconHelper.convertFromLegacyIcon(entry.icon); + }; - function serial(selected, fn, getStatusMsg, index) { - return fn(selected, index).then(function (content) { - index++; - $scope.bulkStatus = getStatusMsg(index, selected.length); - return index < selected.length ? serial(selected, fn, getStatusMsg, index) : content; - }, function (err) { - var reload = index > 0; - showNotificationsAndReset(err, reload); - return err; - }); - } + function serial(selected, fn, getStatusMsg, index) { + return fn(selected, index).then(function (content) { + index++; + $scope.bulkStatus = getStatusMsg(index, selected.length); + return index < selected.length ? serial(selected, fn, getStatusMsg, index) : content; + }, function (err) { + var reload = index > 0; + showNotificationsAndReset(err, reload); + return err; + }); + } - function applySelected(fn, getStatusMsg, getSuccessMsg, confirmMsg) { - var selected = $scope.selection; - if (selected.length === 0) - return; - if (confirmMsg && !confirm(confirmMsg)) - return; + function applySelected(fn, getStatusMsg, getSuccessMsg, confirmMsg) { + var selected = $scope.selection; + if (selected.length === 0) + return; + if (confirmMsg && !confirm(confirmMsg)) + return; - $scope.actionInProgress = true; - $scope.bulkStatus = getStatusMsg(0, selected.length); + $scope.actionInProgress = true; + $scope.bulkStatus = getStatusMsg(0, selected.length); - serial(selected, fn, getStatusMsg, 0).then(function (result) { - // executes once the whole selection has been processed - // in case of an error (caught by serial), result will be the error - if (!(result.data && angular.isArray(result.data.notifications))) - showNotificationsAndReset(result, true, getSuccessMsg(selected.length)); - }); - } + serial(selected, fn, getStatusMsg, 0).then(function (result) { + // executes once the whole selection has been processed + // in case of an error (caught by serial), result will be the error + if (!(result.data && angular.isArray(result.data.notifications))) + showNotificationsAndReset(result, true, getSuccessMsg(selected.length)); + }); + } - $scope.delete = function () { - applySelected( - function (selected, index) { return deleteItemCallback(getIdCallback(selected[index])); }, - function (count, total) { return "Deleted " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Deleted " + total + " item" + (total > 1 ? "s" : ""); }, - "Sure you want to delete?"); - }; + $scope.delete = function () { + applySelected( + function (selected, index) { return deleteItemCallback(getIdCallback(selected[index])); }, + function (count, total) { return "Deleted " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Deleted " + total + " item" + (total > 1 ? "s" : ""); }, + "Sure you want to delete?"); + }; - $scope.publish = function () { - applySelected( - function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); }, - function (count, total) { return "Published " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Published " + total + " item" + (total > 1 ? "s" : ""); }); - }; + $scope.publish = function () { + applySelected( + function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); }, + function (count, total) { return "Published " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Published " + total + " item" + (total > 1 ? "s" : ""); }); + }; - $scope.unpublish = function() { - applySelected( - function (selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); }, - function (count, total) { return "Unpublished " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function (total) { return "Unpublished " + total + " item" + (total > 1 ? "s" : ""); }); - }; + $scope.unpublish = function () { + applySelected( + function (selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); }, + function (count, total) { return "Unpublished " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Unpublished " + total + " item" + (total > 1 ? "s" : ""); }); + }; - $scope.move = function() { + $scope.move = function () { $scope.moveDialog = {}; $scope.moveDialog.title = "Move"; $scope.moveDialog.section = $scope.entityType; @@ -384,9 +395,9 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie $scope.moveDialog.view = "move"; $scope.moveDialog.show = true; - $scope.moveDialog.submit = function(model) { + $scope.moveDialog.submit = function (model) { - if(model.target) { + if (model.target) { performMove(model.target); } @@ -394,22 +405,22 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie $scope.moveDialog = null; }; - $scope.moveDialog.close = function(oldModel) { + $scope.moveDialog.close = function (oldModel) { $scope.moveDialog.show = false; $scope.moveDialog = null; }; - }; + }; - function performMove(target) { + function performMove(target) { applySelected( - function(selected, index) {return contentResource.move({parentId: target.id, id: getIdCallback(selected[index])}); }, - function(count, total) {return "Moved " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function(total) {return "Moved " + total + " item" + (total > 1 ? "s" : ""); }); - } + function (selected, index) { return contentResource.move({ parentId: target.id, id: getIdCallback(selected[index]) }); }, + function (count, total) { return "Moved " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Moved " + total + " item" + (total > 1 ? "s" : ""); }); + } - $scope.copy = function() { + $scope.copy = function () { $scope.copyDialog = {}; $scope.copyDialog.title = "Copy"; $scope.copyDialog.section = $scope.entityType; @@ -417,8 +428,8 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie $scope.copyDialog.view = "copy"; $scope.copyDialog.show = true; - $scope.copyDialog.submit = function(model) { - if(model.target) { + $scope.copyDialog.submit = function (model) { + if (model.target) { performCopy(model.target, model.relateToOriginal); } @@ -426,142 +437,142 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie $scope.copyDialog = null; }; - $scope.copyDialog.close = function(oldModel) { + $scope.copyDialog.close = function (oldModel) { $scope.copyDialog.show = false; $scope.copyDialog = null; }; - }; + }; - function performCopy(target, relateToOriginal) { + function performCopy(target, relateToOriginal) { applySelected( - function(selected, index) {return contentResource.copy({parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal}); }, - function(count, total) {return "Copied " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, - function(total) {return "Copied " + total + " item" + (total > 1 ? "s" : ""); }); - } + function (selected, index) { return contentResource.copy({ parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal }); }, + function (count, total) { return "Copied " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Copied " + total + " item" + (total > 1 ? "s" : ""); }); + } - function getCustomPropertyValue(alias, properties) { - var value = ''; - var index = 0; - var foundAlias = false; - for (var i = 0; i < properties.length; i++) { - if (properties[i].alias == alias) { - foundAlias = true; - break; - } - index++; - } + function getCustomPropertyValue(alias, properties) { + var value = ''; + var index = 0; + var foundAlias = false; + for (var i = 0; i < properties.length; i++) { + if (properties[i].alias == alias) { + foundAlias = true; + break; + } + index++; + } - if (foundAlias) { - value = properties[index].value; - } + if (foundAlias) { + value = properties[index].value; + } - return value; - } + return value; + } - /** This ensures that the correct value is set for each item in a row, we don't want to call a function during interpolation or ng-bind as performance is really bad that way */ - function setPropertyValues(result) { + /** This ensures that the correct value is set for each item in a row, we don't want to call a function during interpolation or ng-bind as performance is really bad that way */ + function setPropertyValues(result) { - //set the edit url - result.editPath = createEditUrlCallback(result); + //set the edit url + result.editPath = createEditUrlCallback(result); - _.each($scope.options.includeProperties, function (e, i) { + _.each($scope.options.includeProperties, function (e, i) { - var alias = e.alias; + var alias = e.alias; - // First try to pull the value directly from the alias (e.g. updatedBy) - var value = result[alias]; + // First try to pull the value directly from the alias (e.g. updatedBy) + var value = result[alias]; - // If this returns an object, look for the name property of that (e.g. owner.name) - if (value === Object(value)) { - value = value['name']; - } + // If this returns an object, look for the name property of that (e.g. owner.name) + if (value === Object(value)) { + value = value['name']; + } - // If we've got nothing yet, look at a user defined property - if (typeof value === 'undefined') { - value = getCustomPropertyValue(alias, result.properties); - } + // If we've got nothing yet, look at a user defined property + if (typeof value === 'undefined') { + value = getCustomPropertyValue(alias, result.properties); + } - // If we have a date, format it - if (isDate(value)) { - value = value.substring(0, value.length - 3); - } + // If we have a date, format it + if (isDate(value)) { + value = value.substring(0, value.length - 3); + } - // set what we've got on the result - result[alias] = value; - }); + // set what we've got on the result + result[alias] = value; + }); - } + } - function isDate(val) { - if (angular.isString(val)) { - return val.match(/^(\d{4})\-(\d{2})\-(\d{2})\ (\d{2})\:(\d{2})\:(\d{2})$/); - } - return false; - } + function isDate(val) { + if (angular.isString(val)) { + return val.match(/^(\d{4})\-(\d{2})\-(\d{2})\ (\d{2})\:(\d{2})\:(\d{2})$/); + } + return false; + } - function initView() { - //default to root id if the id is undefined - var id = $routeParams.id; - if(id === undefined){ - id = -1; - } + function initView() { + //default to root id if the id is undefined + var id = $routeParams.id; + if (id === undefined) { + id = -1; + } - $scope.listViewAllowedTypes = getContentTypesCallback(id); + $scope.listViewAllowedTypes = getContentTypesCallback(id); - $scope.contentId = id; - $scope.isTrashed = id === "-20" || id === "-21"; + $scope.contentId = id; + $scope.isTrashed = id === "-20" || id === "-21"; - $scope.options.allowBulkPublish = $scope.options.allowBulkPublish && !$scope.isTrashed; - $scope.options.allowBulkUnpublish = $scope.options.allowBulkUnpublish && !$scope.isTrashed; + $scope.options.allowBulkPublish = $scope.options.allowBulkPublish && !$scope.isTrashed; + $scope.options.allowBulkUnpublish = $scope.options.allowBulkUnpublish && !$scope.isTrashed; - $scope.options.bulkActionsAllowed = $scope.options.allowBulkPublish || - $scope.options.allowBulkUnpublish || - $scope.options.allowBulkCopy || - $scope.options.allowBulkMove || - $scope.options.allowBulkDelete; + $scope.options.bulkActionsAllowed = $scope.options.allowBulkPublish || + $scope.options.allowBulkUnpublish || + $scope.options.allowBulkCopy || + $scope.options.allowBulkMove || + $scope.options.allowBulkDelete; - $scope.reloadView($scope.contentId); - } + $scope.reloadView($scope.contentId); + } - function getLocalizedKey(alias) { + function getLocalizedKey(alias) { - switch (alias) { - case "sortOrder": - return "general_sort"; - case "updateDate": - return "content_updateDate"; - case "updater": - return "content_updatedBy"; - case "createDate": - return "content_createDate"; - case "owner": - return "content_createBy"; - case "published": - return "content_isPublished"; - case "contentTypeAlias": - //TODO: Check for members - return $scope.entityType === "content" ? "content_documentType" : "content_mediatype"; - case "email": - return "general_email"; - case "username": - return "general_username"; - } - return alias; - } + switch (alias) { + case "sortOrder": + return "general_sort"; + case "updateDate": + return "content_updateDate"; + case "updater": + return "content_updatedBy"; + case "createDate": + return "content_createDate"; + case "owner": + return "content_createBy"; + case "published": + return "content_isPublished"; + case "contentTypeAlias": + //TODO: Check for members + return $scope.entityType === "content" ? "content_documentType" : "content_mediatype"; + case "email": + return "general_email"; + case "username": + return "general_username"; + } + return alias; + } - function getItemKey(itemId) { - for (var i = 0; i < $scope.listViewResultSet.items.length; i++) { - var item = $scope.listViewResultSet.items[i]; - if (item.id === itemId) { - return item.key; - } - } - } + function getItemKey(itemId) { + for (var i = 0; i < $scope.listViewResultSet.items.length; i++) { + var item = $scope.listViewResultSet.items[i]; + if (item.id === itemId) { + return item.key; + } + } + } - //GO! - initView(); + //GO! + initView(); } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index a65821cfa0..580e1a018c 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -48,13 +48,13 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] [UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content)] public class ContentController : ContentControllerBase - { + { /// /// Constructor /// public ContentController() - : this(UmbracoContext.Current) - { + : this(UmbracoContext.Current) + { } /// @@ -62,8 +62,8 @@ namespace Umbraco.Web.Editors /// /// public ContentController(UmbracoContext umbracoContext) - : base(umbracoContext) - { + : base(umbracoContext) + { } /// @@ -110,15 +110,15 @@ namespace Umbraco.Web.Editors [EnsureUserPermissionForContent("id")] public ContentItemDisplay GetById(int id) { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); if (foundContent == null) { HandleContentNotFound(id); } - + var content = Mapper.Map(foundContent); return content; - } + } [EnsureUserPermissionForContent("id")] public ContentItemDisplay GetWithTreeDefinition(int id) @@ -156,7 +156,7 @@ namespace Umbraco.Web.Editors //remove this tab if it exists: umbContainerView var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); - mapped.Tabs = mapped.Tabs.Except(new[] {containerTab}); + mapped.Tabs = mapped.Tabs.Except(new[] { containerTab }); return mapped; } @@ -169,7 +169,7 @@ namespace Umbraco.Web.Editors { var url = Umbraco.NiceUrl(id); var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(url, Encoding.UTF8, "application/json"); + response.Content = new StringContent(url, Encoding.UTF8, "application/json"); return response; } @@ -179,18 +179,22 @@ namespace Umbraco.Web.Editors /// [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren( - int id, - int pageNumber = 0, //TODO: This should be '1' as it's not the index - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - string filter = "") + int id, + int pageNumber = 0, //TODO: This should be '1' as it's not the index + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + int? orderBySystemField = 2, + string filter = "") { + var orderBySystemFieldBool = orderBySystemField == 1 || orderBySystemField == 2; long totalChildren; IContent[] children; if (pageNumber > 0 && pageSize > 0) { - children = Services.ContentService.GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren, orderBy, orderDirection, filter).ToArray(); + children = Services.ContentService + .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren + , orderBy, orderDirection, orderBySystemFieldBool, filter).ToArray(); } else { @@ -205,7 +209,7 @@ namespace Umbraco.Web.Editors var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); pagedResult.Items = children - .Select(Mapper.Map>); + .Select(Mapper.Map>); return pagedResult; } @@ -213,7 +217,7 @@ namespace Umbraco.Web.Editors [Obsolete("Dont use this, it is incorrectly named, use HasPermission instead")] public bool GetHasPermission(string permissionToCheck, int nodeId) { - return HasPermission(permissionToCheck, nodeId); + return HasPermission(permissionToCheck, nodeId); } /// @@ -225,8 +229,8 @@ namespace Umbraco.Web.Editors public Dictionary GetPermissions(int[] nodeIds) { return Services.UserService - .GetPermissions(Security.CurrentUser, nodeIds) - .ToDictionary(x => x.EntityId, x => x.AssignedPermissions); + .GetPermissions(Security.CurrentUser, nodeIds) + .ToDictionary(x => x.EntityId, x => x.AssignedPermissions); } [HttpGet] @@ -240,7 +244,7 @@ namespace Umbraco.Web.Editors return false; } - + /// /// Saves content /// @@ -248,16 +252,16 @@ namespace Umbraco.Web.Editors [FileUploadCleanupFilter] [ContentPostValidate] public ContentItemDisplay PostSave( - [ModelBinder(typeof(ContentItemBinder))] - ContentItemSave contentItem) - { + [ModelBinder(typeof(ContentItemBinder))] + ContentItemSave contentItem) + { //If we've reached here it means: // * Our model has been bound // * and validated // * any file attachments have been saved to their temporary location for us to use // * we have a reference to the DTO object and the persisted object // * Permissions are valid - + MapPropertyValues(contentItem); //We need to manually check the validation results here because: @@ -275,7 +279,7 @@ namespace Umbraco.Web.Editors var forDisplay = Mapper.Map(contentItem.PersistedContent); forDisplay.Errors = ModelState.ToErrorDictionary(); throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); - + } //if the model state is not valid we cannot publish so change it to save @@ -292,7 +296,7 @@ namespace Umbraco.Web.Editors //initialize this to successful var publishStatus = Attempt.Succeed(); - var wasCancelled = false; + var wasCancelled = false; if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew) { @@ -327,8 +331,8 @@ namespace Umbraco.Web.Editors if (wasCancelled == false) { display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentSavedHeader"), - Services.TextService.Localize("speechBubbles/editContentSavedText")); + Services.TextService.Localize("speechBubbles/editContentSavedHeader"), + Services.TextService.Localize("speechBubbles/editContentSavedText")); } else { @@ -340,8 +344,8 @@ namespace Umbraco.Web.Editors if (wasCancelled == false) { display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentSendToPublish"), - Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); + Services.TextService.Localize("speechBubbles/editContentSendToPublish"), + Services.TextService.Localize("speechBubbles/editContentSendToPublishText")); } else { @@ -366,7 +370,7 @@ namespace Umbraco.Web.Editors return display; } - + /// /// Publishes a document with a given ID /// @@ -392,7 +396,7 @@ namespace Umbraco.Web.Editors { var notificationModel = new SimpleNotificationModel(); ShowMessageForPublishStatus(publishResult.Result, notificationModel); - return Request.CreateValidationErrorResponse(notificationModel); + return Request.CreateValidationErrorResponse(notificationModel); } //return ok @@ -440,7 +444,7 @@ namespace Umbraco.Web.Editors //returning an object of INotificationModel will ensure that any pending // notification messages are added to the response. return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } + } } return Request.CreateResponse(HttpStatusCode.OK); @@ -457,7 +461,7 @@ namespace Umbraco.Web.Editors [HttpPost] [EnsureUserPermissionForContent(Constants.System.RecycleBinContent)] public HttpResponseMessage EmptyRecycleBin() - { + { Services.ContentService.EmptyRecycleBin(); return Request.CreateResponse(HttpStatusCode.OK); } @@ -516,7 +520,7 @@ namespace Umbraco.Web.Editors var response = Request.CreateResponse(HttpStatusCode.OK); response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); - return response; + return response; } /// @@ -548,7 +552,7 @@ namespace Umbraco.Web.Editors if (foundContent == null) HandleContentNotFound(id); - + var unpublishResult = Services.ContentService.WithResult().UnPublish(foundContent, Security.CurrentUser.Id); var content = Mapper.Map(foundContent); @@ -559,7 +563,7 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(Request.CreateValidationErrorResponse(content)); } else - { + { content.AddSuccessNotification(Services.TextService.Localize("content/unPublish"), Services.TextService.Localize("speechBubbles/contentUnpublished")); return content; } @@ -597,8 +601,8 @@ namespace Umbraco.Web.Editors contentItem.PersistedContent.ReleaseDate = contentItem.ReleaseDate; //only set the template if it didn't change var templateChanged = (contentItem.PersistedContent.Template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false) - || (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias) - || (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace()); + || (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias) + || (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace()); if (templateChanged) { var template = Services.FileService.GetTemplate(contentItem.TemplateAlias); @@ -641,8 +645,8 @@ namespace Umbraco.Web.Editors if (toMove.ContentType.AllowedAsRoot == false) { throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"))); + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedAtRoot"))); } } else @@ -655,19 +659,19 @@ namespace Umbraco.Web.Editors //check if the item is allowed under this one if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray() - .Any(x => x.Value == toMove.ContentType.Id) == false) + .Any(x => x.Value == toMove.ContentType.Id) == false) { throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("moveOrCopy/notAllowedByContentType"))); + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedByContentType"))); } // Check on paths if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) - { + { throw new HttpResponseException( - Request.CreateNotificationValidationErrorResponse( - Services.TextService.Localize("moveOrCopy/notAllowedByPath"))); + Request.CreateNotificationValidationErrorResponse( + Services.TextService.Localize("moveOrCopy/notAllowedByPath"))); } } @@ -681,52 +685,52 @@ namespace Umbraco.Web.Editors case PublishStatusType.Success: case PublishStatusType.SuccessAlreadyPublished: display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), - Services.TextService.Localize("speechBubbles/editContentPublishedText")); + Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), + Services.TextService.Localize("speechBubbles/editContentPublishedText")); break; case PublishStatusType.FailedPathNotPublished: display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedByParent", - new[] {string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id)}).Trim()); + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedByParent", + new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); break; case PublishStatusType.FailedCancelledByEvent: AddCancelMessage(display, "publish", "speechBubbles/contentPublishedFailedByEvent"); - break; + break; case PublishStatusType.FailedAwaitingRelease: display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease", - new[] {string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id)}).Trim()); + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease", + new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim()); break; case PublishStatusType.FailedHasExpired: display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedExpired", - new[] - { - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), - }).Trim()); + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedExpired", + new[] + { + string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), + }).Trim()); break; case PublishStatusType.FailedIsTrashed: //TODO: We should add proper error messaging for this! break; case PublishStatusType.FailedContentInvalid: display.AddWarningNotification( - Services.TextService.Localize("publish"), - Services.TextService.Localize("publish/contentPublishedFailedInvalid", - new[] - { - string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), - string.Join(",", status.InvalidProperties.Select(x => x.Alias)) - }).Trim()); + Services.TextService.Localize("publish"), + Services.TextService.Localize("publish/contentPublishedFailedInvalid", + new[] + { + string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id), + string.Join(",", status.InvalidProperties.Select(x => x.Alias)) + }).Trim()); break; default: throw new IndexOutOfRangeException(); } } - + /// /// Performs a permissions check for the user to check if it has access to the node based on @@ -741,15 +745,15 @@ namespace Umbraco.Web.Editors /// Specifies the already resolved content item to check against /// internal static bool CheckPermissions( - IDictionary storage, - IUser user, - IUserService userService, - IContentService contentService, - int nodeId, - char[] permissionsToCheck = null, - IContent contentItem = null) + IDictionary storage, + IUser user, + IUserService userService, + IContentService contentService, + int nodeId, + char[] permissionsToCheck = null, + IContent contentItem = null) { - + if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent) { contentItem = contentService.GetById(nodeId); @@ -764,16 +768,16 @@ namespace Umbraco.Web.Editors } var hasPathAccess = (nodeId == Constants.System.Root) - ? UserExtensions.HasPathAccess( - Constants.System.Root.ToInvariantString(), - user.StartContentId, - Constants.System.RecycleBinContent) - : (nodeId == Constants.System.RecycleBinContent) - ? UserExtensions.HasPathAccess( - Constants.System.RecycleBinContent.ToInvariantString(), - user.StartContentId, - Constants.System.RecycleBinContent) - : user.HasPathAccess(contentItem); + ? UserExtensions.HasPathAccess( + Constants.System.Root.ToInvariantString(), + user.StartContentId, + Constants.System.RecycleBinContent) + : (nodeId == Constants.System.RecycleBinContent) + ? UserExtensions.HasPathAccess( + Constants.System.RecycleBinContent.ToInvariantString(), + user.StartContentId, + Constants.System.RecycleBinContent) + : user.HasPathAccess(contentItem); if (hasPathAccess == false) { diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 66889e2206..060fb06750 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -52,7 +52,7 @@ namespace Umbraco.Web.Editors /// public MediaController() : this(UmbracoContext.Current) - { + { } /// @@ -156,7 +156,7 @@ namespace Umbraco.Web.Editors var folderTypes = Services.ContentTypeService.GetAllMediaTypes().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>); + return children.Where(x => folderTypes.Contains(x.ContentTypeId)).Select(Mapper.Map>); } /// @@ -180,13 +180,16 @@ namespace Umbraco.Web.Editors int pageSize = 0, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, string filter = "") { int totalChildren; IMedia[] children; if (pageNumber > 0 && pageSize > 0) { - children = Services.MediaService.GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren, orderBy, orderDirection, filter).ToArray(); + children = Services.MediaService + .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren + , orderBy, orderDirection, orderBySystemField, filter).ToArray(); } else { @@ -261,7 +264,7 @@ namespace Umbraco.Web.Editors var response = Request.CreateResponse(HttpStatusCode.OK); response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); - return response; + return response; } /// @@ -307,7 +310,7 @@ namespace Umbraco.Web.Editors //return the updated model var display = Mapper.Map(contentItem.PersistedContent); - + //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 HandleInvalidModelState(display); @@ -334,8 +337,8 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(Request.CreateValidationErrorResponse(display)); } } - - break; + + break; } return display; @@ -364,7 +367,7 @@ namespace Umbraco.Web.Editors Services.MediaService.EmptyRecycleBin(); return Request.CreateResponse(HttpStatusCode.OK); } - + /// /// Change the sort order for media /// @@ -383,7 +386,7 @@ namespace Umbraco.Web.Editors { return Request.CreateResponse(HttpStatusCode.OK); } - + var mediaService = base.ApplicationContext.Services.MediaService; var sortedMedia = new List(); try @@ -405,7 +408,7 @@ namespace Umbraco.Web.Editors } } - [EnsureUserPermissionForMedia("folder.ParentId")] + [EnsureUserPermissionForMedia("folder.ParentId")] public MediaItemDisplay PostAddFolder(EntityBasic folder) { var mediaService = ApplicationContext.Services.MediaService; @@ -436,7 +439,7 @@ namespace Umbraco.Web.Editors var provider = new MultipartFormDataStreamProvider(root); var result = await Request.Content.ReadAsMultipartAsync(provider); - + //must have a file if (result.FileData.Count == 0) { @@ -449,10 +452,10 @@ namespace Umbraco.Web.Editors { return Request.CreateValidationErrorResponse("The request was not formatted correctly, the currentFolder is not an integer"); } - + //ensure the user has access to this folder by parent id! if (CheckPermissions( - new Dictionary(), + new Dictionary(), Security.CurrentUser, Services.MediaService, parentId) == false) { @@ -463,65 +466,65 @@ namespace Umbraco.Web.Editors Services.TextService.Localize("speechBubbles/invalidUserPermissionsText"), SpeechBubbleIcon.Warning))); } - + var tempFiles = new PostedFiles(); var mediaService = ApplicationContext.Services.MediaService; //in case we pass a path with a folder in it, we will create it and upload media to it. - if (result.FormData.ContainsKey("path")) - { + if (result.FormData.ContainsKey("path")) + { - var folders = result.FormData["path"].Split('/'); + var folders = result.FormData["path"].Split('/'); - for (int i = 0; i < folders.Length - 1; i++) - { - var folderName = folders[i]; - IMedia folderMediaItem; + for (int i = 0; i < folders.Length - 1; i++) + { + var folderName = folders[i]; + IMedia folderMediaItem; - //if uploading directly to media root and not a subfolder - if (parentId == -1) - { - //look for matching folder - folderMediaItem = - mediaService.GetRootMedia().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); - if (folderMediaItem == null) - { - //if null, create a folder - folderMediaItem = mediaService.CreateMedia(folderName, -1, Constants.Conventions.MediaTypes.Folder); - mediaService.Save(folderMediaItem); - } - } - else - { - //get current parent - var mediaRoot = mediaService.GetById(parentId); + //if uploading directly to media root and not a subfolder + if (parentId == -1) + { + //look for matching folder + folderMediaItem = + mediaService.GetRootMedia().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); + if (folderMediaItem == null) + { + //if null, create a folder + folderMediaItem = mediaService.CreateMedia(folderName, -1, Constants.Conventions.MediaTypes.Folder); + mediaService.Save(folderMediaItem); + } + } + else + { + //get current parent + var mediaRoot = mediaService.GetById(parentId); - //if the media root is null, something went wrong, we'll abort - if (mediaRoot == null) - return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, - "The folder: " + folderName + " could not be used for storing images, its ID: " + parentId + - " returned null"); + //if the media root is null, something went wrong, we'll abort + if (mediaRoot == null) + return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, + "The folder: " + folderName + " could not be used for storing images, its ID: " + parentId + + " returned null"); - //look for matching folder - folderMediaItem = mediaRoot.Children().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); - if (folderMediaItem == null) - { - //if null, create a folder - folderMediaItem = mediaService.CreateMedia(folderName, mediaRoot, Constants.Conventions.MediaTypes.Folder); - mediaService.Save(folderMediaItem); - } - } - //set the media root to the folder id so uploaded files will end there. - parentId = folderMediaItem.Id; - } - } + //look for matching folder + folderMediaItem = mediaRoot.Children().FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); + if (folderMediaItem == null) + { + //if null, create a folder + folderMediaItem = mediaService.CreateMedia(folderName, mediaRoot, Constants.Conventions.MediaTypes.Folder); + mediaService.Save(folderMediaItem); + } + } + //set the media root to the folder id so uploaded files will end there. + parentId = folderMediaItem.Id; + } + } - //get the files + //get the files foreach (var file in result.FileData) { var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }); - var ext = fileName.Substring(fileName.LastIndexOf('.')+1).ToLower(); + var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower(); if (UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext) == false) { @@ -583,7 +586,7 @@ namespace Umbraco.Web.Editors if (origin.Value == "blueimp") { return Request.CreateResponse(HttpStatusCode.OK, - tempFiles, + tempFiles, //Don't output the angular xsrf stuff, blue imp doesn't like that new JsonMediaTypeFormatter()); } diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 909189b7ad..2a3bb4399b 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -73,15 +73,16 @@ namespace Umbraco.Web.Editors get { return Services.MemberService.GetMembershipScenario(); } } - public PagedResult GetPagedResults( + public PagedResult GetPagedResults( int pageNumber = 1, int pageSize = 100, string orderBy = "Name", Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, string filter = "", string memberTypeAlias = null) { - + if (pageNumber <= 0 || pageSize <= 0) { throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero"); @@ -90,7 +91,9 @@ namespace Umbraco.Web.Editors if (MembershipScenario == MembershipScenario.NativeUmbraco) { long totalRecords; - var members = Services.MemberService.GetAll((pageNumber - 1), pageSize, out totalRecords, orderBy, orderDirection, memberTypeAlias, filter).ToArray(); + var members = Services.MemberService + .GetAll((pageNumber - 1), pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField + , memberTypeAlias, filter).ToArray(); if (totalRecords == 0) { return new PagedResult(0, 0, 0); @@ -114,7 +117,7 @@ namespace Umbraco.Web.Editors .Select(Mapper.Map); return pagedResult; } - + } /// @@ -162,23 +165,23 @@ namespace Umbraco.Web.Editors return Mapper.Map(foundMember); case MembershipScenario.CustomProviderWithUmbracoLink: - //TODO: Support editing custom properties for members with a custom membership provider here. + //TODO: Support editing custom properties for members with a custom membership provider here. - //foundMember = Services.MemberService.GetByKey(key); - //if (foundMember == null) - //{ - // HandleContentNotFound(key); - //} - //foundMembershipMember = Membership.GetUser(key, false); - //if (foundMembershipMember == null) - //{ - // HandleContentNotFound(key); - //} + //foundMember = Services.MemberService.GetByKey(key); + //if (foundMember == null) + //{ + // HandleContentNotFound(key); + //} + //foundMembershipMember = Membership.GetUser(key, false); + //if (foundMembershipMember == null) + //{ + // HandleContentNotFound(key); + //} - //display = Mapper.Map(foundMembershipMember); - ////map the name over - //display.Name = foundMember.Name; - //return display; + //display = Mapper.Map(foundMembershipMember); + ////map the name over + //display.Name = foundMember.Name; + //return display; case MembershipScenario.StandaloneCustomProvider: default: @@ -219,7 +222,7 @@ namespace Umbraco.Web.Editors emptyContent.AdditionalData["NewPassword"] = Membership.GeneratePassword(Membership.MinRequiredPasswordLength, Membership.MinRequiredNonAlphanumericCharacters); return Mapper.Map(emptyContent); case MembershipScenario.CustomProviderWithUmbracoLink: - //TODO: Support editing custom properties for members with a custom membership provider here. + //TODO: Support editing custom properties for members with a custom membership provider here. case MembershipScenario.StandaloneCustomProvider: default: @@ -275,7 +278,7 @@ namespace Umbraco.Web.Editors { throw new NotSupportedException("Currently the member editor does not support providers that have RequiresQuestionAndAnswer specified"); } - + //We're gonna look up the current roles now because the below code can cause // events to be raised and developers could be manually adding roles to members in // their handlers. If we don't look this up now there's a chance we'll just end up @@ -324,7 +327,7 @@ namespace Umbraco.Web.Editors //create/save the IMember Services.MemberService.Save(contentItem.PersistedContent); } - + //Now let's do the role provider stuff - now that we've saved the content item (that is important since // if we are changing the username, it must be persisted before looking up the member roles). if (rolesToRemove.Any()) @@ -504,7 +507,7 @@ namespace Umbraco.Web.Editors private void RefetchMemberData(MemberSave contentItem, LookupType lookup) { var currProps = contentItem.PersistedContent.Properties.ToArray(); - + switch (MembershipScenario) { case MembershipScenario.NativeUmbraco: @@ -512,11 +515,11 @@ namespace Umbraco.Web.Editors { case LookupType.ByKey: //Go and re-fetch the persisted item - contentItem.PersistedContent = Services.MemberService.GetByKey(contentItem.Key); + contentItem.PersistedContent = Services.MemberService.GetByKey(contentItem.Key); break; case LookupType.ByUserName: contentItem.PersistedContent = Services.MemberService.GetByUsername(contentItem.Username.Trim()); - break; + break; } break; case MembershipScenario.CustomProviderWithUmbracoLink: @@ -524,14 +527,14 @@ namespace Umbraco.Web.Editors default: var membershipUser = _provider.GetUser(contentItem.Key, false); //Go and re-fetch the persisted item - contentItem.PersistedContent = Mapper.Map(membershipUser); + contentItem.PersistedContent = Mapper.Map(membershipUser); break; } UpdateName(contentItem); //re-assign the mapped values that are not part of the membership provider properties. - var builtInAliases = Constants.Conventions.Member.GetStandardPropertyTypeStubs().Select(x => x.Key).ToArray(); + var builtInAliases = Constants.Conventions.Member.GetStandardPropertyTypeStubs().Select(x => x.Key).ToArray(); foreach (var p in contentItem.PersistedContent.Properties) { var valueMapped = currProps.SingleOrDefault(x => x.Alias == p.Alias); @@ -542,7 +545,7 @@ namespace Umbraco.Web.Editors p.TagSupport.Enable = valueMapped.TagSupport.Enable; p.TagSupport.Tags = valueMapped.TagSupport.Tags; } - } + } } /// @@ -595,7 +598,7 @@ namespace Umbraco.Web.Editors contentItem.IsApproved, Guid.NewGuid(), //since it's the umbraco provider, the user key here doesn't make any difference out status); - + break; case MembershipScenario.CustomProviderWithUmbracoLink: //We are using a custom membership provider, we'll create an empty IMember first to get the unique id to use @@ -618,7 +621,7 @@ namespace Umbraco.Web.Editors case MembershipScenario.StandaloneCustomProvider: // we don't have a member type to use so we will just create the basic membership user with the provider with no // link back to the umbraco data - + var newKey = Guid.NewGuid(); //TODO: We are not supporting q/a - passing in empty here membershipUser = _provider.CreateUser( @@ -628,9 +631,9 @@ namespace Umbraco.Web.Editors "TEMP", //some membership provider's require something here even if q/a is disabled! "TEMP", //some membership provider's require something here even if q/a is disabled! contentItem.IsApproved, - newKey, + newKey, out status); - + break; default: throw new ArgumentOutOfRangeException(); @@ -685,12 +688,12 @@ namespace Umbraco.Web.Editors break; case MembershipCreateStatus.InvalidProviderUserKey: ModelState.AddPropertyError( - //specify 'default' just so that it shows up as a notification - is not assigned to a property + //specify 'default' just so that it shows up as a notification - is not assigned to a property new ValidationResult("Invalid provider user key"), "default"); break; case MembershipCreateStatus.DuplicateProviderUserKey: ModelState.AddPropertyError( - //specify 'default' just so that it shows up as a notification - is not assigned to a property + //specify 'default' just so that it shows up as a notification - is not assigned to a property new ValidationResult("Duplicate provider user key"), "default"); break; case MembershipCreateStatus.ProviderError: diff --git a/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs b/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs index e1890326fb..edd1792e0e 100644 --- a/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs @@ -1,50 +1,54 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Web; -using System.Web.Http.Controllers; -using System.Web.Http.Filters; -using Umbraco.Core; - -namespace Umbraco.Web.WebApi.Filters -{ - /// - /// Ensures that the request is not cached by the browser - /// - public class DisableBrowserCacheAttribute : ActionFilterAttribute - { - public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) - { - //See: http://stackoverflow.com/questions/17755239/how-to-stop-chrome-from-caching-rest-response-from-webapi - - base.OnActionExecuted(actionExecutedContext); - - //NOTE: Until we upgraded to WebApi 2, this didn't work correctly and we had to revert to using - // HttpContext.Current responses. I've changed this back to what it should be now since it works - // and now with WebApi2, the HttpContext.Current responses dont! Anyways, all good now. - actionExecutedContext.Response.Headers.CacheControl = new CacheControlHeaderValue() - { - NoCache = true, - NoStore = true, - MaxAge = new TimeSpan(0), - MustRevalidate = true - }; - - actionExecutedContext.Response.Headers.Pragma.Add(new NameValueHeaderValue("no-cache")); - if (actionExecutedContext.Response.Content != null) - { - actionExecutedContext.Response.Content.Headers.Expires = - //Mon, 01 Jan 1990 00:00:00 GMT - new DateTimeOffset(1990, 1, 1, 0, 0, 0, TimeSpan.Zero); - } - - - - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Web; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; +using Umbraco.Core; + +namespace Umbraco.Web.WebApi.Filters +{ + /// + /// Ensures that the request is not cached by the browser + /// + public class DisableBrowserCacheAttribute : ActionFilterAttribute + { + public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) + { + //See: http://stackoverflow.com/questions/17755239/how-to-stop-chrome-from-caching-rest-response-from-webapi + + base.OnActionExecuted(actionExecutedContext); + if (actionExecutedContext == null || actionExecutedContext.Response == null || + actionExecutedContext.Response.Headers == null) + { + return; + } + //NOTE: Until we upgraded to WebApi 2, this didn't work correctly and we had to revert to using + // HttpContext.Current responses. I've changed this back to what it should be now since it works + // and now with WebApi2, the HttpContext.Current responses dont! Anyways, all good now. + actionExecutedContext.Response.Headers.CacheControl = new CacheControlHeaderValue() + { + NoCache = true, + NoStore = true, + MaxAge = new TimeSpan(0), + MustRevalidate = true + }; + + actionExecutedContext.Response.Headers.Pragma.Add(new NameValueHeaderValue("no-cache")); + if (actionExecutedContext.Response.Content != null) + { + actionExecutedContext.Response.Content.Headers.Expires = + //Mon, 01 Jan 1990 00:00:00 GMT + new DateTimeOffset(1990, 1, 1, 0, 0, 0, TimeSpan.Zero); + } + + + + } + } +} diff --git a/src/UmbracoExamine/UmbracoMemberIndexer.cs b/src/UmbracoExamine/UmbracoMemberIndexer.cs index 873391da50..4a460e0f38 100644 --- a/src/UmbracoExamine/UmbracoMemberIndexer.cs +++ b/src/UmbracoExamine/UmbracoMemberIndexer.cs @@ -16,8 +16,8 @@ using Lucene.Net.Analysis; namespace UmbracoExamine { - - /// + + /// /// Custom indexer for members /// public class UmbracoMemberIndexer : UmbracoContentIndexer @@ -26,29 +26,29 @@ namespace UmbracoExamine private readonly IMemberService _memberService; private readonly IDataTypeService _dataTypeService; - /// - /// Default constructor - /// - public UmbracoMemberIndexer() : base() - { + /// + /// Default constructor + /// + public UmbracoMemberIndexer() : base() + { _dataTypeService = ApplicationContext.Current.Services.DataTypeService; _memberService = ApplicationContext.Current.Services.MemberService; - } + } - /// - /// Constructor to allow for creating an indexer at runtime - /// - /// - /// - /// - /// - [Obsolete("Use the overload that specifies the Umbraco services")] - public UmbracoMemberIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, Analyzer analyzer, bool async) - : base(indexerData, indexPath, dataService, analyzer, async) - { + /// + /// Constructor to allow for creating an indexer at runtime + /// + /// + /// + /// + /// + [Obsolete("Use the overload that specifies the Umbraco services")] + public UmbracoMemberIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, Analyzer analyzer, bool async) + : base(indexerData, indexPath, dataService, analyzer, async) + { _dataTypeService = ApplicationContext.Current.Services.DataTypeService; _memberService = ApplicationContext.Current.Services.MemberService; - } + } /// /// Constructor to allow for creating an indexer at runtime @@ -60,19 +60,19 @@ namespace UmbracoExamine /// /// /// - public UmbracoMemberIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, - IDataTypeService dataTypeService, - IMemberService memberService, - Analyzer analyzer, bool async) - : base(indexerData, indexPath, dataService, analyzer, async) - { + public UmbracoMemberIndexer(IIndexCriteria indexerData, DirectoryInfo indexPath, IDataService dataService, + IDataTypeService dataTypeService, + IMemberService memberService, + Analyzer analyzer, bool async) + : base(indexerData, indexPath, dataService, analyzer, async) + { _dataTypeService = dataTypeService; _memberService = memberService; - } + } - - /// + + /// /// Ensures that the'_searchEmail' is added to the user fields so that it is indexed - without having to modify the config /// /// @@ -87,7 +87,7 @@ namespace UmbracoExamine if (indexerData.UserFields.Any(x => x.Name == "_searchEmail") == false) { - var field = new IndexField {Name = "_searchEmail"}; + var field = new IndexField { Name = "_searchEmail" }; var policy = IndexFieldPolicies.FirstOrDefault(x => x.Name == "_searchEmail"); if (policy != null) { @@ -97,7 +97,7 @@ namespace UmbracoExamine return new IndexCriteria( indexerData.StandardFields, - indexerData.UserFields.Concat(new[] {field}), + indexerData.UserFields.Concat(new[] { field }), indexerData.IncludeNodeTypes, indexerData.ExcludeNodeTypes, indexerData.ParentNodeId @@ -105,10 +105,10 @@ namespace UmbracoExamine } } - return indexerData; + return indexerData; } - /// + /// /// The supported types for this indexer /// protected override IEnumerable SupportedTypes @@ -119,39 +119,40 @@ namespace UmbracoExamine } } - /// - /// Reindex all members - /// - /// - protected override void PerformIndexAll(string type) - { + /// + /// Reindex all members + /// + /// + protected override void PerformIndexAll(string type) + { //This only supports members if (SupportedTypes.Contains(type) == false) return; - + const int pageSize = 1000; var pageIndex = 0; IMember[] members; if (IndexerData.IncludeNodeTypes.Any()) - { + { //if there are specific node types then just index those foreach (var nodeType in IndexerData.IncludeNodeTypes) - { + { do { long total; - members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName", Direction.Ascending, nodeType).ToArray(); + members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName" + , Direction.Ascending, true, nodeType).ToArray(); AddNodesToIndex(GetSerializedMembers(members), type); pageIndex++; } while (members.Length == pageSize); - } - } - else - { + } + } + else + { //no node types specified, do all members do { @@ -162,8 +163,8 @@ namespace UmbracoExamine pageIndex++; } while (members.Length == pageSize); - } - } + } + } private IEnumerable GetSerializedMembers(IEnumerable members) { @@ -171,18 +172,18 @@ namespace UmbracoExamine return members.Select(member => serializer.Serialize(_dataTypeService, member)); } - protected override XDocument GetXDocument(string xPath, string type) - { - throw new NotSupportedException(); - } - + protected override XDocument GetXDocument(string xPath, string type) + { + throw new NotSupportedException(); + } + protected override Dictionary GetSpecialFieldsToIndex(Dictionary allValuesForIndexing) { var fields = base.GetSpecialFieldsToIndex(allValuesForIndexing); //adds the special path property to the index fields.Add("__key", allValuesForIndexing["__key"]); - + return fields; } @@ -207,14 +208,14 @@ namespace UmbracoExamine if (e.Fields.ContainsKey("_searchEmail") == false) e.Fields.Add("_searchEmail", e.Node.Attribute("email").Value.Replace(".", " ").Replace("@", " ")); } - + if (e.Fields.ContainsKey(IconFieldName) == false) e.Fields.Add(IconFieldName, (string)e.Node.Attribute("icon")); } private static XElement GetMemberItem(int nodeId) { - //TODO: Change this so that it is not using the LegacyLibrary, just serialize manually! + //TODO: Change this so that it is not using the LegacyLibrary, just serialize manually! var nodes = LegacyLibrary.GetMember(nodeId); return XElement.Parse(nodes.Current.OuterXml); } From 40f7e87e048100e2e061971dceaba8cd84061ae8 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Sat, 2 Apr 2016 13:36:55 +0200 Subject: [PATCH 049/101] Fix issues when using custom font icons in backoffice. --- .../src/less/components/umb-content-grid.less | 4 +++- .../src/less/components/umb-folder-grid.less | 4 +++- .../src/less/components/umb-layout-selector.less | 4 +++- .../src/less/components/umb-table.less | 11 ++++++++--- src/Umbraco.Web.UI.Client/src/less/tree.less | 7 +++++-- .../src/views/components/umb-content-grid.html | 2 +- 6 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less index b34face8ba..aef523887c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-content-grid.less @@ -26,7 +26,9 @@ justify-content: center; } -.umb-content-grid__icon { +.umb-content-grid__icon, +.umb-content-grid__icon[class^="icon-"], +.umb-content-grid__icon[class*=" icon-"] { font-size: 40px; color: lighten(@gray, 20%); } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less index 14e343652d..c7c7fb78e5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-folder-grid.less @@ -37,7 +37,9 @@ align-items: center; } -.umb-folder-grid__folder-icon { +.umb-folder-grid__folder-icon, +.umb-folder-grid__folder-icon[class^="icon-"], +.umb-folder-grid__folder-icon[class*=" icon-"] { font-size: 20px; margin-right: 15px; color: @black; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less index 122107f88c..74d85461fa 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-layout-selector.less @@ -51,7 +51,9 @@ border: 1px solid @blue; } -.umb-layout-selector__dropdown-item-icon { +.umb-layout-selector__dropdown-item-icon, +.umb-layout-selector__dropdown-item-icon[class^="icon-"], +.umb-layout-selector__dropdown-item-icon[class*=" icon-"] { font-size: 20px; color: @gray; text-align: center; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less index e53fe2ba94..874f9e9551 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-table.less @@ -101,16 +101,21 @@ input.umb-table__input { } } -.umb-table-body__icon { +.umb-table-body__icon, +.umb-table-body__icon[class^="icon-"], +.umb-table-body__icon[class*=" icon-"] { margin: 0 auto; - font-size: 22px; + line-height: 22px; color: @blueDark; } -.umb-table-body__checkicon { +.umb-table-body__checkicon, +.umb-table-body__checkicon[class^="icon-"], +.umb-table-body__checkicon[class*=" icon-"] { display: none; font-size: 16px; + line-height: 22px; } .umb-table-body .umb-table__name { diff --git a/src/Umbraco.Web.UI.Client/src/less/tree.less b/src/Umbraco.Web.UI.Client/src/less/tree.less index 185b5a6e3b..35de441137 100644 --- a/src/Umbraco.Web.UI.Client/src/less/tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/tree.less @@ -418,9 +418,9 @@ div.locked:before{ padding-left: 10px; } .umb-actions-child li .menu-label { - font-size: 12px; + font-size: 14px; color: #000; - margin-left: 20px; + margin-left: 10px; } .umb-actions-child li .menu-label small { @@ -436,6 +436,9 @@ div.locked:before{ } .umb-actions-child i { font-size: 32px; + min-width: 32px; + text-align: center; + line-height: 24px; /* set line-height to ensure all icons use same line-height */ } .umb-actions-child li.add { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html index 7443cdd393..499594253e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-content-grid.html @@ -9,7 +9,7 @@
      - +
      From 4e4ea594330e63d33b8cf008ad1a20926e6add28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Riis-Knudsen?= Date: Sat, 2 Apr 2016 19:07:35 +0200 Subject: [PATCH 050/101] * Only restrict to image files when onlyImages is active* Handle disallowed filetypes as per other upload controls * Only restrict to image files when onlyImages is active * Handle disallowed filetypes as per other upload controls --- .../common/overlays/mediaPicker/mediapicker.controller.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js index a63dfdc951..430fdc707f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js @@ -16,7 +16,12 @@ angular.module("umbraco") $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; $scope.cropSize = dialogOptions.cropSize; $scope.lastOpenedNode = $cookieStore.get("umbLastOpenedMediaNodeId"); - $scope.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); + if($scope.onlyImages){ + $scope.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); + } + else { + $scope.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles).replace(/\./g, "!."); + } $scope.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; $scope.model.selectedImages = []; From 680396daabf11f052c58c1f25c50efda748a7fdc Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sun, 3 Apr 2016 11:06:37 +0200 Subject: [PATCH 051/101] Localize server-side error message for disallowed file uploads --- .../src/views/components/upload/umb-file-dropzone.html | 6 +++--- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 1 + src/Umbraco.Web/Editors/MediaController.cs | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html index c1d5ad9de8..60cec9996f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html @@ -78,12 +78,12 @@ {{ file.name }} - (: "{{ accept }}") - ( "{{maxFileSize}}") + + "{{maxFileSize}}" - ({{file.serverErrorMessage}}) + {{file.serverErrorMessage}}
      diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index aa2518a372..402902f48c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -175,6 +175,7 @@ Link to media or click here to choose files Only allowed file types are + Cannot upload this file, it does not have an approved file type Max file size is diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 66889e2206..9e845e4649 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -571,7 +571,7 @@ namespace Umbraco.Web.Editors { tempFiles.Notifications.Add(new Notification( Services.TextService.Localize("speechBubbles/operationFailedHeader"), - "Cannot upload file " + file.Headers.ContentDisposition.FileName + ", it is not an approved file type", + Services.TextService.Localize("media/disallowedFileType"), SpeechBubbleIcon.Warning)); } } From 65024707086fb5554fd1a837ffa23bc1fc4e5a6c Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sun, 3 Apr 2016 12:01:10 +0200 Subject: [PATCH 052/101] U4-8265 Listview list and listview grid does not handle acceptedFileTypes in same way --- .../listview/layouts/grid/grid.listviewlayout.controller.js | 4 +--- .../listview/layouts/list/list.listviewlayout.controller.js | 5 ++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js index e1754dd89d..9ffe69306b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js @@ -14,10 +14,8 @@ var vm = this; vm.nodeId = $scope.contentId; - //vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); - //instead of passing in a whitelist, we pass in a blacklist by adding ! to the ext + //we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles).replace(/\./g, "!."); - vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; vm.activeDrag = false; vm.mediaDetailsTooltip = {}; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js index 1d2d361f28..909e2dee49 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js @@ -6,9 +6,8 @@ var vm = this; vm.nodeId = $scope.contentId; - //vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); - //instead of passing in a whitelist, we pass in a blacklist by adding ! to the ext - vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles).replace(/./g, "!."); + //we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles + vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles).replace(/\./g, "!."); vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; vm.activeDrag = false; vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; From d8c781f97f344792b2d21936605f2045baa112cf Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Sun, 3 Apr 2016 13:39:05 +0200 Subject: [PATCH 053/101] Make the file exclusion filter more readable --- .../views/common/overlays/mediaPicker/mediapicker.controller.js | 2 +- .../listview/layouts/grid/grid.listviewlayout.controller.js | 2 +- .../listview/layouts/list/list.listviewlayout.controller.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js index 430fdc707f..774e14f50e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js @@ -20,7 +20,7 @@ angular.module("umbraco") $scope.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); } else { - $scope.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles).replace(/\./g, "!."); + $scope.acceptedFileTypes = !mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles); } $scope.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js index 9ffe69306b..24c00ebe1d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js @@ -15,7 +15,7 @@ vm.nodeId = $scope.contentId; //we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles - vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles).replace(/\./g, "!."); + vm.acceptedFileTypes = !mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles); vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; vm.activeDrag = false; vm.mediaDetailsTooltip = {}; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js index 909e2dee49..eb29e542e6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js @@ -7,7 +7,7 @@ vm.nodeId = $scope.contentId; //we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles - vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles).replace(/\./g, "!."); + vm.acceptedFileTypes = !mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.disallowedUploadFiles); vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; vm.activeDrag = false; vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; From 8e8b8bdea10c2ea7d28d60fa3ae91c564f3d8ee4 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 4 Apr 2016 09:20:59 +0200 Subject: [PATCH 054/101] Revert "Removes old obsoleted ping.aspx" This reverts commit 453efc02a5370b477a36bd4aadb7a8084d348438. Conflicts: src/Umbraco.Web.UI/Umbraco.Web.UI.csproj U4-8274 '/umbraco/ping.aspx' is missing --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 3 ++ src/Umbraco.Web.UI/umbraco/ping.aspx | 2 + src/Umbraco.Web/Umbraco.Web.csproj | 3 ++ .../umbraco.presentation/umbraco/ping.aspx.cs | 38 +++++++++++++++++++ 4 files changed, 46 insertions(+) create mode 100644 src/Umbraco.Web.UI/umbraco/ping.aspx create mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/ping.aspx.cs diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 8f1f01f137..2f48f4a2e3 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2241,6 +2241,9 @@ Form + + Form + diff --git a/src/Umbraco.Web.UI/umbraco/ping.aspx b/src/Umbraco.Web.UI/umbraco/ping.aspx new file mode 100644 index 0000000000..d79e25b2a1 --- /dev/null +++ b/src/Umbraco.Web.UI/umbraco/ping.aspx @@ -0,0 +1,2 @@ +<%@ Page language="c#" AutoEventWireup="True" %> +I'm alive! \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7aec0b676f..35dc036fd3 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1088,6 +1088,9 @@ ASPXCodeBehind + + ASPXCodeBehind + diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/ping.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/ping.aspx.cs new file mode 100644 index 0000000000..410ada9f4d --- /dev/null +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/ping.aspx.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Web; +using System.Web.SessionState; +using System.Web.UI; +using System.Web.UI.WebControls; +using System.Web.UI.HtmlControls; + +namespace umbraco.presentation +{ + + [Obsolete("This class will be removed in future versions.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public partial class ping : System.Web.UI.Page + { + #region Web Form Designer generated code + override protected void OnInit(EventArgs e) + { + // + // CODEGEN: This call is required by the ASP.NET Web Form Designer. + // + InitializeComponent(); + base.OnInit(e); + } + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + } + #endregion + } +} From 2d514ac09ef547fb67a4a8217334bb550486939c Mon Sep 17 00:00:00 2001 From: madden-tom Date: Tue, 5 Apr 2016 07:01:05 +0100 Subject: [PATCH 055/101] =?UTF-8?q?Added=20check=20for=20publish=20permiss?= =?UTF-8?q?ions=20to=20remove=20option=20for=20Publish=20At=20a=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit U4-287 "Publish at" circumvents "Send for approval/publishing" --- src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index 1f36eb6c5b..90d4efa963 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -164,6 +164,7 @@ namespace Umbraco.Web.Models.Mapping TabsAndPropertiesResolver.AddListView(display, "content", dataTypeService, localizedText); } + //Added check for publish permissions before adding releaseDate and expireDate var properties = new List { new ContentPropertyDisplay @@ -173,19 +174,19 @@ namespace Umbraco.Web.Models.Mapping Value = localizedText.UmbracoDictionaryTranslate(display.ContentTypeName), View = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View }, - new ContentPropertyDisplay + new ContentPropertyDisplay { Alias = string.Format("{0}releasedate", Constants.PropertyEditors.InternalGenericPropertiesPrefix), Label = localizedText.Localize("content/releaseDate"), Value = display.ReleaseDate.HasValue ? display.ReleaseDate.Value.ToIsoString() : null, - View = "datepicker" //TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor - }, + View = display.AllowedActions.Contains('P') ? "datepicker" : PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View //TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor + } , new ContentPropertyDisplay { Alias = string.Format("{0}expiredate", Constants.PropertyEditors.InternalGenericPropertiesPrefix), Label = localizedText.Localize("content/unpublishDate"), Value = display.ExpireDate.HasValue ? display.ExpireDate.Value.ToIsoString() : null, - View = "datepicker" //TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor + View = display.AllowedActions.Contains('P') ? "datepicker" : PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View //TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor }, new ContentPropertyDisplay { From c856795cb77ad7cdebd71cb052b4a299ea26b075 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 5 Apr 2016 08:03:47 +0200 Subject: [PATCH 056/101] Fix hilariously wrong copy/pasted TODO comments.. --- src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index 90d4efa963..ede1dfc78d 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -164,7 +164,6 @@ namespace Umbraco.Web.Models.Mapping TabsAndPropertiesResolver.AddListView(display, "content", dataTypeService, localizedText); } - //Added check for publish permissions before adding releaseDate and expireDate var properties = new List { new ContentPropertyDisplay @@ -179,14 +178,18 @@ namespace Umbraco.Web.Models.Mapping Alias = string.Format("{0}releasedate", Constants.PropertyEditors.InternalGenericPropertiesPrefix), Label = localizedText.Localize("content/releaseDate"), Value = display.ReleaseDate.HasValue ? display.ReleaseDate.Value.ToIsoString() : null, - View = display.AllowedActions.Contains('P') ? "datepicker" : PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View //TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor + //Not editible for people without publish permission (U4-287) + View = display.AllowedActions.Contains('P') ? "datepicker" : PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View + //TODO: Fix up hard coded datepicker } , new ContentPropertyDisplay { Alias = string.Format("{0}expiredate", Constants.PropertyEditors.InternalGenericPropertiesPrefix), Label = localizedText.Localize("content/unpublishDate"), Value = display.ExpireDate.HasValue ? display.ExpireDate.Value.ToIsoString() : null, - View = display.AllowedActions.Contains('P') ? "datepicker" : PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View //TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor + //Not editible for people without publish permission (U4-287) + View = display.AllowedActions.Contains('P') ? "datepicker" : PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View + //TODO: Fix up hard coded datepicker }, new ContentPropertyDisplay { From 80d7443bd3b255281cbdbce1ee66f590175bf504 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 5 Apr 2016 16:00:49 +0200 Subject: [PATCH 057/101] fix build --- .../Repositories/ContentRepository.cs | 3 +- src/Umbraco.Core/Services/ContentService.cs | 45 +++++-------------- src/Umbraco.Core/Services/MediaService.cs | 40 ++++------------- src/Umbraco.Core/Services/MemberService.cs | 6 +-- src/Umbraco.Web.UI/config/trees.config | 1 + src/Umbraco.Web/Editors/MediaController.cs | 2 +- src/Umbraco.Web/Editors/MemberController.cs | 3 +- src/UmbracoExamine/UmbracoMemberIndexer.cs | 3 +- 8 files changed, 27 insertions(+), 76 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 5c66f2a19c..e0e9f0ec52 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -234,7 +234,6 @@ namespace Umbraco.Core.Persistence.Repositories var processed = 0; do { - var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, "Path", Direction.Ascending, true); //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 @@ -242,7 +241,7 @@ namespace Umbraco.Core.Persistence.Repositories // see: http://issues.umbraco.org/issue/U4-6322 & http://issues.umbraco.org/issue/U4-5982 var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, new Tuple("cmsDocument", "nodeId"), - ProcessQuery, "Path", Direction.Ascending); + ProcessQuery, "Path", Direction.Ascending, true); var xmlItems = (from descendant in descendants let xml = serializer(descendant) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index ed9dab8e29..c56d9360b3 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -487,22 +487,10 @@ namespace Umbraco.Core.Services public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy, Direction orderDirection, string filter = "") { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - - var query = Query.Builder; - //if the id is System Root, then just get all - if (id != Constants.System.Root) - { - query.Where(x => x.ParentId == id); - } - long total; - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); - totalChildren = Convert.ToInt32(total); - return contents; - } + long total; + var result = GetPagedChildren(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); + totalChildren = Convert.ToInt32(total); + return result; } /// @@ -541,22 +529,10 @@ namespace Umbraco.Core.Services [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - - var query = Query.Builder; - //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)); - } - long total; - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); - totalChildren = Convert.ToInt32(total); - return contents; - } + long total; + var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); + totalChildren = Convert.ToInt32(total); + return result; } /// @@ -570,9 +546,8 @@ namespace Umbraco.Core.Services /// Direction to order by /// Flag to indicate when ordering by system field /// Search text filter - /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") + /// An Enumerable list of objects + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index b60a66451e..64a22df06f 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -397,19 +397,10 @@ namespace Umbraco.Core.Services public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy, Direction orderDirection, string filter = "") { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - var query = Query.Builder; - query.Where(x => x.ParentId == id); - - long total; - var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); - - totalChildren = Convert.ToInt32(total); - return medias; - } + long total; + var result = GetPagedChildren(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); + totalChildren = Convert.ToInt32(total); + return result; } /// @@ -444,22 +435,10 @@ namespace Umbraco.Core.Services [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - - var query = Query.Builder; - //if the id is -1, then just get all - if (id != -1) - { - query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); - } - long total; - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); - totalChildren = Convert.ToInt32(total); - return contents; - } + long total; + var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); + totalChildren = Convert.ToInt32(total); + return result; } /// @@ -474,8 +453,7 @@ namespace Umbraco.Core.Services /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index db3bfbbd61..47fe52ada9 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -695,16 +695,16 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = "") + string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = "") { long total; - var result = GetAll(Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, memberTypeAlias, filter); + var result = GetAll(Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, memberTypeAlias, filter); totalRecords = Convert.ToInt32(total); return result; } public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = "") { var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMemberRepository(uow)) diff --git a/src/Umbraco.Web.UI/config/trees.config b/src/Umbraco.Web.UI/config/trees.config index 42bc28239b..91c216c727 100644 --- a/src/Umbraco.Web.UI/config/trees.config +++ b/src/Umbraco.Web.UI/config/trees.config @@ -41,4 +41,5 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 6ec9bcb0f8..d2963832fd 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -183,7 +183,7 @@ namespace Umbraco.Web.Editors bool orderBySystemField = true, string filter = "") { - int totalChildren; + long totalChildren; IMedia[] children; if (pageNumber > 0 && pageSize > 0) { diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 0790997b0e..f4799f983c 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -91,8 +91,7 @@ namespace Umbraco.Web.Editors if (MembershipScenario == MembershipScenario.NativeUmbraco) { long totalRecords; - var members = Services.MemberService.GetAll((pageNumber - 1), pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, memberTypeAlias, filter).ToArray(); - var members = Services.MemberService.GetAll((pageNumber - 1), pageSize, out totalRecords, orderBy, orderDirection, memberTypeAlias, filter).ToArray(); + var members = Services.MemberService.GetAll((pageNumber - 1), pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, memberTypeAlias, filter).ToArray(); if (totalRecords == 0) { return new PagedResult(0, 0, 0); diff --git a/src/UmbracoExamine/UmbracoMemberIndexer.cs b/src/UmbracoExamine/UmbracoMemberIndexer.cs index 8c3aced77c..134509610a 100644 --- a/src/UmbracoExamine/UmbracoMemberIndexer.cs +++ b/src/UmbracoExamine/UmbracoMemberIndexer.cs @@ -142,8 +142,7 @@ namespace UmbracoExamine do { long total; - var members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName", Direction.Ascending, true, nodeType).ToArray(); - members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName", Direction.Ascending, nodeType).ToArray(); + members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName", Direction.Ascending, true, nodeType).ToArray(); AddNodesToIndex(GetSerializedMembers(members), type); From 90a66aeebd231027b31aa88a31c9539c819e1ba3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 5 Apr 2016 16:32:46 +0200 Subject: [PATCH 058/101] fixes merge / build --- .../Repositories/VersionableRepositoryBase.cs | 42 +++---------------- .../SqlSyntax/MySqlSyntaxProvider.cs | 6 +-- src/Umbraco.Core/Services/IContentService.cs | 4 +- src/Umbraco.Core/Services/IMediaService.cs | 4 +- src/Umbraco.Core/Services/IMemberService.cs | 2 +- src/Umbraco.Core/Services/MediaService.cs | 4 +- src/Umbraco.Core/Services/MemberService.cs | 2 +- 7 files changed, 14 insertions(+), 50 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index bb7780887e..2d55a3dd05 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -271,9 +271,9 @@ namespace Umbraco.Core.Persistence.Repositories { // Sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through valie // from most recent content version for the given order by field - var sortedInt = string.Format(SqlSyntaxContext.SqlSyntaxProvider.ConvertIntegerToOrderableString, "dataInt"); - var sortedDate = string.Format(SqlSyntaxContext.SqlSyntaxProvider.ConvertDateToOrderableString, "dataDate"); - var sortedString = string.Format(SqlSyntaxContext.SqlSyntaxProvider.IsNull, "dataNvarchar", "''"); + var sortedInt = string.Format(SqlSyntax.ConvertIntegerToOrderableString, "dataInt"); + var sortedDate = string.Format(SqlSyntax.ConvertDateToOrderableString, "dataDate"); + var sortedString = string.Format(SqlSyntax.IsNull, "dataNvarchar", "''"); var orderBySql = string.Format(@"ORDER BY ( SELECT CASE @@ -296,41 +296,9 @@ namespace Umbraco.Core.Persistence.Repositories if (orderDirection == Direction.Descending) { sortedSql.Append(" DESC"); - } - } + } } - else - { - // Sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through valie - // from most recent content version for the given order by field - var sortedInt = string.Format(SqlSyntaxContext.SqlSyntaxProvider.ConvertIntegerToOrderableString, "dataInt"); - var sortedDate = string.Format(SqlSyntaxContext.SqlSyntaxProvider.ConvertDateToOrderableString, "dataDate"); - var sortedString = string.Format(SqlSyntaxContext.SqlSyntaxProvider.IsNull, "dataNvarchar", "''"); - - var orderBySql = string.Format(@"ORDER BY ( - SELECT CASE - WHEN dataInt Is Not Null THEN {0} - WHEN dataDate Is Not Null THEN {1} - ELSE {2} - END - FROM cmsContent c - INNER JOIN cmsContentVersion cv ON cv.ContentId = c.nodeId AND VersionDate = ( - SELECT Max(VersionDate) - FROM cmsContentVersion - WHERE ContentId = c.nodeId - ) - INNER JOIN cmsPropertyData cpd ON cpd.contentNodeId = c.nodeId - AND cpd.versionId = cv.VersionId - INNER JOIN cmsPropertyType cpt ON cpt.Id = cpd.propertytypeId - WHERE c.nodeId = umbracoNode.Id and cpt.Alias = @0)", sortedInt, sortedDate, sortedString); - - sortedSql.Append(orderBySql, orderBy); - if (orderDirection == Direction.Descending) - { - sortedSql.Append(" DESC"); - } - } - + return sortedSql; } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index 9291138bce..d02a5fb8dc 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -363,11 +363,7 @@ ORDER BY TABLE_NAME, INDEX_NAME", public override string IsNull { get { return "IFNULL({0},{1})"; } } public override string ConvertIntegerToOrderableString { get { return "LPAD({0}, 8, '0')"; } } public override string ConvertDateToOrderableString { get { return "DATE_FORMAT({0}, '%Y%m%d')"; } } - - public override string IsNull { get { return "IFNULL({0},{1})"; } } - public override string ConvertIntegerToOrderableString { get { return "LPAD({0}, 8, '0')"; } } - public override string ConvertDateToOrderableString { get { return "DATE_FORMAT({0}, '%Y%m%d')"; } } - + public override bool? SupportsCaseInsensitiveQueries(Database db) { bool? supportsCaseInsensitiveQueries = null; diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 32d5fb1209..75a412e683 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -203,7 +203,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); /// /// Gets a collection of objects by Parent Id @@ -223,7 +223,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); /// /// Gets a collection of objects by Parent Id diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index f950b88734..8359b487b1 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -121,7 +121,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); /// /// Gets a collection of objects by Parent Id @@ -141,7 +141,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); /// /// Gets a collection of objects by Parent Id diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index fbad457ecc..ee37d983aa 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = ""); + string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = ""); /// /// Gets a list of paged objects diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 786dcaa657..d73ee52c2e 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -395,7 +395,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, - string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") + string orderBy, Direction orderDirection, string filter = "") { long total; var result = GetPagedChildren(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); @@ -433,7 +433,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") + public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") { long total; var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index e9e066cb3c..93d7a1a89f 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -695,7 +695,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = "") + string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = "") { long total; var result = GetAll(Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, memberTypeAlias, filter); From dac7b9320985d1f8b36bcfa88992d4b2d7b80571 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 6 Apr 2016 09:17:05 +0200 Subject: [PATCH 059/101] fixes GetChildren non bool argument to work as a bool again. --- .../src/common/resources/content.resource.js | 1238 +++++++++-------- src/Umbraco.Web/Editors/ContentController.cs | 7 +- 2 files changed, 629 insertions(+), 616 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 60ecd16f19..3c1c1e2252 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -25,636 +25,650 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { - /** internal method process the saving of data and post processing the result */ - function saveContentItem(content, action, files) { - return umbRequestHelper.postSaveContent({ - restApiUrl: umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"), - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatContentPostData(c, a); - } - }); - } + /** internal method process the saving of data and post processing the result */ + function saveContentItem(content, action, files) { + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"), + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatContentPostData(c, a); + } + }); + } - return { + return { - getRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for content recycle bin'); - }, + getRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetRecycleBin")), + 'Failed to retrieve data for content recycle bin'); + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#sort - * @methodOf umbraco.resources.contentResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
      -        * var ids = [123,34533,2334,23434];
      -        * contentResource.sort({ parentId: 1244, sortedIds: ids })
      -        *    .then(function() {
      -        *        $scope.complete = true;
      -        *    });
      -        * 
      - * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ - sort: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.sortedIds) { - throw "args.sortedIds cannot be null"; - } + /** + * @ngdoc method + * @name umbraco.resources.contentResource#sort + * @methodOf umbraco.resources.contentResource + * + * @description + * Sorts all children below a given parent node id, based on a collection of node-ids + * + * ##usage + *
      +          * var ids = [123,34533,2334,23434];
      +          * contentResource.sort({ parentId: 1244, sortedIds: ids })
      +          *    .then(function() {
      +          *        $scope.complete = true;
      +          *    });
      +          * 
      + * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ + sort: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.sortedIds) { + throw "args.sortedIds cannot be null"; + } - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort content'); - }, + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"), + { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), + 'Failed to sort content'); + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#move - * @methodOf umbraco.resources.contentResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
      -        * contentResource.move({ parentId: 1244, id: 123 })
      -        *    .then(function() {
      -        *        alert("node was moved");
      -        *    }, function(err){
      -        *      alert("node didnt move:" + err.data.Message); 
      -        *    });
      -        * 
      - * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } + /** + * @ngdoc method + * @name umbraco.resources.contentResource#move + * @methodOf umbraco.resources.contentResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
      +          * contentResource.move({ parentId: 1244, id: 123 })
      +          *    .then(function() {
      +          *        alert("node was moved");
      +          *    }, function(err){
      +          *      alert("node didnt move:" + err.data.Message); 
      +          *    });
      +          * 
      + * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move content'); - }, + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move content'); + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#copy - * @methodOf umbraco.resources.contentResource - * - * @description - * Copies a node underneath a new parentId - * - * ##usage - *
      -        * contentResource.copy({ parentId: 1244, id: 123 })
      -        *    .then(function() {
      -        *        alert("node was copied");
      -        *    }, function(err){
      -        *      alert("node wasnt copy:" + err.data.Message); 
      -        *    });
      -        * 
      - * @param {Object} args arguments object - * @param {Int} args.id the ID of the node to copy - * @param {Int} args.parentId the ID of the parent node to copy to - * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api - * @returns {Promise} resourcePromise object. - * - */ - copy: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } + /** + * @ngdoc method + * @name umbraco.resources.contentResource#copy + * @methodOf umbraco.resources.contentResource + * + * @description + * Copies a node underneath a new parentId + * + * ##usage + *
      +          * contentResource.copy({ parentId: 1244, id: 123 })
      +          *    .then(function() {
      +          *        alert("node was copied");
      +          *    }, function(err){
      +          *      alert("node wasnt copy:" + err.data.Message); 
      +          *    });
      +          * 
      + * @param {Object} args arguments object + * @param {Int} args.id the ID of the node to copy + * @param {Int} args.parentId the ID of the parent node to copy to + * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api + * @returns {Promise} resourcePromise object. + * + */ + copy: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"), - args), - 'Failed to copy content'); - }, + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"), + args), + 'Failed to copy content'); + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#unPublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Unpublishes a content item with a given Id - * - * ##usage - *
      -        * contentResource.unPublish(1234)
      -        *    .then(function() {
      -        *        alert("node was unpulished");
      -        *    }, function(err){
      -        *      alert("node wasnt unpublished:" + err.data.Message); 
      -        *    });
      -        * 
      - * @param {Int} id the ID of the node to unpublish - * @returns {Promise} resourcePromise object. - * - */ - unPublish: function (id) { - if (!id) { - throw "id cannot be null"; - } + /** + * @ngdoc method + * @name umbraco.resources.contentResource#unPublish + * @methodOf umbraco.resources.contentResource + * + * @description + * Unpublishes a content item with a given Id + * + * ##usage + *
      +          * contentResource.unPublish(1234)
      +          *    .then(function() {
      +          *        alert("node was unpulished");
      +          *    }, function(err){
      +          *      alert("node wasnt unpublished:" + err.data.Message); 
      +          *    });
      +          * 
      + * @param {Int} id the ID of the node to unpublish + * @returns {Promise} resourcePromise object. + * + */ + unPublish: function (id) { + if (!id) { + throw "id cannot be null"; + } - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostUnPublish", - [{ id: id }])), - 'Failed to publish content with id ' + id); - }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#emptyRecycleBin - * @methodOf umbraco.resources.contentResource - * - * @description - * Empties the content recycle bin - * - * ##usage - *
      -        * contentResource.emptyRecycleBin()
      -        *    .then(function() {
      -        *        alert('its empty!');
      -        *    });
      -        * 
      - * - * @returns {Promise} resourcePromise object. - * - */ - emptyRecycleBin: function () { - return umbRequestHelper.resourcePromise( + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostUnPublish", + [{ id: id }])), + 'Failed to publish content with id ' + id); + }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#emptyRecycleBin + * @methodOf umbraco.resources.contentResource + * + * @description + * Empties the content recycle bin + * + * ##usage + *
      +          * contentResource.emptyRecycleBin()
      +          *    .then(function() {
      +          *        alert('its empty!');
      +          *    });
      +          * 
      + * + * @returns {Promise} resourcePromise object. + * + */ + emptyRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#deleteById + * @methodOf umbraco.resources.contentResource + * + * @description + * Deletes a content item with a given id + * + * ##usage + *
      +          * contentResource.deleteById(1234)
      +          *    .then(function() {
      +          *        alert('its gone!');
      +          *    });
      +          * 
      + * + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete item ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getById + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets a content item with a given id + * + * ##usage + *
      +          * contentResource.getById(1234)
      +          *    .then(function(content) {
      +          *        var myDoc = content; 
      +          *        alert('its here!');
      +          *    });
      +          * 
      + * + * @param {Int} id id of content item to return + * @returns {Promise} resourcePromise object containing the content item. + * + */ + getById: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve data for content id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getByIds + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets an array of content items, given a collection of ids + * + * ##usage + *
      +          * contentResource.getByIds( [1234,2526,28262])
      +          *    .then(function(contentArray) {
      +          *        var myDoc = contentArray; 
      +          *        alert('they are here!');
      +          *    });
      +          * 
      + * + * @param {Array} ids ids of content items to return as an array + * @returns {Promise} resourcePromise object containing the content items array. + * + */ + getByIds: function (ids) { + + var idQuery = ""; + _.each(ids, function (item) { + idQuery += "ids=" + item + "&"; + }); + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetByIds", + idQuery)), + 'Failed to retrieve data for content with multiple ids'); + }, + + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getScaffold + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. + * + * - Parent Id must be provided so umbraco knows where to store the content + * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold + * + * The scaffold is used to build editors for content that has not yet been populated with data. + * + * ##usage + *
      +          * contentResource.getScaffold(1234, 'homepage')
      +          *    .then(function(scaffold) {
      +          *        var myDoc = scaffold;
      +          *        myDoc.name = "My new document"; 
      +          *
      +          *        contentResource.publish(myDoc, true)
      +          *            .then(function(content){
      +          *                alert("Retrieved, updated and published again");
      +          *            });
      +          *    });
      +          * 
      + * + * @param {Int} parentId id of content item to return + * @param {String} alias contenttype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the content scaffold. + * + */ + getScaffold: function (parentId, alias) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty content item type ' + alias); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getNiceUrl + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a url, given a node ID + * + * ##usage + *
      +          * contentResource.getNiceUrl(id)
      +          *    .then(function(url) {
      +          *        alert('its here!');
      +          *    });
      +          * 
      + * + * @param {Int} id Id of node to return the public url to + * @returns {Promise} resourcePromise object containing the url. + * + */ + getNiceUrl: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetNiceUrl", [{ id: id }])), + 'Failed to retrieve url for id:' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getChildren + * @methodOf umbraco.resources.contentResource + * + * @description + * Gets children of a content item with a given id + * + * ##usage + *
      +          * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
      +          *    .then(function(contentArray) {
      +          *        var children = contentArray; 
      +          *        alert('they are here!');
      +          *    });
      +          * 
      + * + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getChildren: function (parentId, options) { + + var defaults = { + pageSize: 0, + pageNumber: 0, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + orderBySystemField: true + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } + + //converts the value to a js bool + function toBool(v) { + if (angular.isNumber(v)) { + return v > 0; + } + if (angular.isString(v)) { + return v === "true"; + } + if (typeof v === "boolean") { + return v; + } + return false; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetChildren", + [ + { id: parentId }, + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: toBool(options.orderBySystemField) }, + { filter: options.filter } + ])), + 'Failed to retrieve children for content item ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#hasPermission + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns true/false given a permission char to check against a nodeID + * for the current user + * + * ##usage + *
      +          * contentResource.hasPermission('p',1234)
      +          *    .then(function() {
      +          *        alert('You are allowed to publish this item');
      +          *    });
      +          * 
      + * + * @param {String} permission char representing the permission to check + * @param {Int} id id of content item to delete + * @returns {Promise} resourcePromise object. + * + */ + checkPermission: function (permission, id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "HasPermission", + [{ permissionToCheck: permission }, { nodeId: id }])), + 'Failed to check permission for item ' + id); + }, + + getPermissions: function (nodeIds) { + return umbRequestHelper.resourcePromise( $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "EmptyRecycleBin")), - 'Failed to empty the recycle bin'); - }, + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetPermissions"), + nodeIds), + 'Failed to get permissions'); + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#deleteById - * @methodOf umbraco.resources.contentResource - * - * @description - * Deletes a content item with a given id - * - * ##usage - *
      -        * contentResource.deleteById(1234)
      -        *    .then(function() {
      -        *        alert('its gone!');
      -        *    });
      -        * 
      - * - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteById: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getById - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets a content item with a given id - * - * ##usage - *
      -        * contentResource.getById(1234)
      -        *    .then(function(content) {
      -        *        var myDoc = content; 
      -        *        alert('its here!');
      -        *    });
      -        * 
      - * - * @param {Int} id id of content item to return - * @returns {Promise} resourcePromise object containing the content item. - * - */ - getById: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve data for content id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getByIds - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets an array of content items, given a collection of ids - * - * ##usage - *
      -        * contentResource.getByIds( [1234,2526,28262])
      -        *    .then(function(contentArray) {
      -        *        var myDoc = contentArray; 
      -        *        alert('they are here!');
      -        *    });
      -        * 
      - * - * @param {Array} ids ids of content items to return as an array - * @returns {Promise} resourcePromise object containing the content items array. - * - */ - getByIds: function (ids) { - - var idQuery = ""; - _.each(ids, function (item) { - idQuery += "ids=" + item + "&"; - }); - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for content with multiple ids'); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#save + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
      +          * contentResource.getById(1234)
      +          *    .then(function(content) {
      +          *          content.name = "I want a new name!";
      +          *          contentResource.save(content, false)
      +          *            .then(function(content){
      +          *                alert("Retrieved, updated and saved again");
      +          *            });
      +          *    });
      +          * 
      + * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + save: function (content, isNew, files) { + return saveContentItem(content, "save" + (isNew ? "New" : ""), files); + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getScaffold - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias. - * - * - Parent Id must be provided so umbraco knows where to store the content - * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold - * - * The scaffold is used to build editors for content that has not yet been populated with data. - * - * ##usage - *
      -        * contentResource.getScaffold(1234, 'homepage')
      -        *    .then(function(scaffold) {
      -        *        var myDoc = scaffold;
      -        *        myDoc.name = "My new document"; 
      -        *
      -        *        contentResource.publish(myDoc, true)
      -        *            .then(function(content){
      -        *                alert("Retrieved, updated and published again");
      -        *            });
      -        *    });
      -        * 
      - * - * @param {Int} parentId id of content item to return - * @param {String} alias contenttype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the content scaffold. - * - */ - getScaffold: function (parentId, alias) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty content item type ' + alias); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getNiceUrl - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns a url, given a node ID - * - * ##usage - *
      -        * contentResource.getNiceUrl(id)
      -        *    .then(function(url) {
      -        *        alert('its here!');
      -        *    });
      -        * 
      - * - * @param {Int} id Id of node to return the public url to - * @returns {Promise} resourcePromise object containing the url. - * - */ - getNiceUrl: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetNiceUrl", [{ id: id }])), - 'Failed to retrieve url for id:' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#getChildren - * @methodOf umbraco.resources.contentResource - * - * @description - * Gets children of a content item with a given id - * - * ##usage - *
      -        * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
      -        *    .then(function(contentArray) {
      -        *        var children = contentArray; 
      -        *        alert('they are here!');
      -        *    });
      -        * 
      - * - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getChildren: function (parentId, options) { - - var defaults = { - pageSize: 0, - pageNumber: 0, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder", - orderBySystemField: true - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetChildren", - [ - { id: parentId }, - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { orderBySystemField: options.orderBySystemField }, - { filter: options.filter } - ])), - 'Failed to retrieve children for content item ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#hasPermission - * @methodOf umbraco.resources.contentResource - * - * @description - * Returns true/false given a permission char to check against a nodeID - * for the current user - * - * ##usage - *
      -        * contentResource.hasPermission('p',1234)
      -        *    .then(function() {
      -        *        alert('You are allowed to publish this item');
      -        *    });
      -        * 
      - * - * @param {String} permission char representing the permission to check - * @param {Int} id id of content item to delete - * @returns {Promise} resourcePromise object. - * - */ - checkPermission: function (permission, id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "HasPermission", - [{ permissionToCheck: permission }, { nodeId: id }])), - 'Failed to check permission for item ' + id); - }, - - getPermissions: function (nodeIds) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetPermissions"), - nodeIds), - 'Failed to get permissions'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#save - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
      -        * contentResource.getById(1234)
      -        *    .then(function(content) {
      -        *          content.name = "I want a new name!";
      -        *          contentResource.save(content, false)
      -        *            .then(function(content){
      -        *                alert("Retrieved, updated and saved again");
      -        *            });
      -        *    });
      -        * 
      - * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - save: function (content, isNew, files) { - return saveContentItem(content, "save" + (isNew ? "New" : ""), files); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#publish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
      +          * contentResource.getById(1234)
      +          *    .then(function(content) {
      +          *          content.name = "I want a new name, and be published!";
      +          *          contentResource.publish(content, false)
      +          *            .then(function(content){
      +          *                alert("Retrieved, updated and published again");
      +          *            });
      +          *    });
      +          * 
      + * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + publish: function (content, isNew, files) { + return saveContentItem(content, "publish" + (isNew ? "New" : ""), files); + }, - /** - * @ngdoc method - * @name umbraco.resources.contentResource#publish - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation - * if the content item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
      -        * contentResource.getById(1234)
      -        *    .then(function(content) {
      -        *          content.name = "I want a new name, and be published!";
      -        *          contentResource.publish(content, false)
      -        *            .then(function(content){
      -        *                alert("Retrieved, updated and published again");
      -        *            });
      -        *    });
      -        * 
      - * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - publish: function (content, isNew, files) { - return saveContentItem(content, "publish" + (isNew ? "New" : ""), files); - }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#sendToPublish + * @methodOf umbraco.resources.contentResource + * + * @description + * Saves changes made to a content item, and notifies any subscribers about a pending publication + * + * ##usage + *
      +          * contentResource.getById(1234)
      +          *    .then(function(content) {
      +          *          content.name = "I want a new name, and be published!";
      +          *          contentResource.sendToPublish(content, false)
      +          *            .then(function(content){
      +          *                alert("Retrieved, updated and notication send off");
      +          *            });
      +          *    });
      +          * 
      + * + * @param {Object} content The content item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the document + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ + sendToPublish: function (content, isNew, files) { + return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#publishByid + * @methodOf umbraco.resources.contentResource + * + * @description + * Publishes a content item with a given ID + * + * ##usage + *
      +          * contentResource.publishById(1234)
      +          *    .then(function(content) {
      +          *        alert("published");
      +          *    });
      +          * 
      + * + * @param {Int} id The ID of the conten to publish + * @returns {Promise} resourcePromise object containing the published content item. + * + */ + publishById: function (id) { + + if (!id) { + throw "id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostPublishById", + [{ id: id }])), + 'Failed to publish content with id ' + id); + + } - /** - * @ngdoc method - * @name umbraco.resources.contentResource#sendToPublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Saves changes made to a content item, and notifies any subscribers about a pending publication - * - * ##usage - *
      -        * contentResource.getById(1234)
      -        *    .then(function(content) {
      -        *          content.name = "I want a new name, and be published!";
      -        *          contentResource.sendToPublish(content, false)
      -        *            .then(function(content){
      -        *                alert("Retrieved, updated and notication send off");
      -        *            });
      -        *    });
      -        * 
      - * - * @param {Object} content The content item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the document - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ - sendToPublish: function (content, isNew, files) { - return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files); - }, - - /** - * @ngdoc method - * @name umbraco.resources.contentResource#publishByid - * @methodOf umbraco.resources.contentResource - * - * @description - * Publishes a content item with a given ID - * - * ##usage - *
      -        * contentResource.publishById(1234)
      -        *    .then(function(content) {
      -        *        alert("published");
      -        *    });
      -        * 
      - * - * @param {Int} id The ID of the conten to publish - * @returns {Promise} resourcePromise object containing the published content item. - * - */ - publishById: function (id) { - - if (!id) { - throw "id cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostPublishById", - [{ id: id }])), - 'Failed to publish content with id ' + id); - - } - - - }; + }; } angular.module('umbraco.resources').factory('contentResource', contentResource); diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 580e1a018c..7abdaf7e69 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -184,17 +184,16 @@ namespace Umbraco.Web.Editors int pageSize = 0, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, - int? orderBySystemField = 2, + bool orderBySystemField = true, string filter = "") - { - var orderBySystemFieldBool = orderBySystemField == 1 || orderBySystemField == 2; + { long totalChildren; IContent[] children; if (pageNumber > 0 && pageSize > 0) { children = Services.ContentService .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren - , orderBy, orderDirection, orderBySystemFieldBool, filter).ToArray(); + , orderBy, orderDirection, orderBySystemField, filter).ToArray(); } else { From b9b3d6299495df157a19c9bb147257ceb492baa4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 6 Apr 2016 09:44:33 +0200 Subject: [PATCH 060/101] fixes boolean isSystem for media --- .../src/common/resources/media.resource.js | 890 +++++++++--------- 1 file changed, 452 insertions(+), 438 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js index a85b2cc104..4b56e55801 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js @@ -5,471 +5,485 @@ **/ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { - /** internal method process the saving of data and post processing the result */ - function saveMediaItem(content, action, files) { - return umbRequestHelper.postSaveContent({ - restApiUrl: umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "PostSave"), - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatMediaPostData(c, a); - } - }); - } + /** internal method process the saving of data and post processing the result */ + function saveMediaItem(content, action, files) { + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "PostSave"), + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatMediaPostData(c, a); + } + }); + } - return { + return { - getRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for media recycle bin'); - }, + getRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRecycleBin")), + 'Failed to retrieve data for media recycle bin'); + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#sort - * @methodOf umbraco.resources.mediaResource - * - * @description - * Sorts all children below a given parent node id, based on a collection of node-ids - * - * ##usage - *
      -        * var ids = [123,34533,2334,23434];
      -        * mediaResource.sort({ sortedIds: ids })
      -        *    .then(function() {
      -        *        $scope.complete = true;
      -        *    });
      -        * 
      - * @param {Object} args arguments object - * @param {Int} args.parentId the ID of the parent node - * @param {Array} options.sortedIds array of node IDs as they should be sorted - * @returns {Promise} resourcePromise object. - * - */ - sort: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.sortedIds) { - throw "args.sortedIds cannot be null"; - } + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#sort + * @methodOf umbraco.resources.mediaResource + * + * @description + * Sorts all children below a given parent node id, based on a collection of node-ids + * + * ##usage + *
      +          * var ids = [123,34533,2334,23434];
      +          * mediaResource.sort({ sortedIds: ids })
      +          *    .then(function() {
      +          *        $scope.complete = true;
      +          *    });
      +          * 
      + * @param {Object} args arguments object + * @param {Int} args.parentId the ID of the parent node + * @param {Array} options.sortedIds array of node IDs as they should be sorted + * @returns {Promise} resourcePromise object. + * + */ + sort: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.sortedIds) { + throw "args.sortedIds cannot be null"; + } - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort media'); - }, + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), + { + parentId: args.parentId, + idSortOrder: args.sortedIds + }), + 'Failed to sort media'); + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#move - * @methodOf umbraco.resources.mediaResource - * - * @description - * Moves a node underneath a new parentId - * - * ##usage - *
      -        * mediaResource.move({ parentId: 1244, id: 123 })
      -        *    .then(function() {
      -        *        alert("node was moved");
      -        *    }, function(err){
      -        *      alert("node didnt move:" + err.data.Message); 
      -        *    });
      -        * 
      - * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move - * @param {Int} args.parentId the ID of the parent node to move to - * @returns {Promise} resourcePromise object. - * - */ - move: function (args) { - if (!args) { - throw "args cannot be null"; - } - if (!args.parentId) { - throw "args.parentId cannot be null"; - } - if (!args.id) { - throw "args.id cannot be null"; - } + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#move + * @methodOf umbraco.resources.mediaResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
      +          * mediaResource.move({ parentId: 1244, id: 123 })
      +          *    .then(function() {
      +          *        alert("node was moved");
      +          *    }, function(err){
      +          *      alert("node didnt move:" + err.data.Message); 
      +          *    });
      +          * 
      + * @param {Object} args arguments object + * @param {Int} args.idd the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move media'); - }, + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + 'Failed to move media'); + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets a media item with a given id - * - * ##usage - *
      -        * mediaResource.getById(1234)
      -        *    .then(function(media) {
      -        *        var myMedia = media; 
      -        *        alert('its here!');
      -        *    });
      -        * 
      - * - * @param {Int} id id of media item to return - * @returns {Promise} resourcePromise object containing the media item. - * - */ - getById: function (id) { + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets a media item with a given id + * + * ##usage + *
      +          * mediaResource.getById(1234)
      +          *    .then(function(media) {
      +          *        var myMedia = media; 
      +          *        alert('its here!');
      +          *    });
      +          * 
      + * + * @param {Int} id id of media item to return + * @returns {Promise} resourcePromise object containing the media item. + * + */ + getById: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetById", - [{ id: id }])), - 'Failed to retrieve data for media id ' + id); - }, + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve data for media id ' + id); + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#deleteById - * @methodOf umbraco.resources.mediaResource - * - * @description - * Deletes a media item with a given id - * - * ##usage - *
      -        * mediaResource.deleteById(1234)
      -        *    .then(function() {
      -        *        alert('its gone!');
      -        *    });
      -        * 
      - * - * @param {Int} id id of media item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteById: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "DeleteById", - [{ id: id }])), - 'Failed to delete item ' + id); - }, + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#deleteById + * @methodOf umbraco.resources.mediaResource + * + * @description + * Deletes a media item with a given id + * + * ##usage + *
      +          * mediaResource.deleteById(1234)
      +          *    .then(function() {
      +          *        alert('its gone!');
      +          *    });
      +          * 
      + * + * @param {Int} id id of media item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteById: function (id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete item ' + id); + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getByIds - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets an array of media items, given a collection of ids - * - * ##usage - *
      -        * mediaResource.getByIds( [1234,2526,28262])
      -        *    .then(function(mediaArray) {
      -        *        var myDoc = contentArray; 
      -        *        alert('they are here!');
      -        *    });
      -        * 
      - * - * @param {Array} ids ids of media items to return as an array - * @returns {Promise} resourcePromise object containing the media items array. - * - */ - getByIds: function (ids) { + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getByIds + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets an array of media items, given a collection of ids + * + * ##usage + *
      +          * mediaResource.getByIds( [1234,2526,28262])
      +          *    .then(function(mediaArray) {
      +          *        var myDoc = contentArray; 
      +          *        alert('they are here!');
      +          *    });
      +          * 
      + * + * @param {Array} ids ids of media items to return as an array + * @returns {Promise} resourcePromise object containing the media items array. + * + */ + getByIds: function (ids) { - var idQuery = ""; - _.each(ids, function (item) { - idQuery += "ids=" + item + "&"; - }); + var idQuery = ""; + _.each(ids, function (item) { + idQuery += "ids=" + item + "&"; + }); - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for media ids ' + ids); - }, + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetByIds", + idQuery)), + 'Failed to retrieve data for media ids ' + ids); + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getScaffold - * @methodOf umbraco.resources.mediaResource - * - * @description - * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. - * - * - Parent Id must be provided so umbraco knows where to store the media - * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold - * - * The scaffold is used to build editors for media that has not yet been populated with data. - * - * ##usage - *
      -        * mediaResource.getScaffold(1234, 'folder')
      -        *    .then(function(scaffold) {
      -        *        var myDoc = scaffold;
      -        *        myDoc.name = "My new media item"; 
      -        *
      -        *        mediaResource.save(myDoc, true)
      -        *            .then(function(media){
      -        *                alert("Retrieved, updated and saved again");
      -        *            });
      -        *    });
      -        * 
      - * - * @param {Int} parentId id of media item to return - * @param {String} alias mediatype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the media scaffold. - * - */ - getScaffold: function (parentId, alias) { + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getScaffold + * @methodOf umbraco.resources.mediaResource + * + * @description + * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias. + * + * - Parent Id must be provided so umbraco knows where to store the media + * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold + * + * The scaffold is used to build editors for media that has not yet been populated with data. + * + * ##usage + *
      +          * mediaResource.getScaffold(1234, 'folder')
      +          *    .then(function(scaffold) {
      +          *        var myDoc = scaffold;
      +          *        myDoc.name = "My new media item"; 
      +          *
      +          *        mediaResource.save(myDoc, true)
      +          *            .then(function(media){
      +          *                alert("Retrieved, updated and saved again");
      +          *            });
      +          *    });
      +          * 
      + * + * @param {Int} parentId id of media item to return + * @param {String} alias mediatype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the media scaffold. + * + */ + getScaffold: function (parentId, alias) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }, { parentId: parentId }])), - 'Failed to retrieve data for empty media item type ' + alias); + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty media item type ' + alias); - }, + }, - rootMedia: function () { + rootMedia: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRootMedia")), - 'Failed to retrieve data for root media'); + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetRootMedia")), + 'Failed to retrieve data for root media'); - }, + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildren - * @methodOf umbraco.resources.mediaResource - * - * @description - * Gets children of a media item with a given id - * - * ##usage - *
      -        * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
      -        *    .then(function(contentArray) {
      -        *        var children = contentArray; 
      -        *        alert('they are here!');
      -        *    });
      -        * 
      - * - * @param {Int} parentid id of content item to return children of - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 - * @param {Int} options.pageNumber if paging data, current page index, default = 0 - * @param {String} options.filter if provided, query will only return those with names matching the filter - * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` - * @param {String} options.orderBy property to order items by, default: `SortOrder` - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ - getChildren: function (parentId, options) { + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildren + * @methodOf umbraco.resources.mediaResource + * + * @description + * Gets children of a media item with a given id + * + * ##usage + *
      +          * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
      +          *    .then(function(contentArray) {
      +          *        var children = contentArray; 
      +          *        alert('they are here!');
      +          *    });
      +          * 
      + * + * @param {Int} parentid id of content item to return children of + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0 + * @param {Int} options.pageNumber if paging data, current page index, default = 0 + * @param {String} options.filter if provided, query will only return those with names matching the filter + * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending` + * @param {String} options.orderBy property to order items by, default: `SortOrder` + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ + getChildren: function (parentId, options) { - var defaults = { - pageSize: 0, - pageNumber: 0, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder", - orderBySystemField: true - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } + var defaults = { + pageSize: 0, + pageNumber: 0, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + orderBySystemField: true + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildren", - [ - { id: parentId }, - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { orderBySystemField: options.orderBySystemField }, - { filter: options.filter } - ])), - 'Failed to retrieve children for media item ' + parentId); - }, + //converts the value to a js bool + function toBool(v) { + if (angular.isNumber(v)) { + return v > 0; + } + if (angular.isString(v)) { + return v === "true"; + } + if (typeof v === "boolean") { + return v; + } + return false; + } - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#save - * @methodOf umbraco.resources.mediaResource - * - * @description - * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation - * if the media item needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
      -        * mediaResource.getById(1234)
      -        *    .then(function(media) {
      -        *          media.name = "I want a new name!";
      -        *          mediaResource.save(media, false)
      -        *            .then(function(media){
      -        *                alert("Retrieved, updated and saved again");
      -        *            });
      -        *    });
      -        * 
      - * - * @param {Object} media The media item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the media item - * @returns {Promise} resourcePromise object containing the saved media item. - * - */ - save: function (media, isNew, files) { - return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); - }, + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildren", + [ + { id: parentId }, + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: toBool(options.orderBySystemField) }, + { filter: options.filter } + ])), + 'Failed to retrieve children for media item ' + parentId); + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#addFolder - * @methodOf umbraco.resources.mediaResource - * - * @description - * Shorthand for adding a media item of the type "Folder" under a given parent ID - * - * ##usage - *
      -        * mediaResource.addFolder("My gallery", 1234)
      -        *    .then(function(folder) {
      -        *        alert('New folder');
      -        *    });
      -        * 
      - * - * @param {string} name Name of the folder to create - * @param {int} parentId Id of the media item to create the folder underneath - * @returns {Promise} resourcePromise object. - * - */ - addFolder: function (name, parentId) { - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper - .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), - { - name: name, - parentId: parentId - }), - 'Failed to add folder'); - }, + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#save + * @methodOf umbraco.resources.mediaResource + * + * @description + * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation + * if the media item needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
      +          * mediaResource.getById(1234)
      +          *    .then(function(media) {
      +          *          media.name = "I want a new name!";
      +          *          mediaResource.save(media, false)
      +          *            .then(function(media){
      +          *                alert("Retrieved, updated and saved again");
      +          *            });
      +          *    });
      +          * 
      + * + * @param {Object} media The media item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the media item + * @returns {Promise} resourcePromise object containing the saved media item. + * + */ + save: function (media, isNew, files) { + return saveMediaItem(media, "save" + (isNew ? "New" : ""), files); + }, - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#getChildFolders - * @methodOf umbraco.resources.mediaResource - * - * @description - * Retrieves all media children with types used as folders. - * Uses the convention of looking for media items with mediaTypes ending in - * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, - * - * ##usage - *
      -        * mediaResource.getChildFolders(1234)
      -        *    .then(function(data) {
      -        *        alert('folders');
      -        *    });
      -        * 
      - * - * @param {int} parentId Id of the media item to query for child folders - * @returns {Promise} resourcePromise object. - * - */ - getChildFolders: function (parentId) { - if (!parentId) { - parentId = -1; - } + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#addFolder + * @methodOf umbraco.resources.mediaResource + * + * @description + * Shorthand for adding a media item of the type "Folder" under a given parent ID + * + * ##usage + *
      +          * mediaResource.addFolder("My gallery", 1234)
      +          *    .then(function(folder) {
      +          *        alert('New folder');
      +          *    });
      +          * 
      + * + * @param {string} name Name of the folder to create + * @param {int} parentId Id of the media item to create the folder underneath + * @returns {Promise} resourcePromise object. + * + */ + addFolder: function (name, parentId) { + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper + .getApiUrl("mediaApiBaseUrl", "PostAddFolder"), + { + name: name, + parentId: parentId + }), + 'Failed to add folder'); + }, - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildFolders", - [ - { id: parentId } - ])), - 'Failed to retrieve child folders for media item ' + parentId); - }, + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#getChildFolders + * @methodOf umbraco.resources.mediaResource + * + * @description + * Retrieves all media children with types used as folders. + * Uses the convention of looking for media items with mediaTypes ending in + * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc, + * + * ##usage + *
      +          * mediaResource.getChildFolders(1234)
      +          *    .then(function(data) {
      +          *        alert('folders');
      +          *    });
      +          * 
      + * + * @param {int} parentId Id of the media item to query for child folders + * @returns {Promise} resourcePromise object. + * + */ + getChildFolders: function (parentId) { + if (!parentId) { + parentId = -1; + } - /** - * @ngdoc method - * @name umbraco.resources.mediaResource#emptyRecycleBin - * @methodOf umbraco.resources.mediaResource - * - * @description - * Empties the media recycle bin - * - * ##usage - *
      -        * mediaResource.emptyRecycleBin()
      -        *    .then(function() {
      -        *        alert('its empty!');
      -        *    });
      -        * 
      - * - * @returns {Promise} resourcePromise object. - * - */ - emptyRecycleBin: function () { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "EmptyRecycleBin")), - 'Failed to empty the recycle bin'); - } - }; + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildFolders", + [ + { id: parentId } + ])), + 'Failed to retrieve child folders for media item ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.mediaResource#emptyRecycleBin + * @methodOf umbraco.resources.mediaResource + * + * @description + * Empties the media recycle bin + * + * ##usage + *
      +          * mediaResource.emptyRecycleBin()
      +          *    .then(function() {
      +          *        alert('its empty!');
      +          *    });
      +          * 
      + * + * @returns {Promise} resourcePromise object. + * + */ + emptyRecycleBin: function () { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); + } + }; } angular.module('umbraco.resources').factory('mediaResource', mediaResource); From 6856540a7cc6f5bd84010fb5a71b72d39a48c52a Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 6 Apr 2016 09:49:53 +0200 Subject: [PATCH 061/101] fixes bool parameter for members --- .../src/common/resources/member.resource.js | 440 +++++++++--------- 1 file changed, 227 insertions(+), 213 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js index 3ec9258f98..2073307db9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/member.resource.js @@ -5,231 +5,245 @@ **/ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) { - /** internal method process the saving of data and post processing the result */ - function saveMember(content, action, files) { + /** internal method process the saving of data and post processing the result */ + function saveMember(content, action, files) { - return umbRequestHelper.postSaveContent({ - restApiUrl: umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "PostSave"), - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatMemberPostData(c, a); - } - }); - } + return umbRequestHelper.postSaveContent({ + restApiUrl: umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "PostSave"), + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatMemberPostData(c, a); + } + }); + } - return { + return { - getPagedResults: function (memberTypeAlias, options) { + getPagedResults: function (memberTypeAlias, options) { - if (memberTypeAlias === 'all-members') { - memberTypeAlias = null; - } + if (memberTypeAlias === 'all-members') { + memberTypeAlias = null; + } - var defaults = { - pageSize: 25, - pageNumber: 1, - filter: '', - orderDirection: "Ascending", - orderBy: "LoginName", - orderBySystemField: true - }; - if (options === undefined) { - options = {}; - } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; - //change asc/desct - if (options.orderDirection === "asc") { - options.orderDirection = "Ascending"; - } - else if (options.orderDirection === "desc") { - options.orderDirection = "Descending"; - } + var defaults = { + pageSize: 25, + pageNumber: 1, + filter: '', + orderDirection: "Ascending", + orderBy: "LoginName", + orderBySystemField: true + }; + if (options === undefined) { + options = {}; + } + //overwrite the defaults if there are any specified + angular.extend(defaults, options); + //now copy back to the options we will use + options = defaults; + //change asc/desct + if (options.orderDirection === "asc") { + options.orderDirection = "Ascending"; + } + else if (options.orderDirection === "desc") { + options.orderDirection = "Descending"; + } - var params = [ - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { orderBySystemField: options.orderBySystemField }, - { filter: options.filter } - ]; - if (memberTypeAlias != null) { - params.push({ memberTypeAlias: memberTypeAlias }); - } + //converts the value to a js bool + function toBool(v) { + if (angular.isNumber(v)) { + return v > 0; + } + if (angular.isString(v)) { + return v === "true"; + } + if (typeof v === "boolean") { + return v; + } + return false; + } - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetPagedResults", - params)), - 'Failed to retrieve member paged result'); - }, + var params = [ + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: toBool(options.orderBySystemField) }, + { filter: options.filter } + ]; + if (memberTypeAlias != null) { + params.push({ memberTypeAlias: memberTypeAlias }); + } - getListNode: function (listName) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetListNodeDisplay", - [{ listName: listName }])), - 'Failed to retrieve data for member list ' + listName); - }, - - /** - * @ngdoc method - * @name umbraco.resources.memberResource#getByKey - * @methodOf umbraco.resources.memberResource - * - * @description - * Gets a member item with a given key - * - * ##usage - *
      -        * memberResource.getByKey("0000-0000-000-00000-000")
      -        *    .then(function(member) {
      -        *        var mymember = member; 
      -        *        alert('its here!');
      -        *    });
      -        * 
      - * - * @param {Guid} key key of member item to return - * @returns {Promise} resourcePromise object containing the member item. - * - */ - getByKey: function (key) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetByKey", - [{ key: key }])), - 'Failed to retrieve data for member id ' + key); - }, - - /** - * @ngdoc method - * @name umbraco.resources.memberResource#deleteByKey - * @methodOf umbraco.resources.memberResource - * - * @description - * Deletes a member item with a given key - * - * ##usage - *
      -        * memberResource.deleteByKey("0000-0000-000-00000-000")
      -        *    .then(function() {
      -        *        alert('its gone!');
      -        *    });
      -        * 
      - * - * @param {Guid} key id of member item to delete - * @returns {Promise} resourcePromise object. - * - */ - deleteByKey: function (key) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "DeleteByKey", - [{ key: key }])), - 'Failed to delete item ' + key); - }, - - /** - * @ngdoc method - * @name umbraco.resources.memberResource#getScaffold - * @methodOf umbraco.resources.memberResource - * - * @description - * Returns a scaffold of an empty member item, given the id of the member item to place it underneath and the member type alias. - * - * - Member Type alias must be provided so umbraco knows which properties to put on the member scaffold - * - * The scaffold is used to build editors for member that has not yet been populated with data. - * - * ##usage - *
      -        * memberResource.getScaffold('client')
      -        *    .then(function(scaffold) {
      -        *        var myDoc = scaffold;
      -        *        myDoc.name = "My new member item"; 
      -        *
      -        *        memberResource.save(myDoc, true)
      -        *            .then(function(member){
      -        *                alert("Retrieved, updated and saved again");
      -        *            });
      -        *    });
      -        * 
      - * - * @param {String} alias membertype alias to base the scaffold on - * @returns {Promise} resourcePromise object containing the member scaffold. - * - */ - getScaffold: function (alias) { - - if (alias) { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetEmpty", - [{ contentTypeAlias: alias }])), - 'Failed to retrieve data for empty member item type ' + alias); - } - else { + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetPagedResults", + params)), + 'Failed to retrieve member paged result'); + }, + + getListNode: function (listName) { + return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "memberApiBaseUrl", - "GetEmpty")), - 'Failed to retrieve data for empty member item type ' + alias); - } + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetListNodeDisplay", + [{ listName: listName }])), + 'Failed to retrieve data for member list ' + listName); + }, - }, + /** + * @ngdoc method + * @name umbraco.resources.memberResource#getByKey + * @methodOf umbraco.resources.memberResource + * + * @description + * Gets a member item with a given key + * + * ##usage + *
      +          * memberResource.getByKey("0000-0000-000-00000-000")
      +          *    .then(function(member) {
      +          *        var mymember = member; 
      +          *        alert('its here!');
      +          *    });
      +          * 
      + * + * @param {Guid} key key of member item to return + * @returns {Promise} resourcePromise object containing the member item. + * + */ + getByKey: function (key) { - /** - * @ngdoc method - * @name umbraco.resources.memberResource#save - * @methodOf umbraco.resources.memberResource - * - * @description - * Saves changes made to a member, if the member is new, the isNew paramater must be passed to force creation - * if the member needs to have files attached, they must be provided as the files param and passed separately - * - * - * ##usage - *
      -        * memberResource.getBykey("23234-sd8djsd-3h8d3j-sdh8d")
      -        *    .then(function(member) {
      -        *          member.name = "Bob";
      -        *          memberResource.save(member, false)
      -        *            .then(function(member){
      -        *                alert("Retrieved, updated and saved again");
      -        *            });
      -        *    });
      -        * 
      - * - * @param {Object} media The member item object with changes applied - * @param {Bool} isNew set to true to create a new item or to update an existing - * @param {Array} files collection of files for the media item - * @returns {Promise} resourcePromise object containing the saved media item. - * - */ - save: function (member, isNew, files) { - return saveMember(member, "save" + (isNew ? "New" : ""), files); - } - }; + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetByKey", + [{ key: key }])), + 'Failed to retrieve data for member id ' + key); + }, + + /** + * @ngdoc method + * @name umbraco.resources.memberResource#deleteByKey + * @methodOf umbraco.resources.memberResource + * + * @description + * Deletes a member item with a given key + * + * ##usage + *
      +          * memberResource.deleteByKey("0000-0000-000-00000-000")
      +          *    .then(function() {
      +          *        alert('its gone!');
      +          *    });
      +          * 
      + * + * @param {Guid} key id of member item to delete + * @returns {Promise} resourcePromise object. + * + */ + deleteByKey: function (key) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "DeleteByKey", + [{ key: key }])), + 'Failed to delete item ' + key); + }, + + /** + * @ngdoc method + * @name umbraco.resources.memberResource#getScaffold + * @methodOf umbraco.resources.memberResource + * + * @description + * Returns a scaffold of an empty member item, given the id of the member item to place it underneath and the member type alias. + * + * - Member Type alias must be provided so umbraco knows which properties to put on the member scaffold + * + * The scaffold is used to build editors for member that has not yet been populated with data. + * + * ##usage + *
      +          * memberResource.getScaffold('client')
      +          *    .then(function(scaffold) {
      +          *        var myDoc = scaffold;
      +          *        myDoc.name = "My new member item"; 
      +          *
      +          *        memberResource.save(myDoc, true)
      +          *            .then(function(member){
      +          *                alert("Retrieved, updated and saved again");
      +          *            });
      +          *    });
      +          * 
      + * + * @param {String} alias membertype alias to base the scaffold on + * @returns {Promise} resourcePromise object containing the member scaffold. + * + */ + getScaffold: function (alias) { + + if (alias) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }])), + 'Failed to retrieve data for empty member item type ' + alias); + } + else { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "memberApiBaseUrl", + "GetEmpty")), + 'Failed to retrieve data for empty member item type ' + alias); + } + + }, + + /** + * @ngdoc method + * @name umbraco.resources.memberResource#save + * @methodOf umbraco.resources.memberResource + * + * @description + * Saves changes made to a member, if the member is new, the isNew paramater must be passed to force creation + * if the member needs to have files attached, they must be provided as the files param and passed separately + * + * + * ##usage + *
      +          * memberResource.getBykey("23234-sd8djsd-3h8d3j-sdh8d")
      +          *    .then(function(member) {
      +          *          member.name = "Bob";
      +          *          memberResource.save(member, false)
      +          *            .then(function(member){
      +          *                alert("Retrieved, updated and saved again");
      +          *            });
      +          *    });
      +          * 
      + * + * @param {Object} media The member item object with changes applied + * @param {Bool} isNew set to true to create a new item or to update an existing + * @param {Array} files collection of files for the media item + * @returns {Promise} resourcePromise object containing the saved media item. + * + */ + save: function (member, isNew, files) { + return saveMember(member, "save" + (isNew ? "New" : ""), files); + } + }; } angular.module('umbraco.resources').factory('memberResource', memberResource); From 75be22a778321415f815a0e1c135f3d97905fcb7 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 6 Apr 2016 12:05:52 +0200 Subject: [PATCH 062/101] Fixes tests, adds null checks --- src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs | 3 ++- src/Umbraco.Web/PublishedContentExtensions.cs | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index 476c37d85c..aea5243058 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -74,9 +74,10 @@ namespace Umbraco.Tests.UmbracoExamine mediaService = Mock.Of( x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()) + It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()) == allRecs); + } if (dataTypeService == null) { diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 197175366e..578aab2755 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -1201,6 +1201,7 @@ namespace Umbraco.Web /// Enumerates bottom-up ie walking up the tree (parent, grand-parent, etc). internal static IEnumerable EnumerateAncestors(this IPublishedContent content, bool orSelf) { + if (content == null) throw new ArgumentNullException("content"); if (orSelf) yield return content; while ((content = content.Parent) != null) yield return content; @@ -1373,6 +1374,7 @@ namespace Umbraco.Web internal static IEnumerable EnumerateDescendants(this IPublishedContent content, bool orSelf) { + if (content == null) throw new ArgumentNullException("content"); if (orSelf) yield return content; foreach (var child in content.Children) @@ -1707,6 +1709,7 @@ namespace Umbraco.Web public static T Parent(this IPublishedContent content) where T : class, IPublishedContent { + if (content == null) throw new ArgumentNullException("content"); return content.Parent as T; } @@ -1724,9 +1727,10 @@ namespace Umbraco.Web /// This method exists for consistency, it is the same as calling content.Children as a property. /// public static IEnumerable Children(this IPublishedContent content) - { - return content.Children; - } + { + if (content == null) throw new ArgumentNullException("content"); + return content.Children; + } /// /// Gets the children of the content, filtered by a predicate. From e2954fbf39b3f5350ce8349e81118aea1760c9f4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 6 Apr 2016 13:23:39 +0200 Subject: [PATCH 063/101] refactors interface methods so they are not breaking existing signatures and not overlapping based on optional parameters --- src/Umbraco.Core/Services/ContentService.cs | 37 ++++++++++++++++++-- src/Umbraco.Core/Services/IContentService.cs | 32 +++++++++++++++-- src/Umbraco.Core/Services/IMediaService.cs | 32 +++++++++++++++-- src/Umbraco.Core/Services/IMemberService.cs | 17 ++++++++- src/Umbraco.Core/Services/MediaService.cs | 37 ++++++++++++++++++-- src/Umbraco.Core/Services/MemberService.cs | 8 ++++- src/UmbracoExamine/UmbracoMemberIndexer.cs | 3 +- 7 files changed, 154 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 2d08632905..bbae07eb6a 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -493,6 +493,23 @@ namespace Umbraco.Core.Services return result; } + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page index (zero based) + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, + string orderBy, Direction orderDirection, string filter = "") + { + return GetPagedChildren(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter); + } + /// /// Gets a collection of objects by Parent Id /// @@ -506,7 +523,7 @@ namespace Umbraco.Core.Services /// Search text filter /// An Enumerable list of objects public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField, string filter) { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); @@ -535,6 +552,22 @@ namespace Umbraco.Core.Services return result; } + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + { + return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter); + } + /// /// Gets a collection of objects by Parent Id /// @@ -547,7 +580,7 @@ namespace Umbraco.Core.Services /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") + 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"); diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 75a412e683..df70fa8094 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -205,6 +205,20 @@ namespace Umbraco.Core.Services IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + /// /// Gets a collection of objects by Parent Id /// @@ -218,13 +232,27 @@ namespace Umbraco.Core.Services /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + string orderBy, Direction orderDirection, bool orderBySystemField, string filter); [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + /// /// Gets a collection of objects by Parent Id /// @@ -238,7 +266,7 @@ namespace Umbraco.Core.Services /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + string orderBy, Direction orderDirection, bool orderBySystemField, string filter); /// /// Gets a collection of an objects versions by its Id diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index 8359b487b1..6ff8f75402 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -123,6 +123,20 @@ namespace Umbraco.Core.Services IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + /// /// Gets a collection of objects by Parent Id /// @@ -136,13 +150,27 @@ namespace Umbraco.Core.Services /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + string orderBy, Direction orderDirection, bool orderBySystemField, string filter); [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + /// /// Gets a collection of objects by Parent Id /// @@ -156,7 +184,7 @@ namespace Umbraco.Core.Services /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = ""); + string orderBy, Direction orderDirection, bool orderBySystemField, string filter); /// /// Gets descendants of a object by its Id diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index ee37d983aa..a893d89feb 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -27,6 +27,21 @@ namespace Umbraco.Core.Services IEnumerable GetAll(int pageIndex, int pageSize, out int totalRecords, string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = ""); + /// + /// Gets a list of paged objects + /// + /// An can be of type + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// Field to order by + /// Direction to order by + /// + /// Search text filter + /// + IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = ""); + /// /// Gets a list of paged objects /// @@ -41,7 +56,7 @@ namespace Umbraco.Core.Services /// Search text filter /// IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = ""); + string orderBy, Direction orderDirection, bool orderBySystemField, string memberTypeAlias, string filter); /// /// Creates an object without persisting it diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index d73ee52c2e..34d130d725 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -403,6 +403,23 @@ namespace Umbraco.Core.Services return result; } + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page index (zero based) + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, + string orderBy, Direction orderDirection, string filter = "") + { + return GetPagedChildren(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter); + } + /// /// Gets a collection of objects by Parent Id /// @@ -416,7 +433,7 @@ namespace Umbraco.Core.Services /// Search text filter /// An Enumerable list of objects public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - string orderBy, Direction orderDirection, bool orderBySystemField = true, string filter = "") + string orderBy, Direction orderDirection, bool orderBySystemField, string filter) { Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); Mandate.ParameterCondition(pageSize > 0, "pageSize"); @@ -441,6 +458,22 @@ namespace Umbraco.Core.Services return result; } + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Descendants from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + { + return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter); + } + /// /// Gets a collection of objects by Parent Id /// @@ -453,7 +486,7 @@ namespace Umbraco.Core.Services /// Flag to indicate when ordering by system field /// Search text filter /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") + 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"); diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 93d7a1a89f..094539d66e 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -704,7 +704,13 @@ namespace Umbraco.Core.Services } public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField = true, string memberTypeAlias = null, string filter = "") + string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = "") + { + return GetAll(pageIndex, pageSize, out totalRecords, orderBy, orderDirection, true, memberTypeAlias, filter); + } + + public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, string memberTypeAlias, string filter) { var uow = UowProvider.GetUnitOfWork(); using (var repository = RepositoryFactory.CreateMemberRepository(uow)) diff --git a/src/UmbracoExamine/UmbracoMemberIndexer.cs b/src/UmbracoExamine/UmbracoMemberIndexer.cs index 4a460e0f38..64a574822f 100644 --- a/src/UmbracoExamine/UmbracoMemberIndexer.cs +++ b/src/UmbracoExamine/UmbracoMemberIndexer.cs @@ -142,8 +142,7 @@ namespace UmbracoExamine do { long total; - members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName" - , Direction.Ascending, true, nodeType).ToArray(); + members = _memberService.GetAll(pageIndex, pageSize, out total, "LoginName", Direction.Ascending, true, null, nodeType).ToArray(); AddNodesToIndex(GetSerializedMembers(members), type); From 2d6b8712d5c220b3bdeb71e1cd52871ca9095df8 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Wed, 6 Apr 2016 19:44:09 +0200 Subject: [PATCH 064/101] Limit this to listview, so it doesn't affect other property editors. --- src/Umbraco.Web.UI.Client/src/less/listview.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/listview.less b/src/Umbraco.Web.UI.Client/src/less/listview.less index 87d5f9a5e2..88762edf0c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/listview.less +++ b/src/Umbraco.Web.UI.Client/src/less/listview.less @@ -244,7 +244,7 @@ } /* don't hide all icons, e.g. for a sortable handle */ -.table-striped tbody i:not(.handle):hover { +.umb-listview .table-striped tbody i:not(.handle):hover { display: none !important } From 2c29e422bc17d284022061533177221fa049db2d Mon Sep 17 00:00:00 2001 From: bjarnef Date: Wed, 6 Apr 2016 19:47:05 +0200 Subject: [PATCH 065/101] Add icons for internal links. --- .../relatedlinks/relatedlinks.controller.js | 8 ++++++-- .../views/propertyeditors/relatedlinks/relatedlinks.html | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js index 01b465daba..cb6c8e595a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js @@ -1,6 +1,6 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.RelatedLinksController", - function ($rootScope, $scope, dialogService) { + function ($rootScope, $scope, dialogService, iconHelper) { if (!$scope.model.value) { $scope.model.value = []; @@ -13,6 +13,7 @@ $scope.newNewWindow = false; $scope.newInternal = null; $scope.newInternalName = ''; + $scope.newInternalIcon = null; $scope.addExternal = true; $scope.currentEditLink = null; $scope.hasError = false; @@ -108,6 +109,7 @@ this.edit = false; this.isInternal = true; this.internalName = $scope.newInternalName; + this.internalIcon = $scope.newInternalIcon; this.type = "internal"; this.title = $scope.newCaption; }; @@ -118,7 +120,7 @@ $scope.newNewWindow = false; $scope.newInternal = null; $scope.newInternalName = ''; - + $scope.newInternalIcon = null; } $event.preventDefault(); }; @@ -223,10 +225,12 @@ if ($scope.currentEditLink != null) { $scope.currentEditLink.internal = data.id; $scope.currentEditLink.internalName = data.name; + $scope.currentEditLink.internalIcon = iconHelper.convertFromLegacyIcon(data.icon); $scope.currentEditLink.link = data.id; } else { $scope.newInternal = data.id; $scope.newInternalName = data.name; + $scope.newInternalIcon = iconHelper.convertFromLegacyIcon(data.icon); } } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html index 17358e0628..e3f74c6e21 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html @@ -21,6 +21,7 @@ @@ -33,6 +34,7 @@ @@ -71,6 +73,7 @@ From ae154885e4ee7206f9232acf5f7e74d118e17e79 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Wed, 6 Apr 2016 19:52:21 +0200 Subject: [PATCH 066/101] Ensure icon has a value. --- .../views/propertyeditors/relatedlinks/relatedlinks.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html index e3f74c6e21..e7d3b67de3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html @@ -21,7 +21,7 @@ @@ -34,7 +34,7 @@ @@ -73,7 +73,7 @@ From 1e5b6a6a0f16887291a4e5228229f0e6913c7072 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 7 Apr 2016 15:42:51 +0200 Subject: [PATCH 067/101] Fixes: U4-8298 CacheRefresher authorization logic reversed - traditional load balancing will not work --- .../webservices/CacheRefresher.asmx.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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 a58a81dfc7..2cda4a0593 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs @@ -60,7 +60,7 @@ namespace umbraco.presentation.webservices return jsonRefresher; } - private bool NotAutorized(string login, string rawPassword) + private bool Autorized(string login, string rawPassword) { //TODO: This technique of passing the raw password in is a legacy idea and isn't really // a very happy way to secure this webservice. To prevent brute force attacks, we need @@ -92,7 +92,7 @@ namespace umbraco.presentation.webservices [WebMethod] public void BulkRefresh(RefreshInstruction[] instructions, string appId, string login, string password) { - if (NotAutorized(login, password)) return; + if (Autorized(login, password) == false) return; if (SelfMessage(appId)) return; // do not process self-messages // only execute distinct instructions - no sense in running the same one more than once @@ -131,29 +131,29 @@ namespace umbraco.presentation.webservices [WebMethod] public void RefreshAll(Guid uniqueIdentifier, string Login, string Password) { - if (NotAutorized(Login, Password)) return; + if (Autorized(Login, Password) == false) return; GetRefresher(uniqueIdentifier).RefreshAll(); } [WebMethod] public void RefreshByGuid(Guid uniqueIdentifier, Guid Id, string Login, string Password) { - if (NotAutorized(Login, Password)) return; + if (Autorized(Login, Password) == false) return; GetRefresher(uniqueIdentifier).Refresh(Id); } [WebMethod] public void RefreshById(Guid uniqueIdentifier, int Id, string Login, string Password) { - if (NotAutorized(Login, Password)) return; + if (Autorized(Login, Password) == false) return; GetRefresher(uniqueIdentifier).Refresh(Id); } [WebMethod] public void RefreshByIds(Guid uniqueIdentifier, string jsonIds, string Login, string Password) { - if (NotAutorized(Login, Password)) return; - var refresher = GetRefresher(uniqueIdentifier); + if (Autorized(Login, Password) == false) return; + var refresher = GetRefresher(uniqueIdentifier); foreach (var id in JsonConvert.DeserializeObject(jsonIds)) refresher.Refresh(id); } @@ -161,21 +161,21 @@ namespace umbraco.presentation.webservices [WebMethod] public void RefreshByJson(Guid uniqueIdentifier, string jsonPayload, string Login, string Password) { - if (NotAutorized(Login, Password)) return; + if (Autorized(Login, Password) == false) return; GetJsonRefresher(uniqueIdentifier).Refresh(jsonPayload); } [WebMethod] public void RemoveById(Guid uniqueIdentifier, int Id, string Login, string Password) { - if (NotAutorized(Login, Password)) return; + if (Autorized(Login, Password) == false) return; GetRefresher(uniqueIdentifier).Remove(Id); } [WebMethod] public XmlDocument GetRefreshers(string Login, string Password) { - if (NotAutorized(Login, Password)) return null; + if (Autorized(Login, Password) == false) return null; var xd = new XmlDocument(); xd.LoadXml(""); From f87bec398d1fa3d58d200d9b866458bd49badf7b Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 7 Apr 2016 16:44:45 +0200 Subject: [PATCH 068/101] U4-8239 DisableBrowserCacheAttribute causing YSOD when a previous exceptions is thrown before the filter kicks in --- src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs | 7 +++++++ .../WebApi/Filters/DisableBrowserCacheAttribute.cs | 6 +++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs b/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs index c1b5b88643..c1c2186171 100644 --- a/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs +++ b/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs @@ -13,6 +13,13 @@ namespace Umbraco.Web.Mvc { base.OnActionExecuted(filterContext); + // could happens if exception (but afaik this wouldn't happen in MVC) + if (filterContext.HttpContext == null || filterContext.HttpContext.Response == null || + filterContext.HttpContext.Response.Cache == null) + { + return; + } + filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache); filterContext.HttpContext.Response.Cache.SetMaxAge(TimeSpan.Zero); filterContext.HttpContext.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches); diff --git a/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs b/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs index 0bb283bd1a..3deddde831 100644 --- a/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs @@ -16,7 +16,11 @@ namespace Umbraco.Web.WebApi.Filters base.OnActionExecuted(actionExecutedContext); // happens if exception - if (actionExecutedContext.Response == null) return; + if (actionExecutedContext == null || actionExecutedContext.Response == null || + actionExecutedContext.Response.Headers == null) + { + return; + } //NOTE: Until we upgraded to WebApi 2, this didn't work correctly and we had to revert to using // HttpContext.Current responses. I've changed this back to what it should be now since it works From 506ed9f866deeca3d0c74fdb0adb91e97daea40e Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 7 Apr 2016 17:51:09 +0200 Subject: [PATCH 069/101] U4-8286 Add OWIN startup events to the UmbracoDefaultOwinStartup class --- src/Umbraco.Core/UmbracoApplicationBase.cs | 5 +++++ .../OwinMiddlewareConfiguredEventArgs.cs | 15 +++++++++++++ .../Security/Identity/AppBuilderExtensions.cs | 21 +++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + src/Umbraco.Web/UmbracoDefaultOwinStartup.cs | 17 +++++++++++++-- 5 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Web/OwinMiddlewareConfiguredEventArgs.cs diff --git a/src/Umbraco.Core/UmbracoApplicationBase.cs b/src/Umbraco.Core/UmbracoApplicationBase.cs index 225493b57b..bb81c79f2a 100644 --- a/src/Umbraco.Core/UmbracoApplicationBase.cs +++ b/src/Umbraco.Core/UmbracoApplicationBase.cs @@ -72,6 +72,11 @@ namespace Umbraco.Core /// /// Override init and raise the event /// + /// + /// DID YOU KNOW? The Global.asax Init call is the thing that initializes all of the httpmodules, ties up a bunch of stuff with IIS, etc... + /// Therefore, since OWIN is an HttpModule when running in IIS/ASP.Net the OWIN startup is not executed until this method fires and by that + /// time, Umbraco has performed it's bootup sequence. + /// public override void Init() { base.Init(); diff --git a/src/Umbraco.Web/OwinMiddlewareConfiguredEventArgs.cs b/src/Umbraco.Web/OwinMiddlewareConfiguredEventArgs.cs new file mode 100644 index 0000000000..03c2dc631d --- /dev/null +++ b/src/Umbraco.Web/OwinMiddlewareConfiguredEventArgs.cs @@ -0,0 +1,15 @@ +using System; +using Owin; + +namespace Umbraco.Web +{ + public class OwinMiddlewareConfiguredEventArgs : EventArgs + { + public OwinMiddlewareConfiguredEventArgs(IAppBuilder appBuilder) + { + AppBuilder = appBuilder; + } + + public IAppBuilder AppBuilder { get; private set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs index 3097407de3..be4c8923d7 100644 --- a/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs +++ b/src/Umbraco.Web/Security/Identity/AppBuilderExtensions.cs @@ -24,10 +24,31 @@ namespace Umbraco.Web.Security.Identity { public static class AppBuilderExtensions { + /// + /// Called at the end of configuring middleware + /// + /// + /// + /// This could be used for something else in the future - maybe to inform Umbraco that middleware is done/ready, but for + /// now this is used to raise the custom event + /// + /// This is an extension method in case developer entirely replace the UmbracoDefaultOwinStartup class, in which case they will + /// need to ensure they call this extension method in their startup class. + /// + /// TODO: Move this method in v8, it doesn't belong in this namespace/extension class + /// + public static void FinalizeMiddlewareConfiguration(this IAppBuilder app) + { + UmbracoDefaultOwinStartup.OnMiddlewareConfigured(new OwinMiddlewareConfiguredEventArgs(app)); + } + /// /// Sets the OWIN logger to use Umbraco's logging system /// /// + /// + /// TODO: Move this method in v8, it doesn't belong in this namespace/extension class + /// public static void SetUmbracoLoggerFactory(this IAppBuilder app) { app.SetLoggerFactory(new OwinLoggerFactory()); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 35dc036fd3..3aeffcfe16 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -349,6 +349,7 @@ + diff --git a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs index 5b3fadd0f3..046b2167d5 100644 --- a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs +++ b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs @@ -1,4 +1,5 @@ -using System.Web; +using System; +using System.Web; using Microsoft.Owin; using Microsoft.Owin.Extensions; using Microsoft.Owin.Logging; @@ -59,12 +60,24 @@ namespace Umbraco.Web app .UseUmbracoBackOfficeCookieAuthentication(ApplicationContext, PipelineStage.Authenticate) .UseUmbracoBackOfficeExternalCookieAuthentication(ApplicationContext, PipelineStage.Authenticate) - .UseUmbracoPreviewAuthentication(ApplicationContext, PipelineStage.Authorize); + .UseUmbracoPreviewAuthentication(ApplicationContext, PipelineStage.Authorize) + .FinalizeMiddlewareConfiguration(); } + /// + /// Raised when the middelware has been configured + /// + public static event EventHandler MiddlewareConfigured; + protected virtual ApplicationContext ApplicationContext { get { return ApplicationContext.Current; } } + + internal static void OnMiddlewareConfigured(OwinMiddlewareConfiguredEventArgs args) + { + var handler = MiddlewareConfigured; + if (handler != null) handler(null, args); + } } } From 36255709e61525b25f213eae86aa559f8e3b5b14 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 7 Apr 2016 18:16:03 +0200 Subject: [PATCH 070/101] U4-8297 Custom MembershipProvider search doesn't work in the listview --- src/Umbraco.Web/Editors/MemberController.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 909189b7ad..377647d3ba 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -103,7 +103,24 @@ namespace Umbraco.Web.Editors else { int totalRecords; - var members = _provider.GetAllUsers((pageNumber - 1), pageSize, out totalRecords); + + MembershipUserCollection members; + if (filter.IsNullOrWhiteSpace()) + { + members = _provider.GetAllUsers((pageNumber - 1), pageSize, out totalRecords); + } + else + { + //we need to search! + + //try by name first + members = _provider.FindUsersByName(filter, (pageNumber - 1), pageSize, out totalRecords); + if (totalRecords == 0) + { + //try by email then + members = _provider.FindUsersByEmail(filter, (pageNumber - 1), pageSize, out totalRecords); + } + } if (totalRecords == 0) { return new PagedResult(0, 0, 0); From 20347c1ccd9665fe0fa775a283d6ebbd332f860d Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 12 Apr 2016 13:17:41 +0200 Subject: [PATCH 071/101] Fixing typo. --- .../umbraco/webservices/CacheRefresher.asmx.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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 2cda4a0593..cb6bd451b0 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/CacheRefresher.asmx.cs @@ -60,7 +60,7 @@ namespace umbraco.presentation.webservices return jsonRefresher; } - private bool Autorized(string login, string rawPassword) + private bool Authorized(string login, string rawPassword) { //TODO: This technique of passing the raw password in is a legacy idea and isn't really // a very happy way to secure this webservice. To prevent brute force attacks, we need @@ -92,7 +92,7 @@ namespace umbraco.presentation.webservices [WebMethod] public void BulkRefresh(RefreshInstruction[] instructions, string appId, string login, string password) { - if (Autorized(login, password) == false) return; + if (Authorized(login, password) == false) return; if (SelfMessage(appId)) return; // do not process self-messages // only execute distinct instructions - no sense in running the same one more than once @@ -131,28 +131,28 @@ namespace umbraco.presentation.webservices [WebMethod] public void RefreshAll(Guid uniqueIdentifier, string Login, string Password) { - if (Autorized(Login, Password) == false) return; + if (Authorized(Login, Password) == false) return; GetRefresher(uniqueIdentifier).RefreshAll(); } [WebMethod] public void RefreshByGuid(Guid uniqueIdentifier, Guid Id, string Login, string Password) { - if (Autorized(Login, Password) == false) return; + if (Authorized(Login, Password) == false) return; GetRefresher(uniqueIdentifier).Refresh(Id); } [WebMethod] public void RefreshById(Guid uniqueIdentifier, int Id, string Login, string Password) { - if (Autorized(Login, Password) == false) return; + if (Authorized(Login, Password) == false) return; GetRefresher(uniqueIdentifier).Refresh(Id); } [WebMethod] public void RefreshByIds(Guid uniqueIdentifier, string jsonIds, string Login, string Password) { - if (Autorized(Login, Password) == false) return; + if (Authorized(Login, Password) == false) return; var refresher = GetRefresher(uniqueIdentifier); foreach (var id in JsonConvert.DeserializeObject(jsonIds)) refresher.Refresh(id); @@ -161,21 +161,21 @@ namespace umbraco.presentation.webservices [WebMethod] public void RefreshByJson(Guid uniqueIdentifier, string jsonPayload, string Login, string Password) { - if (Autorized(Login, Password) == false) return; + if (Authorized(Login, Password) == false) return; GetJsonRefresher(uniqueIdentifier).Refresh(jsonPayload); } [WebMethod] public void RemoveById(Guid uniqueIdentifier, int Id, string Login, string Password) { - if (Autorized(Login, Password) == false) return; + if (Authorized(Login, Password) == false) return; GetRefresher(uniqueIdentifier).Remove(Id); } [WebMethod] public XmlDocument GetRefreshers(string Login, string Password) { - if (Autorized(Login, Password) == false) return null; + if (Authorized(Login, Password) == false) return null; var xd = new XmlDocument(); xd.LoadXml(""); From 365a01a476dac2fab732219f8bdcbe407bf4851b Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 12 Apr 2016 15:11:07 +0200 Subject: [PATCH 072/101] From PetaPoco to NPoco (#1207) * NPoco - 2.x (builds) * NPoco - v3.1 (does not build) * NPoco - builds * NPoco - configure database factory (tests fail) * Pick fix from 7.4 * NPoco - stock v3.1 - sort-of working * NPoco - fix merge * Fix Newtonsoft.Json in web.Template.Debug.config * NPoco - fix SELECT * * NPoco - fixing repositories * NPoco - fix EntityRepository * NPoco - fix EntityRepository * NPoco - cosmetic * NPoco - use 3.1.0-u001 from github/zpqrtbnk/NPoco * Fixes build, NPoco needed to be referenced in the cms and UmbracoExamine projects * Fixes lots of tests * fixes more tests * NPoco - bugfixing * Bugfix CacheHelper in tests * Bugfix connection mocking in tests * NPoco - inject database in Sql.Select<> * NPoco - discovery retry policy only once * Enable C# 6 for Umbraco.Core * NPoco - introduce UmbracoSql, cleanup * NPoco - more cleanup and fixing * NPoco - fix UserRepository * Optimize InGroupsOf * Implement UmbracoDatabase.FetchByGroups * NPoco - fix Select * NPoco - simplify GetPagedResultsByQuery * Cherry-pick DisableBrowserCacheAttribute fix from 7.4 * Upgrade NPoco to use Sql * U4-8257 - cleanup relators * 4-8257 - cleanup more relators * Upgrade NPoco with more OOTB version * fixes a couple tests, changes double check lock to Lazy --- src/Umbraco.Core/CoreBootManager.cs | 67 +- src/Umbraco.Core/DatabaseContext.cs | 6 + .../RepositoryCompositionRoot.cs | 8 +- src/Umbraco.Core/EnumerableExtensions.cs | 54 +- src/Umbraco.Core/ListExtensions.cs | 50 + src/Umbraco.Core/Models/Rdbms/AccessDto.cs | 4 +- .../Models/Rdbms/AccessRuleDto.cs | 3 +- .../Models/Rdbms/CacheInstructionDto.cs | 1 + src/Umbraco.Core/Models/Rdbms/ContentDto.cs | 4 +- .../Rdbms/ContentType2ContentTypeDto.cs | 3 +- .../Rdbms/ContentTypeAllowedContentTypeDto.cs | 6 +- .../Models/Rdbms/ContentTypeDto.cs | 3 +- .../Models/Rdbms/ContentTypeTemplateDto.cs | 6 +- .../Models/Rdbms/ContentVersionDto.cs | 2 + .../Models/Rdbms/ContentXmlDto.cs | 3 +- src/Umbraco.Core/Models/Rdbms/DataTypeDto.cs | 2 + .../Models/Rdbms/DataTypePreValueDto.cs | 3 +- .../Models/Rdbms/DictionaryDto.cs | 2 + src/Umbraco.Core/Models/Rdbms/DocumentDto.cs | 7 +- .../Rdbms/DocumentPublishedReadOnlyDto.cs | 3 +- src/Umbraco.Core/Models/Rdbms/DomainDto.cs | 3 +- .../Models/Rdbms/ExternalLoginDto.cs | 1 + src/Umbraco.Core/Models/Rdbms/LanguageDto.cs | 3 +- .../Models/Rdbms/LanguageTextDto.cs | 1 + src/Umbraco.Core/Models/Rdbms/LogDto.cs | 1 + src/Umbraco.Core/Models/Rdbms/MacroDto.cs | 2 + .../Models/Rdbms/MacroPropertyDto.cs | 7 +- .../Models/Rdbms/Member2MemberGroupDto.cs | 5 +- src/Umbraco.Core/Models/Rdbms/MemberDto.cs | 6 +- .../Models/Rdbms/MemberTypeDto.cs | 3 +- .../Models/Rdbms/MemberTypeReadOnlyDto.cs | 3 + src/Umbraco.Core/Models/Rdbms/MigrationDto.cs | 1 + src/Umbraco.Core/Models/Rdbms/NodeDto.cs | 1 + .../Models/Rdbms/PreviewXmlDto.cs | 3 +- .../Models/Rdbms/PropertyDataDto.cs | 6 +- .../Models/Rdbms/PropertyTypeDto.cs | 2 + .../Models/Rdbms/PropertyTypeGroupDto.cs | 4 +- .../Rdbms/PropertyTypeGroupReadOnlyDto.cs | 5 +- .../Models/Rdbms/PropertyTypeReadOnlyDto.cs | 1 + src/Umbraco.Core/Models/Rdbms/RelationDto.cs | 1 + .../Models/Rdbms/RelationTypeDto.cs | 1 + .../Models/Rdbms/ServerRegistrationDto.cs | 1 + .../Models/Rdbms/StylesheetDto.cs | 5 +- .../Models/Rdbms/StylesheetPropertyDto.cs | 5 +- src/Umbraco.Core/Models/Rdbms/TagDto.cs | 3 +- .../Models/Rdbms/TagRelationshipDto.cs | 5 +- src/Umbraco.Core/Models/Rdbms/TaskDto.cs | 2 + src/Umbraco.Core/Models/Rdbms/TaskTypeDto.cs | 3 +- src/Umbraco.Core/Models/Rdbms/TemplateDto.cs | 4 +- .../Models/Rdbms/UmbracoDeployChecksumDto.cs | 1 + .../Rdbms/UmbracoDeployDependencyDto.cs | 1 + src/Umbraco.Core/Models/Rdbms/User2AppDto.cs | 5 +- .../Models/Rdbms/User2NodeNotifyDto.cs | 5 +- .../Models/Rdbms/User2NodePermissionDto.cs | 5 +- src/Umbraco.Core/Models/Rdbms/UserDto.cs | 4 +- src/Umbraco.Core/Models/Rdbms/UserTypeDto.cs | 3 +- .../DefinitionFactory.cs | 5 +- .../Persistence/DatabaseSchemaHelper.cs | 1 + .../Persistence/DatabasenodeLockExtensions.cs | 2 +- .../Persistence/DefaultDatabaseFactory.cs | 111 +- .../Persistence/Factories/MacroFactory.cs | 2 +- .../Factories/UmbracoEntityFactory.cs | 17 +- .../Persistence/Factories/UserFactory.cs | 2 +- .../FaultHandling/RetryDbConnection.cs | 222 ++ .../Persistence/LockedRepository.cs | 5 +- .../Persistence/Mappers/BaseMapper.cs | 1 + .../Persistence/Mappers/MappingResolver.cs | 8 +- .../Persistence/Mappers/PetaPocoMapper.cs | 45 - .../Persistence/Mappers/PocoMapper.cs | 31 + .../Migrations/IMigrationContext.cs | 1 + .../Migrations/IMigrationExpression.cs | 4 +- .../Migrations/Initial/BaseDataCreation.cs | 1 + .../Initial/DatabaseSchemaCreation.cs | 1 + .../Persistence/Migrations/MigrationBase.cs | 6 + .../Migrations/MigrationContext.cs | 1 + .../Migrations/MigrationExpressionBase.cs | 1 + .../Persistence/Migrations/MigrationRunner.cs | 19 +- .../Syntax/Execute/ExecuteBuilder.cs | 1 + .../ExecuteCodeStatementExpression.cs | 1 + .../Syntax/Execute/IExecuteBuilder.cs | 1 + .../Expressions/RenameColumnExpression.cs | 3 +- .../UpdateRelatedLinksData.cs | 5 +- .../AddUniqueIdPropertyTypeGroupColumn.cs | 1 + ...EnsureContentTypeUniqueIdsAreConsistent.cs | 11 +- .../FixListViewMediaSortOrder.cs | 3 +- .../UpdateUserLanguagesToIsoCode.cs | 3 +- .../EnsureMigrationsTableIdentityIsCorrect.cs | 3 +- ...reignKeysForLanguageAndDictionaryTables.cs | 3 +- .../MigrateAndRemoveTemplateMasterColumn.cs | 3 +- .../MovePublicAccessXmlDataToDb.cs | 3 +- .../AddChangeDocumentTypePermission.cs | 5 +- .../UpdateToNewMemberPropertyAliases.cs | 1 + .../UpdatePropertyTypesAndGroups.cs | 1 + ...tensions.cs => NPocoDatabaseExtensions.cs} | 666 ++--- src/Umbraco.Core/Persistence/PetaPoco.cs | 2504 ----------------- .../Persistence/PetaPocoCommandExtensions.cs | 277 -- .../PetaPocoConnectionExtensions.cs | 35 - .../Persistence/PetaPocoSqlExtensions.cs | 143 - .../Querying/BaseExpressionHelper.cs | 10 +- .../Querying/PocoToSqlExpressionHelper.cs | 44 +- .../Persistence/Querying/SqlTranslator.cs | 7 +- .../Relators/AccessRulesRelator.cs | 51 - .../Relators/DictionaryLanguageTextRelator.cs | 43 - .../Relators/GroupPropertyTypeRelator.cs | 48 - .../Relators/MacroPropertyRelator.cs | 89 - .../PropertyTypePropertyGroupRelator.cs | 63 - .../Relators/UserSectionRelator.cs | 50 - .../Repositories/AuditRepository.cs | 17 +- .../Repositories/ContentPreviewRepository.cs | 5 +- .../Repositories/ContentRepository.cs | 195 +- .../Repositories/ContentTypeBaseRepository.cs | 121 +- .../Repositories/ContentTypeRepository.cs | 64 +- .../Repositories/ContentXmlRepository.cs | 5 +- .../DataTypeDefinitionRepository.cs | 68 +- .../Repositories/DictionaryRepository.cs | 60 +- .../Repositories/DomainRepository.cs | 15 +- .../Repositories/EntityContainerRepository.cs | 60 +- .../Repositories/EntityRepository.cs | 318 +-- .../Repositories/ExternalLoginRepository.cs | 16 +- .../Repositories/LanguageRepository.cs | 26 +- .../Repositories/MacroRepository.cs | 56 +- .../Repositories/MediaRepository.cs | 129 +- .../Repositories/MediaTypeRepository.cs | 24 +- .../Repositories/MemberGroupRepository.cs | 153 +- .../Repositories/MemberRepository.cs | 205 +- .../Repositories/MemberTypeRepository.cs | 131 +- .../Repositories/MigrationEntryRepository.cs | 23 +- ...positoryBase.cs => NPocoRepositoryBase.cs} | 41 +- .../Repositories/NotificationsRepository.cs | 31 +- .../Repositories/PermissionRepository.cs | 18 +- .../Repositories/PublicAccessRepository.cs | 24 +- .../Repositories/RecycleBinRepository.cs | 17 +- .../Repositories/RelationRepository.cs | 19 +- .../Repositories/RelationTypeRepository.cs | 19 +- .../ServerRegistrationRepository.cs | 17 +- .../Repositories/SimpleGetRepository.cs | 7 +- .../Persistence/Repositories/TagRepository.cs | 191 +- .../Repositories/TaskRepository.cs | 53 +- .../Repositories/TaskTypeRepository.cs | 9 +- .../Repositories/TemplateRepository.cs | 74 +- .../Repositories/TupleExtensions.cs | 19 + .../Repositories/UserRepository.cs | 108 +- .../Repositories/UserTypeRepository.cs | 23 +- .../Repositories/VersionableRepositoryBase.cs | 418 ++- src/Umbraco.Core/Persistence/SqlContext.cs | 36 + .../SqlSyntax/ISqlSyntaxProvider.cs | 1 + .../SqlSyntax/MySqlSyntaxProvider.cs | 3 +- .../SqlSyntax/SqlCeSyntaxProvider.cs | 1 + .../SqlSyntax/SqlServerSyntaxProvider.cs | 1 + .../SqlSyntax/SqlSyntaxProviderBase.cs | 3 +- .../SqlSyntax/SqlSyntaxProviderExtensions.cs | 4 +- .../Persistence/UmbracoDatabase.cs | 72 +- .../Persistence/UmbracoSqlExtensions.cs | 250 ++ ...taPocoUnitOfWork.cs => NPocoUnitOfWork.cs} | 364 +-- .../UnitOfWork/NPocoUnitOfWorkProvider.cs | 47 + .../UnitOfWork/PetaPocoUnitOfWorkProvider.cs | 71 - src/Umbraco.Core/Services/EntityService.cs | 27 +- .../Sync/DatabaseServerMessenger.cs | 15 +- src/Umbraco.Core/Umbraco.Core.csproj | 31 +- .../Umbraco.Core.csproj.DotSettings | 2 +- src/Umbraco.Core/packages.config | 1 + src/Umbraco.Tests/App.config | 20 +- .../EnumerableExtensionsTests.cs | 9 + .../Migrations/AlterMigrationTests.cs | 2 + .../Migrations/MigrationRunnerTests.cs | 4 + .../Migrations/Upgrades/BaseUpgradeTest.cs | 6 +- .../Upgrades/SqlCeDataUpgradeTest.cs | 4 +- .../Migrations/Upgrades/SqlCeUpgradeTest.cs | 8 +- .../Upgrades/ValidateOlderSchemaTest.cs | 9 +- .../Upgrades/ValidateV7UpgradeTest.cs | 3 + .../Mapping/ContentTypeModelMappingTests.cs | 12 +- .../Persistence/DatabaseContextTests.cs | 10 +- .../FaultHandling/ConnectionRetryTest.cs | 4 +- .../MigrationStartupHandlerTests.cs | 49 +- .../Persistence/NPocoExtensionsTest.cs | 123 + .../Persistence/PetaPocoCachesTest.cs | 193 ++ .../Persistence/PetaPocoExtensionsTest.cs | 308 -- .../ContentRepositorySqlClausesTest.cs | 89 +- .../ContentTypeRepositorySqlClausesTest.cs | 100 +- .../Querying/ContentTypeSqlMappingTests.cs | 2 +- ...aTypeDefinitionRepositorySqlClausesTest.cs | 13 +- .../Persistence/Querying/ExpressionTests.cs | 23 +- .../Querying/MediaRepositorySqlClausesTest.cs | 17 +- .../MediaTypeRepositorySqlClausesTest.cs | 13 +- .../{PetaPocoSqlTests.cs => NPocoSqlTests.cs} | 122 +- .../Persistence/Querying/QueryBuilderTests.cs | 41 +- .../Repositories/AuditRepositoryTest.cs | 2 +- .../Repositories/ContentRepositoryTest.cs | 77 +- .../Repositories/ContentTypeRepositoryTest.cs | 56 +- .../DataTypeDefinitionRepositoryTest.cs | 93 +- .../Repositories/DictionaryRepositoryTest.cs | 26 +- .../Repositories/DomainRepositoryTest.cs | 26 +- .../Repositories/LanguageRepositoryTest.cs | 25 +- .../Repositories/MacroRepositoryTest.cs | 36 +- .../Repositories/MediaRepositoryTest.cs | 42 +- .../Repositories/MediaTypeRepositoryTest.cs | 32 +- .../Repositories/MemberRepositoryTest.cs | 101 +- .../Repositories/MemberTypeRepositoryTest.cs | 22 +- .../NotificationsRepositoryTest.cs | 10 +- .../PublicAccessRepositoryTest.cs | 60 +- .../Repositories/RelationRepositoryTest.cs | 22 +- .../RelationTypeRepositoryTest.cs | 20 +- .../ServerRegistrationRepositoryTest.cs | 52 +- .../Repositories/TagRepositoryTest.cs | 42 +- .../Repositories/TaskRepositoryTest.cs | 18 +- .../Repositories/TaskTypeRepositoryTest.cs | 2 +- .../Repositories/TemplateRepositoryTest.cs | 44 +- .../Repositories/UserRepositoryTest.cs | 40 +- .../Repositories/UserTypeRepositoryTest.cs | 22 +- .../Persistence/SqlCeTableByTableTest.cs | 83 +- .../SqlCeSyntaxProviderTests.cs | 30 +- .../Services/ContentServicePerformanceTest.cs | 8 +- .../Services/ContentServiceTests.cs | 16 +- .../Services/MacroServiceTests.cs | 2 +- .../Services/MediaServiceTests.cs | 2 +- .../Services/MemberServiceTests.cs | 15 +- .../Services/PerformanceTests.cs | 5 +- .../Services/ThreadSafetyServiceTest.cs | 10 +- .../Services/UserServiceTests.cs | 18 +- .../TestHelpers/BaseDatabaseFactoryTest.cs | 28 +- .../TestHelpers/BaseUmbracoApplicationTest.cs | 12 +- .../TestHelpers/BaseUsingSqlCeSyntax.cs | 24 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 9 +- src/Umbraco.Tests/packages.config | 1 + .../MajorVersion7UpgradeReport.cs | 1 + .../EnsureListViewDataTypeIsCreated.cs | 1 + .../PublishAfterUpgradeToVersionSixth.cs | 25 +- src/Umbraco.Web/Umbraco.Web.csproj | 5 + .../Filters/DisableBrowserCacheAttribute.cs | 3 + .../WebServices/XmlDataIntegrityController.cs | 39 +- src/Umbraco.Web/packages.config | 1 + .../DataServices/PropertyAliasDto.cs | 2 +- src/UmbracoExamine/UmbracoExamine.csproj | 9 + src/UmbracoExamine/packages.config | 2 + src/umbraco.cms/packages.config | 1 + src/umbraco.cms/umbraco.cms.csproj | 5 + 236 files changed, 4327 insertions(+), 6705 deletions(-) create mode 100644 src/Umbraco.Core/ListExtensions.cs create mode 100644 src/Umbraco.Core/Persistence/FaultHandling/RetryDbConnection.cs delete mode 100644 src/Umbraco.Core/Persistence/Mappers/PetaPocoMapper.cs create mode 100644 src/Umbraco.Core/Persistence/Mappers/PocoMapper.cs rename src/Umbraco.Core/Persistence/{PetaPocoExtensions.cs => NPocoDatabaseExtensions.cs} (87%) delete mode 100644 src/Umbraco.Core/Persistence/PetaPoco.cs delete mode 100644 src/Umbraco.Core/Persistence/PetaPocoCommandExtensions.cs delete mode 100644 src/Umbraco.Core/Persistence/PetaPocoConnectionExtensions.cs delete mode 100644 src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs delete mode 100644 src/Umbraco.Core/Persistence/Relators/AccessRulesRelator.cs delete mode 100644 src/Umbraco.Core/Persistence/Relators/DictionaryLanguageTextRelator.cs delete mode 100644 src/Umbraco.Core/Persistence/Relators/GroupPropertyTypeRelator.cs delete mode 100644 src/Umbraco.Core/Persistence/Relators/MacroPropertyRelator.cs delete mode 100644 src/Umbraco.Core/Persistence/Relators/PropertyTypePropertyGroupRelator.cs delete mode 100644 src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs rename src/Umbraco.Core/Persistence/Repositories/{PetaPocoRepositoryBase.cs => NPocoRepositoryBase.cs} (65%) create mode 100644 src/Umbraco.Core/Persistence/Repositories/TupleExtensions.cs create mode 100644 src/Umbraco.Core/Persistence/SqlContext.cs create mode 100644 src/Umbraco.Core/Persistence/UmbracoSqlExtensions.cs rename src/Umbraco.Core/Persistence/UnitOfWork/{PetaPocoUnitOfWork.cs => NPocoUnitOfWork.cs} (88%) create mode 100644 src/Umbraco.Core/Persistence/UnitOfWork/NPocoUnitOfWorkProvider.cs delete mode 100644 src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs create mode 100644 src/Umbraco.Tests/Persistence/NPocoExtensionsTest.cs create mode 100644 src/Umbraco.Tests/Persistence/PetaPocoCachesTest.cs delete mode 100644 src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs rename src/Umbraco.Tests/Persistence/Querying/{PetaPocoSqlTests.cs => NPocoSqlTests.cs} (63%) diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index ba60925b11..eb9cc6903e 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -6,6 +6,7 @@ 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; @@ -40,19 +41,19 @@ namespace Umbraco.Core { /// - /// A bootstrapper for the Umbraco application which initializes all objects for the Core of the application + /// A bootstrapper for the Umbraco application which initializes all objects for the Core of the application /// /// /// This does not provide any startup functionality relating to web objects /// public class CoreBootManager : IBootManager { - + private ServiceContainer _appStartupEvtContainer; protected ProfilingLogger ProfilingLogger { get; private set; } private DisposableTimer _timer; protected PluginManager PluginManager { get; private set; } - + private bool _isInitialized = false; private bool _isStarted = false; @@ -76,7 +77,7 @@ namespace Umbraco.Core public CoreBootManager(UmbracoApplicationBase umbracoApplication) { if (umbracoApplication == null) throw new ArgumentNullException("umbracoApplication"); - _umbracoApplication = umbracoApplication; + _umbracoApplication = umbracoApplication; } internal CoreBootManager(UmbracoApplicationBase umbracoApplication, ProfilingLogger logger) @@ -91,7 +92,7 @@ namespace Umbraco.Core { if (_isInitialized) throw new InvalidOperationException("The boot manager has already been initialized"); - + //Create logger/profiler, and their resolvers, these are special resolvers that can be resolved before frozen so we can start logging LoggerResolver.Current = new LoggerResolver(_umbracoApplication.Logger) { CanResolveBeforeFrozen = true }; var profiler = CreateProfiler(); @@ -99,7 +100,7 @@ namespace Umbraco.Core ProfilingLogger = new ProfilingLogger(_umbracoApplication.Logger, profiler); ProfilingLogger = ProfilingLogger?? new ProfilingLogger(LoggerResolver.Current.Logger, ProfilerResolver.Current.Profiler); - + ApplicationCache = CreateApplicationCache(); _timer = ProfilingLogger.TraceDuration( @@ -112,26 +113,24 @@ namespace Umbraco.Core //TODO: this is currently a singleton but it would be better if it weren't. Unfortunately the only way to get // rid of this singleton would be to put it into IoC and then use the ServiceLocator pattern. PluginManager.Current = PluginManager = new PluginManager(ServiceProvider, ApplicationCache.RuntimeCache, ProfilingLogger, true); - + //build up core IoC servoces ConfigureCoreServices(Container); //set the singleton resolved from the core container ApplicationContext.Current = ApplicationContext = Container.GetInstance(); - //TODO: Remove these for v8! + //TODO: Remove these for v8! LegacyPropertyEditorIdToAliasConverter.CreateMappingsForCoreEditors(); LegacyParameterEditorAliasConverter.CreateMappingsForCoreEditors(); - //TODO: Make this as part of the db ctor! - Database.Mapper = new PetaPocoMapper(); - + //Create a 'child'container which is a copy of all of the current registrations and begin a sub scope for it // this child container will be used to manage the application event handler instances and the scope will be // completed at the end of the boot process to allow garbage collection _appStartupEvtContainer = Container.CreateChildContainer(); _appStartupEvtContainer.BeginScope(); _appStartupEvtContainer.RegisterCollection(PluginManager.ResolveApplicationStartupHandlers()); - + //build up standard IoC services ConfigureApplicationServices(Container); @@ -143,11 +142,11 @@ namespace Umbraco.Core { try { - using (ProfilingLogger.DebugDuration(string.Format("Executing {0} in ApplicationInitialized", x.GetType()))) - { - x.OnApplicationInitialized(UmbracoApplication, ApplicationContext); + using (ProfilingLogger.DebugDuration(string.Format("Executing {0} in ApplicationInitialized", x.GetType()))) + { + x.OnApplicationInitialized(UmbracoApplication, ApplicationContext); + } } - } catch (Exception ex) { ProfilingLogger.Logger.Error("An error occurred running OnApplicationInitialized for handler " + x.GetType(), ex); @@ -166,12 +165,12 @@ namespace Umbraco.Core internal virtual void ConfigureCoreServices(ServiceContainer container) { container.Register(factory => container); - + //Logging container.RegisterSingleton(factory => _umbracoApplication.Logger); container.RegisterSingleton(factory => ProfilingLogger.Profiler); container.RegisterSingleton(factory => ProfilingLogger); - + //Config container.RegisterFrom(); @@ -193,7 +192,7 @@ namespace Umbraco.Core container.RegisterSingleton(); container.Register(factory => FileSystemProviderManager.Current.GetFileSystemProvider()); - + } /// @@ -202,9 +201,9 @@ namespace Umbraco.Core /// internal virtual void ConfigureApplicationServices(ServiceContainer container) { - + } - + /// /// Creates the ApplicationCache based on a new instance of System.Web.Caching.Cache /// @@ -260,7 +259,7 @@ namespace Umbraco.Core } /// - /// Fires after initialization and calls the callback to allow for customizations to occur & + /// Fires after initialization and calls the callback to allow for customizations to occur & /// Ensure that the OnApplicationStarting methods of the IApplicationEvents are called /// /// @@ -275,11 +274,11 @@ namespace Umbraco.Core { try { - using (ProfilingLogger.DebugDuration(string.Format("Executing {0} in ApplicationStarting", x.GetType()))) - { - x.OnApplicationStarting(UmbracoApplication, ApplicationContext); + using (ProfilingLogger.DebugDuration(string.Format("Executing {0} in ApplicationStarting", x.GetType()))) + { + x.OnApplicationStarting(UmbracoApplication, ApplicationContext); + } } - } catch (Exception ex) { ProfilingLogger.Logger.Error("An error occurred running OnApplicationStarting for handler " + x.GetType(), ex); @@ -306,7 +305,7 @@ namespace Umbraco.Core { if (_isComplete) throw new InvalidOperationException("The boot manager has already been completed"); - + FreezeResolution(); //Here we need to make sure the db can be connected to @@ -318,16 +317,16 @@ namespace Umbraco.Core ((UserService) ApplicationContext.Services.UserService).IsUpgrading = true; - + //call OnApplicationStarting of each application events handler Parallel.ForEach(_appStartupEvtContainer.GetAllInstances(), x => { try { - using (ProfilingLogger.DebugDuration(string.Format("Executing {0} in ApplicationStarted", x.GetType()))) - { - x.OnApplicationStarted(UmbracoApplication, ApplicationContext); - } + using (ProfilingLogger.DebugDuration(string.Format("Executing {0} in ApplicationStarted", x.GetType()))) + { + x.OnApplicationStarted(UmbracoApplication, ApplicationContext); + } } catch (Exception ex) { @@ -445,7 +444,7 @@ namespace Umbraco.Core Container, ProfilingLogger.Logger, () => PluginManager.ResolveAssignedMapperTypes()); - + //RepositoryResolver.Current = new RepositoryResolver( // new RepositoryFactory(ApplicationCache)); @@ -469,7 +468,7 @@ namespace Umbraco.Core PluginManager.ResolveTypes()); // use the new DefaultShortStringHelper - ShortStringHelperResolver.Current = new ShortStringHelperResolver(Container, + ShortStringHelperResolver.Current = new ShortStringHelperResolver(Container, factory => new DefaultShortStringHelper(factory.GetInstance()).WithDefaultConfig()); UrlSegmentProviderResolver.Current = new UrlSegmentProviderResolver( diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index 1f00fb0df9..8f94c993ba 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Web; using System.Web.Configuration; using System.Xml.Linq; +using NPoco; using Semver; using Umbraco.Core.Configuration; using Umbraco.Core.IO; @@ -789,5 +790,10 @@ namespace Umbraco.Core return true; } + + public Sql Sql() + { + return NPoco.Sql.BuilderFor(new SqlContext(SqlSyntax, Database)); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/DependencyInjection/RepositoryCompositionRoot.cs b/src/Umbraco.Core/DependencyInjection/RepositoryCompositionRoot.cs index 2d8e0109ec..f6ff4f1914 100644 --- a/src/Umbraco.Core/DependencyInjection/RepositoryCompositionRoot.cs +++ b/src/Umbraco.Core/DependencyInjection/RepositoryCompositionRoot.cs @@ -23,16 +23,20 @@ namespace Umbraco.Core.DependencyInjection { container.RegisterSingleton(factory => new DefaultDatabaseFactory(GlobalSettings.UmbracoConnectionName, factory.GetInstance())); container.RegisterSingleton(factory => GetDbContext(factory)); + container.RegisterSingleton(factory => SqlSyntaxProviders.CreateDefault(factory.GetInstance())); + container.Register(factory => factory.GetInstance().SqlSyntax); + container.RegisterSingleton(); - container.RegisterSingleton(factory => new PetaPocoUnitOfWorkProvider(factory.GetInstance())); + container.RegisterSingleton(factory => new NPocoUnitOfWorkProvider(factory.GetInstance())); + container.RegisterSingleton(factory => new MappingResolver( factory.GetInstance(), factory.GetInstance(), () => factory.GetInstance().ResolveAssignedMapperTypes())); container.Register(); - container.Register(factory => factory.GetInstance().SqlSyntax); container.RegisterSingleton(factory => CacheHelper.CreateDisabledCacheHelper(), "DisabledCache"); + container.RegisterSingleton(factory => new PhysicalFileSystem(SystemDirectories.Scripts), "ScriptFileSystem"); container.RegisterSingleton(factory => new PhysicalFileSystem(SystemDirectories.MvcViews + "/Partials/"), "PartialViewFileSystem"); container.RegisterSingleton(factory => new PhysicalFileSystem(SystemDirectories.MvcViews + "/MacroPartials/"), "PartialViewMacroFileSystem"); diff --git a/src/Umbraco.Core/EnumerableExtensions.cs b/src/Umbraco.Core/EnumerableExtensions.cs index 58d4d453b7..156831670a 100644 --- a/src/Umbraco.Core/EnumerableExtensions.cs +++ b/src/Umbraco.Core/EnumerableExtensions.cs @@ -20,9 +20,33 @@ namespace Umbraco.Core if (groupSize <= 0) throw new ArgumentException("Must be greater than zero.", "groupSize"); - return source - .Select((x, i) => Tuple.Create(i / groupSize, x)) - .GroupBy(t => t.Item1, t => t.Item2); + + // following code derived from MoreLinq and does not allocate bazillions of tuples + + T[] temp = null; + var count = 0; + + foreach (var item in source) + { + if (temp == null) temp = new T[groupSize]; + temp[count++] = item; + if (count != groupSize) continue; + yield return temp/*.Select(x => x)*/; + temp = null; + count = 0; + } + + if (temp != null && count > 0) + yield return temp.Take(count); + } + + public static IEnumerable SelectByGroups(this IEnumerable source, Func, IEnumerable> selector, int groupSize) + { + // don't want to use a SelectMany(x => x) here - isn't this better? + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var resultGroup in source.InGroupsOf(groupSize).Select(selector)) + foreach (var result in resultGroup) + yield return result; } /// The distinct by. @@ -287,7 +311,7 @@ namespace Umbraco.Core /// /// The logic for this is taken from: /// http://stackoverflow.com/questions/4576723/test-whether-two-ienumerablet-have-the-same-values-with-the-same-frequencies - /// + /// /// There's a few answers, this one seems the best for it's simplicity and based on the comment of Eamon /// public static bool UnsortedSequenceEqual(this IEnumerable source, IEnumerable other) @@ -301,5 +325,27 @@ namespace Umbraco.Core && list1Groups.All(g => g.Count() == list2Groups[g.Key].Count()); } + /// + /// Transforms an enumerable. + /// + /// + /// + /// + /// + public static IEnumerable Transform(this IEnumerable source, Func, IEnumerable> transform) + { + return transform(source); + } + + /// + /// Gets a null IEnumerable as an empty IEnumerable. + /// + /// + /// + /// + public static IEnumerable EmptyNull(this IEnumerable items) + { + return items ?? Enumerable.Empty(); + } } } diff --git a/src/Umbraco.Core/ListExtensions.cs b/src/Umbraco.Core/ListExtensions.cs new file mode 100644 index 0000000000..e639cbd5c7 --- /dev/null +++ b/src/Umbraco.Core/ListExtensions.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core +{ + /// + /// Provides extensions to the List type. + /// + internal static class ListExtensions + { + // based upon the original Zip method + public static IEnumerable Zip(this IEnumerable e1, IEnumerable e2, IEnumerable e3, + Func resultSelector) + { + if (e1 == null) throw new ArgumentNullException("e1"); + if (e2 == null) throw new ArgumentNullException("e2"); + if (e3 == null) throw new ArgumentNullException("e3"); + if (resultSelector == null) throw new ArgumentNullException("resultSelector"); + return ZipIterator(e1, e2, e3, resultSelector); + } + + private static IEnumerable ZipIterator(IEnumerable ie1, IEnumerable ie2, IEnumerable ie3, + Func resultSelector) + { + var e1 = ie1.GetEnumerator(); + try + { + var e2 = ie2.GetEnumerator(); + var e3 = ie3.GetEnumerator(); + try + { + while (e1.MoveNext() && e2.MoveNext() && e3.MoveNext()) + yield return resultSelector(e1.Current, e2.Current, e3.Current); + } + finally + { + if (e2 != null) + e2.Dispose(); + if (e3 != null) + e3.Dispose(); + } + } + finally + { + if (e1 != null) + e1.Dispose(); + } + } + } +} diff --git a/src/Umbraco.Core/Models/Rdbms/AccessDto.cs b/src/Umbraco.Core/Models/Rdbms/AccessDto.cs index 37b1dbddd8..b9a512e027 100644 --- a/src/Umbraco.Core/Models/Rdbms/AccessDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/AccessDto.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Security.AccessControl; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -9,7 +10,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Models.Rdbms { [TableName("umbracoAccess")] - [PrimaryKey("id", autoIncrement = false)] + [PrimaryKey("id", AutoIncrement = false)] [ExplicitColumns] internal class AccessDto { @@ -39,6 +40,7 @@ namespace Umbraco.Core.Models.Rdbms public DateTime UpdateDate { get; set; } [ResultColumn] + [Reference(ReferenceType.Many, ReferenceMemberName = "AccessId")] public List Rules { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/AccessRuleDto.cs b/src/Umbraco.Core/Models/Rdbms/AccessRuleDto.cs index 78e3444e56..07f9d4f175 100644 --- a/src/Umbraco.Core/Models/Rdbms/AccessRuleDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/AccessRuleDto.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -6,7 +7,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Models.Rdbms { [TableName("umbracoAccessRule")] - [PrimaryKey("id", autoIncrement = false)] + [PrimaryKey("id", AutoIncrement = false)] [ExplicitColumns] internal class AccessRuleDto { diff --git a/src/Umbraco.Core/Models/Rdbms/CacheInstructionDto.cs b/src/Umbraco.Core/Models/Rdbms/CacheInstructionDto.cs index c24004b7dc..182b8c67b3 100644 --- a/src/Umbraco.Core/Models/Rdbms/CacheInstructionDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/CacheInstructionDto.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; diff --git a/src/Umbraco.Core/Models/Rdbms/ContentDto.cs b/src/Umbraco.Core/Models/Rdbms/ContentDto.cs index eb9a66a7f2..8cef85b998 100644 --- a/src/Umbraco.Core/Models/Rdbms/ContentDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/ContentDto.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms @@ -22,6 +23,7 @@ namespace Umbraco.Core.Models.Rdbms public int ContentTypeId { get; set; } [ResultColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "NodeId")] public NodeDto NodeDto { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/ContentType2ContentTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/ContentType2ContentTypeDto.cs index 89773ef1ee..a4e5b6b71b 100644 --- a/src/Umbraco.Core/Models/Rdbms/ContentType2ContentTypeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/ContentType2ContentTypeDto.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms diff --git a/src/Umbraco.Core/Models/Rdbms/ContentTypeAllowedContentTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/ContentTypeAllowedContentTypeDto.cs index 91904437f0..9c101ffda4 100644 --- a/src/Umbraco.Core/Models/Rdbms/ContentTypeAllowedContentTypeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/ContentTypeAllowedContentTypeDto.cs @@ -1,10 +1,11 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { [TableName("cmsContentTypeAllowedContentType")] - [PrimaryKey("Id", autoIncrement = false)] + [PrimaryKey("Id", AutoIncrement = false)] [ExplicitColumns] internal class ContentTypeAllowedContentTypeDto { @@ -22,6 +23,7 @@ namespace Umbraco.Core.Models.Rdbms public int SortOrder { get; set; } [ResultColumn] + [Reference(ReferenceType.OneToOne)] public ContentTypeDto ContentTypeDto { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/ContentTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/ContentTypeDto.cs index 8d163abf44..483587cffc 100644 --- a/src/Umbraco.Core/Models/Rdbms/ContentTypeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/ContentTypeDto.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms diff --git a/src/Umbraco.Core/Models/Rdbms/ContentTypeTemplateDto.cs b/src/Umbraco.Core/Models/Rdbms/ContentTypeTemplateDto.cs index 88ef02ea90..5ebd97319a 100644 --- a/src/Umbraco.Core/Models/Rdbms/ContentTypeTemplateDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/ContentTypeTemplateDto.cs @@ -1,10 +1,11 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { [TableName("cmsDocumentType")] - [PrimaryKey("contentTypeNodeId", autoIncrement = false)] + [PrimaryKey("contentTypeNodeId", AutoIncrement = false)] [ExplicitColumns] internal class ContentTypeTemplateDto { @@ -23,6 +24,7 @@ namespace Umbraco.Core.Models.Rdbms public bool IsDefault { get; set; } [ResultColumn] + [Reference(ReferenceType.OneToOne)] public ContentTypeDto ContentTypeDto { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/ContentVersionDto.cs b/src/Umbraco.Core/Models/Rdbms/ContentVersionDto.cs index 3bd85cccf6..715f9908e4 100644 --- a/src/Umbraco.Core/Models/Rdbms/ContentVersionDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/ContentVersionDto.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -28,6 +29,7 @@ namespace Umbraco.Core.Models.Rdbms public DateTime VersionDate { get; set; } [ResultColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "NodeId", ReferenceMemberName = "NodeId")] public ContentDto ContentDto { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/ContentXmlDto.cs b/src/Umbraco.Core/Models/Rdbms/ContentXmlDto.cs index ca48a4074c..c72af43901 100644 --- a/src/Umbraco.Core/Models/Rdbms/ContentXmlDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/ContentXmlDto.cs @@ -1,11 +1,12 @@ using System.Data; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { [TableName("cmsContentXml")] - [PrimaryKey("nodeId", autoIncrement = false)] + [PrimaryKey("nodeId", AutoIncrement = false)] [ExplicitColumns] internal class ContentXmlDto { diff --git a/src/Umbraco.Core/Models/Rdbms/DataTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/DataTypeDto.cs index b0e63b0726..148116d70e 100644 --- a/src/Umbraco.Core/Models/Rdbms/DataTypeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/DataTypeDto.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; @@ -26,6 +27,7 @@ namespace Umbraco.Core.Models.Rdbms public string DbType { get; set; }//NOTE Is set to [varchar] (50) in Sql Server script [ResultColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "DataTypeId")] public NodeDto NodeDto { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/DataTypePreValueDto.cs b/src/Umbraco.Core/Models/Rdbms/DataTypePreValueDto.cs index 652e63df0b..69c9e17dbd 100644 --- a/src/Umbraco.Core/Models/Rdbms/DataTypePreValueDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/DataTypePreValueDto.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms diff --git a/src/Umbraco.Core/Models/Rdbms/DictionaryDto.cs b/src/Umbraco.Core/Models/Rdbms/DictionaryDto.cs index 712d1937a9..262c9f3c5b 100644 --- a/src/Umbraco.Core/Models/Rdbms/DictionaryDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/DictionaryDto.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; @@ -28,6 +29,7 @@ namespace Umbraco.Core.Models.Rdbms public string Key { get; set; } [ResultColumn] + [Reference(ReferenceType.Many, ColumnName = "UniqueId", ReferenceMemberName = "UniqueId")] public List LanguageTextDtos { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/DocumentDto.cs b/src/Umbraco.Core/Models/Rdbms/DocumentDto.cs index 88e04087c9..2ad44fbe58 100644 --- a/src/Umbraco.Core/Models/Rdbms/DocumentDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/DocumentDto.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -6,7 +7,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Models.Rdbms { [TableName("cmsDocument")] - [PrimaryKey("versionId", autoIncrement = false)] + [PrimaryKey("versionId", AutoIncrement = false)] [ExplicitColumns] internal class DocumentDto { @@ -46,16 +47,18 @@ namespace Umbraco.Core.Models.Rdbms [NullSetting(NullSetting = NullSettings.Null)] [ForeignKey(typeof(TemplateDto), Column = "nodeId")] public int? TemplateId { get; set; } - + [Column("newest")] [Constraint(Default = "0")] [Index(IndexTypes.NonClustered, Name = "IX_cmsDocument_newest")] public bool Newest { get; set; } [ResultColumn] + [Reference(ReferenceType.OneToOne, ReferenceMemberName = "NodeId")] public ContentVersionDto ContentVersionDto { get; set; } [ResultColumn] + [Reference(ReferenceType.OneToOne, ReferenceMemberName = "NodeId")] public DocumentPublishedReadOnlyDto DocumentPublishedReadOnlyDto { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/DocumentPublishedReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/DocumentPublishedReadOnlyDto.cs index 4a7e359d91..788e321877 100644 --- a/src/Umbraco.Core/Models/Rdbms/DocumentPublishedReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/DocumentPublishedReadOnlyDto.cs @@ -1,10 +1,11 @@ using System; +using NPoco; using Umbraco.Core.Persistence; namespace Umbraco.Core.Models.Rdbms { [TableName("cmsDocument")] - [PrimaryKey("versionId", autoIncrement = false)] + [PrimaryKey("versionId", AutoIncrement = false)] [ExplicitColumns] internal class DocumentPublishedReadOnlyDto { diff --git a/src/Umbraco.Core/Models/Rdbms/DomainDto.cs b/src/Umbraco.Core/Models/Rdbms/DomainDto.cs index e43c1bdeae..e78c442184 100644 --- a/src/Umbraco.Core/Models/Rdbms/DomainDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/DomainDto.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms diff --git a/src/Umbraco.Core/Models/Rdbms/ExternalLoginDto.cs b/src/Umbraco.Core/Models/Rdbms/ExternalLoginDto.cs index 803c25fdfc..1f335ec6ce 100644 --- a/src/Umbraco.Core/Models/Rdbms/ExternalLoginDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/ExternalLoginDto.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; diff --git a/src/Umbraco.Core/Models/Rdbms/LanguageDto.cs b/src/Umbraco.Core/Models/Rdbms/LanguageDto.cs index a634dca0a4..2e6a4fdae2 100644 --- a/src/Umbraco.Core/Models/Rdbms/LanguageDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/LanguageDto.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms diff --git a/src/Umbraco.Core/Models/Rdbms/LanguageTextDto.cs b/src/Umbraco.Core/Models/Rdbms/LanguageTextDto.cs index 87329fbd4c..e14ffdb8a8 100644 --- a/src/Umbraco.Core/Models/Rdbms/LanguageTextDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/LanguageTextDto.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; diff --git a/src/Umbraco.Core/Models/Rdbms/LogDto.cs b/src/Umbraco.Core/Models/Rdbms/LogDto.cs index be67b5873a..277720d29b 100644 --- a/src/Umbraco.Core/Models/Rdbms/LogDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/LogDto.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; diff --git a/src/Umbraco.Core/Models/Rdbms/MacroDto.cs b/src/Umbraco.Core/Models/Rdbms/MacroDto.cs index 15422f7a08..1affe3a04e 100644 --- a/src/Umbraco.Core/Models/Rdbms/MacroDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/MacroDto.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; @@ -59,6 +60,7 @@ namespace Umbraco.Core.Models.Rdbms public string MacroFilePath { get; set; } [ResultColumn] + [Reference(ReferenceType.Many, ReferenceMemberName = "Macro")] public List MacroPropertyDtos { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/MacroPropertyDto.cs b/src/Umbraco.Core/Models/Rdbms/MacroPropertyDto.cs index e2efddc829..6d10315ec1 100644 --- a/src/Umbraco.Core/Models/Rdbms/MacroPropertyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/MacroPropertyDto.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms @@ -11,8 +12,8 @@ namespace Umbraco.Core.Models.Rdbms [Column("id")] [PrimaryKeyColumn] public int Id { get; set; } - - [Column("editorAlias")] + + [Column("editorAlias")] public string EditorAlias { get; set; } [Column("macro")] diff --git a/src/Umbraco.Core/Models/Rdbms/Member2MemberGroupDto.cs b/src/Umbraco.Core/Models/Rdbms/Member2MemberGroupDto.cs index 888f331364..653f4d3c87 100644 --- a/src/Umbraco.Core/Models/Rdbms/Member2MemberGroupDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/Member2MemberGroupDto.cs @@ -1,10 +1,11 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { [TableName("cmsMember2MemberGroup")] - [PrimaryKey("Member", autoIncrement = false)] + [PrimaryKey("Member", AutoIncrement = false)] [ExplicitColumns] internal class Member2MemberGroupDto { diff --git a/src/Umbraco.Core/Models/Rdbms/MemberDto.cs b/src/Umbraco.Core/Models/Rdbms/MemberDto.cs index e5f7b3f17c..35d5db1042 100644 --- a/src/Umbraco.Core/Models/Rdbms/MemberDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/MemberDto.cs @@ -1,10 +1,11 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { [TableName("cmsMember")] - [PrimaryKey("nodeId", autoIncrement = false)] + [PrimaryKey("nodeId", AutoIncrement = false)] [ExplicitColumns] internal class MemberDto { @@ -30,6 +31,7 @@ namespace Umbraco.Core.Models.Rdbms public string Password { get; set; } [ResultColumn] + [Reference(ReferenceType.OneToOne, ReferenceMemberName = "NodeId")] public ContentVersionDto ContentVersionDto { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/MemberTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/MemberTypeDto.cs index c9432652af..7d637e74c6 100644 --- a/src/Umbraco.Core/Models/Rdbms/MemberTypeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/MemberTypeDto.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms diff --git a/src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs index 4833cb33f0..4997134ccf 100644 --- a/src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/MemberTypeReadOnlyDto.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using NPoco; using Umbraco.Core.Persistence; namespace Umbraco.Core.Models.Rdbms @@ -68,11 +69,13 @@ namespace Umbraco.Core.Models.Rdbms /* PropertyTypes */ //TODO Add PropertyTypeDto (+MemberTypeDto and DataTypeDto as one) ReadOnly list [ResultColumn] + [Reference(ReferenceType.Many, ReferenceMemberName = "ContentTypeId")] public List PropertyTypes { get; set; } /* PropertyTypeGroups */ //TODO Add PropertyTypeGroupDto ReadOnly list [ResultColumn] + [Reference(ReferenceType.Many, ReferenceMemberName = "ContentTypeNodeId")] public List PropertyTypeGroups { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/MigrationDto.cs b/src/Umbraco.Core/Models/Rdbms/MigrationDto.cs index 1a76895e90..2cff4f1c5a 100644 --- a/src/Umbraco.Core/Models/Rdbms/MigrationDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/MigrationDto.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; diff --git a/src/Umbraco.Core/Models/Rdbms/NodeDto.cs b/src/Umbraco.Core/Models/Rdbms/NodeDto.cs index c5fac092df..b0860ea34a 100644 --- a/src/Umbraco.Core/Models/Rdbms/NodeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/NodeDto.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; diff --git a/src/Umbraco.Core/Models/Rdbms/PreviewXmlDto.cs b/src/Umbraco.Core/Models/Rdbms/PreviewXmlDto.cs index d981855f24..e3adb3c915 100644 --- a/src/Umbraco.Core/Models/Rdbms/PreviewXmlDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PreviewXmlDto.cs @@ -1,11 +1,12 @@ using System; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { [TableName("cmsPreviewXml")] - [PrimaryKey("nodeId", autoIncrement = false)] + [PrimaryKey("nodeId", AutoIncrement = false)] [ExplicitColumns] internal class PreviewXmlDto { diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs index 63e3104d12..8840cb771d 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; @@ -52,6 +53,7 @@ namespace Umbraco.Core.Models.Rdbms public string Text { get; set; } [ResultColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "PropertyTypeId")] public PropertyTypeDto PropertyTypeDto { get; set; } [Ignore] @@ -68,12 +70,12 @@ namespace Umbraco.Core.Models.Rdbms { return Decimal.Value; } - + if (Date.HasValue) { return Date.Value; } - + if (string.IsNullOrEmpty(VarChar) == false) { return VarChar; diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeDto.cs index 2be4b24157..cc74268c8a 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeDto.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -52,6 +53,7 @@ namespace Umbraco.Core.Models.Rdbms public string Description { get; set; } [ResultColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "DataTypeId")] public DataTypeDto DataTypeDto { get; set; } [Column("UniqueID")] diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupDto.cs index 68de420a97..c560780dcf 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupDto.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -7,7 +8,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Models.Rdbms { [TableName("cmsPropertyTypeGroup")] - [PrimaryKey("id", autoIncrement = true)] + [PrimaryKey("id", AutoIncrement = true)] [ExplicitColumns] internal class PropertyTypeGroupDto { @@ -26,6 +27,7 @@ namespace Umbraco.Core.Models.Rdbms public int SortOrder { get; set; } [ResultColumn] + [Reference(ReferenceType.Many, ReferenceMemberName = "PropertyTypeGroupId")] public List PropertyTypeDtos { get; set; } [Column("uniqueID")] diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs index beebef9eeb..ac38b7b642 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeGroupReadOnlyDto.cs @@ -1,9 +1,10 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; namespace Umbraco.Core.Models.Rdbms { [TableName("cmsPropertyTypeGroup")] - [PrimaryKey("id", autoIncrement = true)] + [PrimaryKey("id", AutoIncrement = true)] [ExplicitColumns] internal class PropertyTypeGroupReadOnlyDto { diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs index 2f448860b0..bba1546a93 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyTypeReadOnlyDto.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence; namespace Umbraco.Core.Models.Rdbms diff --git a/src/Umbraco.Core/Models/Rdbms/RelationDto.cs b/src/Umbraco.Core/Models/Rdbms/RelationDto.cs index 368904a5cb..44f0199b17 100644 --- a/src/Umbraco.Core/Models/Rdbms/RelationDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/RelationDto.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; diff --git a/src/Umbraco.Core/Models/Rdbms/RelationTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/RelationTypeDto.cs index 8a6eb5c02a..c927fcf455 100644 --- a/src/Umbraco.Core/Models/Rdbms/RelationTypeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/RelationTypeDto.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; diff --git a/src/Umbraco.Core/Models/Rdbms/ServerRegistrationDto.cs b/src/Umbraco.Core/Models/Rdbms/ServerRegistrationDto.cs index 2a3751c083..55b6145a3a 100644 --- a/src/Umbraco.Core/Models/Rdbms/ServerRegistrationDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/ServerRegistrationDto.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; diff --git a/src/Umbraco.Core/Models/Rdbms/StylesheetDto.cs b/src/Umbraco.Core/Models/Rdbms/StylesheetDto.cs index 3580fcf918..91132d3a7e 100644 --- a/src/Umbraco.Core/Models/Rdbms/StylesheetDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/StylesheetDto.cs @@ -1,10 +1,11 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { [TableName("cmsStylesheet")] - [PrimaryKey("nodeId", autoIncrement = false)] + [PrimaryKey("nodeId", AutoIncrement = false)] [ExplicitColumns] internal class StylesheetDto { diff --git a/src/Umbraco.Core/Models/Rdbms/StylesheetPropertyDto.cs b/src/Umbraco.Core/Models/Rdbms/StylesheetPropertyDto.cs index cfe3148764..8e249a8da2 100644 --- a/src/Umbraco.Core/Models/Rdbms/StylesheetPropertyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/StylesheetPropertyDto.cs @@ -1,10 +1,11 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { [TableName("cmsStylesheetProperty")] - [PrimaryKey("nodeId", autoIncrement = false)] + [PrimaryKey("nodeId", AutoIncrement = false)] [ExplicitColumns] internal class StylesheetPropertyDto { diff --git a/src/Umbraco.Core/Models/Rdbms/TagDto.cs b/src/Umbraco.Core/Models/Rdbms/TagDto.cs index d612f656bb..cb37f2c723 100644 --- a/src/Umbraco.Core/Models/Rdbms/TagDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/TagDto.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms diff --git a/src/Umbraco.Core/Models/Rdbms/TagRelationshipDto.cs b/src/Umbraco.Core/Models/Rdbms/TagRelationshipDto.cs index 4fc936713a..d605530839 100644 --- a/src/Umbraco.Core/Models/Rdbms/TagRelationshipDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/TagRelationshipDto.cs @@ -1,10 +1,11 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { [TableName("cmsTagRelationship")] - [PrimaryKey("nodeId", autoIncrement = false)] + [PrimaryKey("nodeId", AutoIncrement = false)] [ExplicitColumns] internal class TagRelationshipDto { diff --git a/src/Umbraco.Core/Models/Rdbms/TaskDto.cs b/src/Umbraco.Core/Models/Rdbms/TaskDto.cs index e27f7c0a93..7ce24aa21e 100644 --- a/src/Umbraco.Core/Models/Rdbms/TaskDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/TaskDto.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -44,6 +45,7 @@ namespace Umbraco.Core.Models.Rdbms public string Comment { get; set; } [ResultColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "TaskTypeId")] public TaskTypeDto TaskTypeDto { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/TaskTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/TaskTypeDto.cs index 2ff61953ce..113908a47c 100644 --- a/src/Umbraco.Core/Models/Rdbms/TaskTypeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/TaskTypeDto.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms diff --git a/src/Umbraco.Core/Models/Rdbms/TemplateDto.cs b/src/Umbraco.Core/Models/Rdbms/TemplateDto.cs index 8e2fe0b9f5..b0bdb17bba 100644 --- a/src/Umbraco.Core/Models/Rdbms/TemplateDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/TemplateDto.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms @@ -27,6 +28,7 @@ namespace Umbraco.Core.Models.Rdbms public string Design { get; set; } [ResultColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "NodeId")] public NodeDto NodeDto { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/UmbracoDeployChecksumDto.cs b/src/Umbraco.Core/Models/Rdbms/UmbracoDeployChecksumDto.cs index 06d904ee88..a012d85563 100644 --- a/src/Umbraco.Core/Models/Rdbms/UmbracoDeployChecksumDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UmbracoDeployChecksumDto.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; diff --git a/src/Umbraco.Core/Models/Rdbms/UmbracoDeployDependencyDto.cs b/src/Umbraco.Core/Models/Rdbms/UmbracoDeployDependencyDto.cs index 765a32c929..6ec19a513d 100644 --- a/src/Umbraco.Core/Models/Rdbms/UmbracoDeployDependencyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UmbracoDeployDependencyDto.cs @@ -1,3 +1,4 @@ +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; diff --git a/src/Umbraco.Core/Models/Rdbms/User2AppDto.cs b/src/Umbraco.Core/Models/Rdbms/User2AppDto.cs index eafc6be230..5c80c6e117 100644 --- a/src/Umbraco.Core/Models/Rdbms/User2AppDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/User2AppDto.cs @@ -1,10 +1,11 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { [TableName("umbracoUser2app")] - [PrimaryKey("user", autoIncrement = false)] + [PrimaryKey("user", AutoIncrement = false)] [ExplicitColumns] internal class User2AppDto { diff --git a/src/Umbraco.Core/Models/Rdbms/User2NodeNotifyDto.cs b/src/Umbraco.Core/Models/Rdbms/User2NodeNotifyDto.cs index 298ad920d9..e45c0a6ad8 100644 --- a/src/Umbraco.Core/Models/Rdbms/User2NodeNotifyDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/User2NodeNotifyDto.cs @@ -1,10 +1,11 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { [TableName("umbracoUser2NodeNotify")] - [PrimaryKey("userId", autoIncrement = false)] + [PrimaryKey("userId", AutoIncrement = false)] [ExplicitColumns] internal class User2NodeNotifyDto { diff --git a/src/Umbraco.Core/Models/Rdbms/User2NodePermissionDto.cs b/src/Umbraco.Core/Models/Rdbms/User2NodePermissionDto.cs index 1e6662735f..117f88fe66 100644 --- a/src/Umbraco.Core/Models/Rdbms/User2NodePermissionDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/User2NodePermissionDto.cs @@ -1,10 +1,11 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { [TableName("umbracoUser2NodePermission")] - [PrimaryKey("userId", autoIncrement = false)] + [PrimaryKey("userId", AutoIncrement = false)] [ExplicitColumns] internal class User2NodePermissionDto { diff --git a/src/Umbraco.Core/Models/Rdbms/UserDto.cs b/src/Umbraco.Core/Models/Rdbms/UserDto.cs index 05a93c86bf..350f4b4a79 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UserDto.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; +using NPoco; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms { [TableName("umbracoUser")] - [PrimaryKey("id", autoIncrement = true)] + [PrimaryKey("id", AutoIncrement = true)] [ExplicitColumns] internal class UserDto { @@ -75,6 +76,7 @@ namespace Umbraco.Core.Models.Rdbms public DateTime? LastLoginDate { get; set; } [ResultColumn] + [Reference(ReferenceType.Many, ReferenceMemberName = "UserId")] public List User2AppDtos { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/UserTypeDto.cs b/src/Umbraco.Core/Models/Rdbms/UserTypeDto.cs index d3268a14fb..8b6d286073 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserTypeDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UserTypeDto.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence; +using NPoco; +using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs index eefacbcbc1..7936ac0fa8 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs @@ -2,6 +2,7 @@ using System.Data; using System.Linq; using System.Reflection; +using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.SqlSyntax; @@ -11,7 +12,7 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions { public static TableDefinition GetTableDefinition(Type modelType, ISqlSyntaxProvider sqlSyntax) { - //Looks for PetaPoco's TableNameAtribute for the name of the table + //Looks for NPoco's TableNameAtribute for the name of the table //If no attribute is set we use the name of the Type as the default convention var tableNameAttribute = modelType.FirstAttribute(); string tableName = tableNameAttribute == null ? modelType.Name : tableNameAttribute.Value; @@ -108,7 +109,7 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions var constraintAttribute = propertyInfo.FirstAttribute(); if (constraintAttribute != null) { - //Special case for MySQL as it can't have multiple default DateTime values, which + //Special case for MySQL as it can't have multiple default DateTime values, which //is what the umbracoServer table definition is trying to create if (sqlSyntax is MySqlSyntaxProvider && definition.TableName == "umbracoServer" && definition.TableName.ToLowerInvariant() == "lastNotifiedDate".ToLowerInvariant()) diff --git a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs index 418e5b953d..ad6af9dd0a 100644 --- a/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs +++ b/src/Umbraco.Core/Persistence/DatabaseSchemaHelper.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using NPoco; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; diff --git a/src/Umbraco.Core/Persistence/DatabasenodeLockExtensions.cs b/src/Umbraco.Core/Persistence/DatabasenodeLockExtensions.cs index 3e6d245416..d6ad4f1f3c 100644 --- a/src/Umbraco.Core/Persistence/DatabasenodeLockExtensions.cs +++ b/src/Umbraco.Core/Persistence/DatabasenodeLockExtensions.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence { if (database == null) throw new ArgumentNullException("database"); - if (database.CurrentTransactionIsolationLevel < IsolationLevel.RepeatableRead) + if (database.GetCurrentTransactionIsolationLevel() < IsolationLevel.RepeatableRead) throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required."); } diff --git a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs index 72c4275541..f5c2ab9c4f 100644 --- a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs @@ -1,7 +1,12 @@ using System; +using System.Configuration; using System.Web; +using NPoco; +using NPoco.FluentMappings; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.FaultHandling; +using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Persistence { @@ -17,82 +22,100 @@ namespace Umbraco.Core.Persistence { private readonly string _connectionStringName; private readonly ILogger _logger; - public string ConnectionString { get; private set; } + private readonly DatabaseFactory _databaseFactory; + private readonly RetryPolicy _connectionRetryPolicy; + private readonly RetryPolicy _commandRetryPolicy; + + public string ConnectionString { get; private set; } public string ProviderName { get; private set; } - + //very important to have ThreadStatic: // see: http://issues.umbraco.org/issue/U4-2172 [ThreadStatic] - private static volatile UmbracoDatabase _nonHttpInstance; - - private static readonly object Locker = new object(); - + private static Lazy _nonHttpInstance; + /// /// Constructor accepting custom connection string /// /// Name of the connection string in web.config /// public DefaultDatabaseFactory(string connectionStringName, ILogger logger) + : this(logger) { - if (logger == null) throw new ArgumentNullException("logger"); Mandate.ParameterNotNullOrEmpty(connectionStringName, "connectionStringName"); _connectionStringName = connectionStringName; - _logger = logger; - } - /// - /// Constructor accepting custom connectino string and provider name - /// - /// Connection String to use with Database - /// Database Provider for the Connection String - /// - public DefaultDatabaseFactory(string connectionString, string providerName, ILogger logger) + if (ConfigurationManager.ConnectionStrings[connectionStringName] == null) + throw new InvalidOperationException("Can't find a connection string with the name '" + connectionStringName + "'"); + var connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; + + _connectionRetryPolicy = RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(connectionString); + _commandRetryPolicy = RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(connectionString); + } + + /// + /// Constructor accepting custom connectino string and provider name + /// + /// Connection String to use with Database + /// Database Provider for the Connection String + /// + public DefaultDatabaseFactory(string connectionString, string providerName, ILogger logger) + : this(logger) { - if (logger == null) throw new ArgumentNullException("logger"); Mandate.ParameterNotNullOrEmpty(connectionString, "connectionString"); Mandate.ParameterNotNullOrEmpty(providerName, "providerName"); ConnectionString = connectionString; ProviderName = providerName; + + _connectionRetryPolicy = RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(connectionString); + _commandRetryPolicy = RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(connectionString); + } + + private DefaultDatabaseFactory(ILogger logger) + { + if (logger == null) throw new ArgumentNullException("logger"); _logger = logger; - } + + // ensure we have only 1 set of mappers, and 1 PocoDataFactory, for all database + // so that everything NPoco is properly cached for the lifetime of the application + var mappers = new MapperCollection { new PocoMapper() }; + var pocoDataFactory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, mappers).Init()); + var config = new FluentConfig(xmappers => pocoDataFactory); + + // create the database factory + _databaseFactory = DatabaseFactory.Config(x => x + .UsingDatabase(CreateDatabaseInstance) // creating UmbracoDatabase instances + .WithFluentConfig(config)); // with proper configuration + + _nonHttpInstance = new Lazy(() => (UmbracoDatabase)_databaseFactory.GetDatabase()); + } + + private UmbracoDatabase CreateDatabaseInstance() + { + return string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false + ? new UmbracoDatabase(ConnectionString, ProviderName, _logger, _connectionRetryPolicy, _commandRetryPolicy) + : new UmbracoDatabase(_connectionStringName, _logger, _connectionRetryPolicy, _commandRetryPolicy); + } public UmbracoDatabase CreateDatabase() { - //no http context, create the singleton global object + // no http context, create the singleton global object if (HttpContext.Current == null) { - if (_nonHttpInstance == null) - { - lock (Locker) - { - //double check - if (_nonHttpInstance == null) - { - _nonHttpInstance = string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false - ? new UmbracoDatabase(ConnectionString, ProviderName, _logger) - : new UmbracoDatabase(_connectionStringName, _logger); - } - } - } - return _nonHttpInstance; + return _nonHttpInstance.Value; } - //we have an http context, so only create one per request - if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory)) == false) - { - HttpContext.Current.Items.Add(typeof (DefaultDatabaseFactory), - string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false - ? new UmbracoDatabase(ConnectionString, ProviderName, _logger) - : new UmbracoDatabase(_connectionStringName, _logger)); - } - return (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)]; + // we have an http context, so only create one per request + var db = HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] as UmbracoDatabase; + if (db == null) HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] = db = (UmbracoDatabase) _databaseFactory.GetDatabase(); + return db; } protected override void DisposeResources() { - if (HttpContext.Current == null) + if (HttpContext.Current == null && _nonHttpInstance.IsValueCreated) { - _nonHttpInstance.Dispose(); + _nonHttpInstance.Value.Dispose(); } else { diff --git a/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs b/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs index d6b54e4797..6327a2ac62 100644 --- a/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MacroFactory.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.Factories public IMacro BuildEntity(MacroDto dto) { var model = new Macro(dto.Id, dto.UseInEditor, dto.RefreshRate, dto.Alias, dto.Name, dto.ScriptType, dto.ScriptAssembly, dto.Xslt, dto.CacheByPage, dto.CachePersonalized, dto.DontRender, dto.MacroFilePath); - foreach (var p in dto.MacroPropertyDtos) + foreach (var p in dto.MacroPropertyDtos.EmptyNull()) { model.Properties.Add(new MacroProperty(p.Id, p.Alias, p.Name, p.SortOrder, p.EditorAlias)); } diff --git a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs index 658ddad2d4..0685432398 100644 --- a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs @@ -93,18 +93,15 @@ namespace Umbraco.Core.Persistence.Factories entity.IsDraft = dto.NewestVersion != default(Guid) && (dto.PublishedVersion == default(Guid) || dto.PublishedVersion != dto.NewestVersion); entity.HasPendingChanges = (dto.PublishedVersion != default(Guid) && dto.NewestVersion != default(Guid)) && dto.PublishedVersion != dto.NewestVersion; - if (dto.UmbracoPropertyDtos != null) + foreach (var propertyDto in dto.UmbracoPropertyDtos.EmptyNull()) { - foreach (var propertyDto in dto.UmbracoPropertyDtos) + entity.AdditionalData[propertyDto.PropertyAlias] = new UmbracoEntity.EntityProperty { - entity.AdditionalData[propertyDto.PropertyAlias] = new UmbracoEntity.EntityProperty - { - PropertyEditorAlias = propertyDto.PropertyEditorAlias, - Value = propertyDto.NTextValue.IsNullOrWhiteSpace() - ? propertyDto.NVarcharValue - : propertyDto.NTextValue.ConvertToJsonIfPossible() - }; - } + PropertyEditorAlias = propertyDto.PropertyEditorAlias, + Value = propertyDto.NTextValue.IsNullOrWhiteSpace() + ? propertyDto.NVarcharValue + : propertyDto.NTextValue.ConvertToJsonIfPossible() + }; } return entity; diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index bf6ff7d09e..bbc193d461 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -41,7 +41,7 @@ namespace Umbraco.Core.Persistence.Factories LastPasswordChangeDate = dto.LastPasswordChangeDate ?? DateTime.MinValue }; - foreach (var app in dto.User2AppDtos) + foreach (var app in dto.User2AppDtos.EmptyNull()) { user.AddAllowedSection(app.AppAlias); } diff --git a/src/Umbraco.Core/Persistence/FaultHandling/RetryDbConnection.cs b/src/Umbraco.Core/Persistence/FaultHandling/RetryDbConnection.cs new file mode 100644 index 0000000000..ee67774dc1 --- /dev/null +++ b/src/Umbraco.Core/Persistence/FaultHandling/RetryDbConnection.cs @@ -0,0 +1,222 @@ +using System; +using System.Data; +using System.Data.Common; +using NPoco; +using Transaction = System.Transactions.Transaction; + +namespace Umbraco.Core.Persistence.FaultHandling +{ + class RetryDbConnection : DbConnection + { + private DbConnection _inner; + private readonly RetryPolicy _conRetryPolicy; + private readonly RetryPolicy _cmdRetryPolicy; + + public RetryDbConnection(DbConnection connection, RetryPolicy conRetryPolicy, RetryPolicy cmdRetryPolicy) + { + _inner = connection; + _inner.StateChange += StateChangeHandler; + + _conRetryPolicy = conRetryPolicy ?? RetryPolicy.NoRetry; + _cmdRetryPolicy = cmdRetryPolicy; + } + + public DbConnection Inner { get { return _inner; } } + + public override string ConnectionString { get { return _inner.ConnectionString; } set { _inner.ConnectionString = value; } } + + protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) + { + return _inner.BeginTransaction(isolationLevel); + } + + protected override bool CanRaiseEvents + { + get { return true; } + } + + public override void ChangeDatabase(string databaseName) + { + _inner.ChangeDatabase(databaseName); + } + + public override void Close() + { + _inner.Close(); + } + + public override int ConnectionTimeout + { + get { return _inner.ConnectionTimeout; } + } + + protected override DbCommand CreateDbCommand() + { + return new FaultHandlingDbCommand(this, _inner.CreateCommand(), _cmdRetryPolicy); + } + + public override string DataSource + { + get { return _inner.DataSource; } + } + + public override string Database + { + get { return _inner.Database; } + } + + protected override void Dispose(bool disposing) + { + if (disposing && _inner != null) + { + _inner.StateChange -= StateChangeHandler; + _inner.Dispose(); + } + _inner = null; + base.Dispose(disposing); + } + + public override void EnlistTransaction(Transaction transaction) + { + _inner.EnlistTransaction(transaction); + } + + public override DataTable GetSchema() + { + return _inner.GetSchema(); + } + + public override DataTable GetSchema(string collectionName) + { + return _inner.GetSchema(collectionName); + } + + public override DataTable GetSchema(string collectionName, string[] restrictionValues) + { + return _inner.GetSchema(collectionName, restrictionValues); + } + + public override void Open() + { + _conRetryPolicy.ExecuteAction(_inner.Open); + } + + public override string ServerVersion + { + get { return _inner.ServerVersion; } + } + + public override ConnectionState State + { + get { return _inner.State; } + } + + private void StateChangeHandler(object sender, StateChangeEventArgs stateChangeEventArguments) + { + OnStateChange(stateChangeEventArguments); + } + + public void Ensure() + { + // verify whether or not the connection is valid and is open. This code may be retried therefore + // it is important to ensure that a connection is re-established should it have previously failed + if (State != ConnectionState.Open) + Open(); + } + } + + class FaultHandlingDbCommand : DbCommand + { + private RetryDbConnection _connection; + private DbCommand _inner; + private readonly RetryPolicy _cmdRetryPolicy; + + public FaultHandlingDbCommand(RetryDbConnection connection, DbCommand command, RetryPolicy cmdRetryPolicy) + { + _connection = connection; + _inner = command; + _cmdRetryPolicy = cmdRetryPolicy ?? RetryPolicy.NoRetry; + } + + protected override void Dispose(bool disposing) + { + if (disposing && _inner != null) + _inner.Dispose(); + _inner = null; + base.Dispose(disposing); + } + + public override void Cancel() + { + _inner.Cancel(); + } + + public override string CommandText { get { return _inner.CommandText; } set { _inner.CommandText = value; } } + + public override int CommandTimeout { get { return _inner.CommandTimeout; } set { _inner.CommandTimeout = value; } } + + public override CommandType CommandType { get { return _inner.CommandType; } set { _inner.CommandType = value; } } + + protected override DbConnection DbConnection + { + get + { + return _connection; + } + set + { + if (value == null) throw new ArgumentNullException("value"); + var connection = value as RetryDbConnection; + if (connection == null) throw new ArgumentException("Value is not a FaultHandlingDbConnection instance."); + if (_connection != null && _connection != connection) throw new Exception("Value is another FaultHandlingDbConnection instance."); + _connection = connection; + _inner.Connection = connection.Inner; + } + } + + protected override DbParameter CreateDbParameter() + { + return _inner.CreateParameter(); + } + + protected override DbParameterCollection DbParameterCollection + { + get { return _inner.Parameters; } + } + + protected override DbTransaction DbTransaction { get { return _inner.Transaction; } set { _inner.Transaction = value; } } + + public override bool DesignTimeVisible { get; set; } + + protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) + { + return Execute(() => _inner.ExecuteReader(behavior)); + } + + public override int ExecuteNonQuery() + { + return Execute(() => _inner.ExecuteNonQuery()); + } + + public override object ExecuteScalar() + { + return Execute(() => _inner.ExecuteScalar()); + } + + private T Execute(Func f) + { + return _cmdRetryPolicy.ExecuteAction(() => + { + _connection.Ensure(); + return f(); + }); + } + + public override void Prepare() + { + _inner.Prepare(); + } + + public override UpdateRowSource UpdatedRowSource { get { return _inner.UpdatedRowSource; } set { _inner.UpdatedRowSource = value; } } + } +} diff --git a/src/Umbraco.Core/Persistence/LockedRepository.cs b/src/Umbraco.Core/Persistence/LockedRepository.cs index b5d2d672f2..c067e84288 100644 --- a/src/Umbraco.Core/Persistence/LockedRepository.cs +++ b/src/Umbraco.Core/Persistence/LockedRepository.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; @@ -7,14 +8,14 @@ namespace Umbraco.Core.Persistence internal class LockedRepository where TRepository : IDisposable, IRepository { - public LockedRepository(Transaction transaction, IDatabaseUnitOfWork unitOfWork, TRepository repository) + public LockedRepository(ITransaction transaction, IDatabaseUnitOfWork unitOfWork, TRepository repository) { Transaction = transaction; UnitOfWork = unitOfWork; Repository = repository; } - public Transaction Transaction { get; private set; } + public ITransaction Transaction { get; private set; } public IDatabaseUnitOfWork UnitOfWork { get; private set; } public TRepository Repository { get; private set; } diff --git a/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs b/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs index ff373df2bf..7f64daa899 100644 --- a/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Linq.Expressions; using System.Reflection; +using NPoco; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Mappers diff --git a/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs b/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs index a622bcda8c..808a932670 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Persistence.Mappers public MappingResolver(IServiceContainer container, ILogger logger, Func> assignedMapperTypes) : base(container, logger, assignedMapperTypes) { - + } /// @@ -36,7 +36,7 @@ namespace Umbraco.Core.Persistence.Mappers { return _mapperCache.GetOrAdd(type, type1 => { - + //first check if we can resolve it by attribute var byAttribute = TryGetMapperByAttribute(type); @@ -54,7 +54,7 @@ namespace Umbraco.Core.Persistence.Mappers /// /// private Attempt TryGetMapperByAttribute(Type entityType) - { + { //check if any of the mappers are assigned to this type var mapper = Values.FirstOrDefault( x => x.GetType().GetCustomAttributes(false) @@ -66,7 +66,7 @@ namespace Umbraco.Core.Persistence.Mappers } return Attempt.Succeed(mapper); - } + } internal string GetMapping(Type type, string propertyName) { diff --git a/src/Umbraco.Core/Persistence/Mappers/PetaPocoMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PetaPocoMapper.cs deleted file mode 100644 index fd516073ce..0000000000 --- a/src/Umbraco.Core/Persistence/Mappers/PetaPocoMapper.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Reflection; - -namespace Umbraco.Core.Persistence.Mappers -{ - /// - /// Represents the PetaPocoMapper, which is the implementation of the IMapper interface. - /// This is currently only used to ensure that nullable dates are not saved to the database. - /// - public class PetaPocoMapper : IMapper - { - public void GetTableInfo(Type t, TableInfo ti) - { - } - - public bool MapPropertyToColumn(Type t, PropertyInfo pi, ref string columnName, ref bool resultColumn) - { - return true; - } - - public Func GetFromDbConverter(PropertyInfo pi, Type sourceType) - { - return null; - } - - public Func GetToDbConverter(Type sourceType) - { - //We need this check to ensure that PetaPoco doesn't try to insert an invalid - //date from a nullable DateTime property. - if (sourceType == typeof(DateTime)) - { - return datetimeVal => - { - var datetime = datetimeVal as DateTime?; - if (datetime.HasValue && datetime.Value > DateTime.MinValue) - return datetime.Value; - - return null; - }; - } - - return null; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Mappers/PocoMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PocoMapper.cs new file mode 100644 index 0000000000..281e05499c --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/PocoMapper.cs @@ -0,0 +1,31 @@ +using System; +using System.Reflection; +using NPoco; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Extends NPoco default mapper and ensures that nullable dates are not saved to the database. + /// + public class PocoMapper : DefaultMapper + { + public override Func GetToDbConverter(Type destType, MemberInfo sourceMemberInfo) + { + // ensures that NPoco does not try to insert an invalid date + // from a nullable DateTime property + if (sourceMemberInfo.GetMemberInfoType() == typeof(DateTime)) + { + return datetimeVal => + { + var datetime = datetimeVal as DateTime?; + if (datetime.HasValue && datetime.Value > DateTime.MinValue) + return datetime.Value; + + return null; + }; + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/IMigrationContext.cs b/src/Umbraco.Core/Persistence/Migrations/IMigrationContext.cs index b162eba305..4440e7d2e3 100644 --- a/src/Umbraco.Core/Persistence/Migrations/IMigrationContext.cs +++ b/src/Umbraco.Core/Persistence/Migrations/IMigrationContext.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using NPoco; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations diff --git a/src/Umbraco.Core/Persistence/Migrations/IMigrationExpression.cs b/src/Umbraco.Core/Persistence/Migrations/IMigrationExpression.cs index c8d36de915..4ea66b869f 100644 --- a/src/Umbraco.Core/Persistence/Migrations/IMigrationExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/IMigrationExpression.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Core.Persistence.Migrations +using NPoco; + +namespace Umbraco.Core.Persistence.Migrations { /// /// Marker interface for migration expressions diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index 7e70adb10f..66d9c8be0c 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs index f789c18aa1..e89d7367ec 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationBase.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationBase.cs index 70821d301f..966a0715c7 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationBase.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationBase.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Logging; using Umbraco.Core.Persistence.Migrations.Syntax.Alter; using Umbraco.Core.Persistence.Migrations.Syntax.Create; @@ -79,5 +80,10 @@ namespace Umbraco.Core.Persistence.Migrations { return new IfDatabaseBuilder(Context, SqlSyntax, databaseProviders); } + + protected Sql Sql() + { + return NPoco.Sql.BuilderFor(new SqlContext(SqlSyntax, Context.Database)); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationContext.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationContext.cs index c2f0b6051d..8126a402f2 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationContext.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationContext.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using NPoco; using Umbraco.Core.Logging; using Umbraco.Core.Persistence.SqlSyntax; diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationExpressionBase.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationExpressionBase.cs index 6c3ef66735..8a0e0d7063 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationExpressionBase.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationExpressionBase.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using NPoco; using Umbraco.Core.Logging; using Umbraco.Core.Persistence.SqlSyntax; diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs index 3c70cf6bcb..dbb98c877f 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Text; using log4net; +using NPoco; using Semver; using Umbraco.Core.Configuration; using Umbraco.Core.Events; @@ -51,7 +52,7 @@ namespace Umbraco.Core.Persistence.Migrations /// /// Executes the migrations against the database. /// - /// The PetaPoco Database, which the migrations will be run against + /// The NPoco Database, which the migrations will be run against /// /// /// Boolean indicating whether this is an upgrade or downgrade @@ -67,7 +68,7 @@ namespace Umbraco.Core.Persistence.Migrations ? OrderedUpgradeMigrations(foundMigrations).ToList() : OrderedDowngradeMigrations(foundMigrations).ToList(); - + if (Migrating.IsRaisedEventCancelled(new MigrationEventArgs(migrations, _currentVersion, _targetVersion, _productName, true), this)) { _logger.Warn("Migration was cancelled by an event"); @@ -164,9 +165,9 @@ namespace Umbraco.Core.Persistence.Migrations } internal MigrationContext InitializeMigrations( - List migrations, - Database database, - DatabaseProviders databaseProvider, + List migrations, + Database database, + DatabaseProviders databaseProvider, ISqlSyntaxProvider sqlSyntax, bool isUpgrade = true) { @@ -242,7 +243,7 @@ namespace Umbraco.Core.Persistence.Migrations var exeSql = sb.ToString(); _logger.Info("Executing sql statement " + i + ": " + exeSql); database.Execute(exeSql); - + //restart the string builder sb.Remove(0, sb.Length); } @@ -259,7 +260,7 @@ namespace Umbraco.Core.Persistence.Migrations database.Execute(exeSql); } } - + i++; } @@ -273,9 +274,9 @@ namespace Umbraco.Core.Persistence.Migrations var exists = _migrationEntryService.FindEntry(_productName, _targetVersion); if (exists == null) { - _migrationEntryService.CreateEntry(_productName, _targetVersion); + _migrationEntryService.CreateEntry(_productName, _targetVersion); } - + } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilder.cs index 549a4a0c66..5d8bc6b965 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/ExecuteBuilder.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence.Migrations.Syntax.Execute.Expressions; using Umbraco.Core.Persistence.SqlSyntax; diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteCodeStatementExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteCodeStatementExpression.cs index c5ab17db44..87ff16f31f 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteCodeStatementExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/Expressions/ExecuteCodeStatementExpression.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute.Expressions diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/IExecuteBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/IExecuteBuilder.cs index 46717e0a3f..1d4c8e3f55 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/IExecuteBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Execute/IExecuteBuilder.cs @@ -1,4 +1,5 @@ using System; +using NPoco; namespace Umbraco.Core.Persistence.Migrations.Syntax.Execute { diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameColumnExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameColumnExpression.cs index c99a1f95f8..db1ebde5d9 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameColumnExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Rename/Expressions/RenameColumnExpression.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence.SqlSyntax; +using NPoco; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Syntax.Rename.Expressions { diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateRelatedLinksData.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateRelatedLinksData.cs index d65fc90bcc..121d6d7ff3 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateRelatedLinksData.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/UpdateRelatedLinksData.cs @@ -9,6 +9,7 @@ using System.Web.Script.Serialization; using System.Xml; using System.Xml.Linq; using Newtonsoft.Json; +using NPoco; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; @@ -33,8 +34,8 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven { if (database != null) { - var dtSql = new Sql().Select("nodeId").From(SqlSyntax) - .Where(SqlSyntax, dto => dto.PropertyEditorAlias == Constants.PropertyEditors.RelatedLinksAlias); + var dtSql = Sql().Select("nodeId").From() + .Where(dto => dto.PropertyEditorAlias == Constants.PropertyEditors.RelatedLinksAlias); var dataTypeIds = database.Fetch(dtSql); if (!dataTypeIds.Any()) return string.Empty; diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs index fe600f6b69..c8ba7325a6 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/AddUniqueIdPropertyTypeGroupColumn.cs @@ -2,6 +2,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Persistence.DatabaseModelDefinitions; diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/EnsureContentTypeUniqueIdsAreConsistent.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/EnsureContentTypeUniqueIdsAreConsistent.cs index 4d017dd230..c040fd7349 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/EnsureContentTypeUniqueIdsAreConsistent.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/EnsureContentTypeUniqueIdsAreConsistent.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using NPoco; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; @@ -28,12 +29,12 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZer Constants.ObjectTypes.MemberTypeGuid, }; - var sql = new Sql() + var sql = NPoco.Sql.BuilderFor(new SqlContext(SqlSyntax, Context.Database)) .Select("umbracoNode.id,cmsContentType.alias,umbracoNode.nodeObjectType") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.NodeId, dto => dto.NodeId) - .WhereIn(SqlSyntax, x => x.NodeObjectType, objectTypes); + .From() + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.NodeId) + .WhereIn(x => x.NodeObjectType, objectTypes); var rows = Context.Database.Fetch(sql); diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/FixListViewMediaSortOrder.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/FixListViewMediaSortOrder.cs index 00915e1947..323248d5d5 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/FixListViewMediaSortOrder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenFourZero/FixListViewMediaSortOrder.cs @@ -1,4 +1,5 @@ using System.Linq; +using NPoco; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; @@ -17,7 +18,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFourZer public override void Up() { var mediaListviewIncludeProperties = Context.Database.Fetch( - new Sql().Select("*").From(SqlSyntax).Where(SqlSyntax, x => x.Id == -9)).FirstOrDefault(); + Sql().SelectAll().From().Where(x => x.Id == -9)).FirstOrDefault(); if (mediaListviewIncludeProperties != null) { if (mediaListviewIncludeProperties.Value.Contains("\"alias\":\"sort\"")) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeOne/UpdateUserLanguagesToIsoCode.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeOne/UpdateUserLanguagesToIsoCode.cs index 50f78ca66d..5c61c03723 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeOne/UpdateUserLanguagesToIsoCode.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeOne/UpdateUserLanguagesToIsoCode.cs @@ -1,4 +1,5 @@ using System.Linq; +using NPoco; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; @@ -18,7 +19,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeOn public override void Up() { - var userData = Context.Database.Fetch(new Sql().Select("*").From(SqlSyntax)); + var userData = Context.Database.Fetch(Sql().SelectAll().From()); foreach (var user in userData.Where(x => x.UserLanguage.Contains("_"))) { var languageParts = user.UserLanguage.Split('_'); diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeTwo/EnsureMigrationsTableIdentityIsCorrect.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeTwo/EnsureMigrationsTableIdentityIsCorrect.cs index 91b4bd6438..06a3995c36 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeTwo/EnsureMigrationsTableIdentityIsCorrect.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeTwo/EnsureMigrationsTableIdentityIsCorrect.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using NPoco; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; @@ -25,7 +26,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeTw List migrations = null; Execute.Code(db => { - migrations = Context.Database.Fetch(new Sql().Select("*").From(SqlSyntax)); + migrations = Context.Database.Fetch(Sql().SelectAll().From()); return string.Empty; }); Delete.FromTable("umbracoMigration").AllRows(); diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs index eea56031d4..7bc94cdaba 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/AddForeignKeysForLanguageAndDictionaryTables.cs @@ -1,6 +1,7 @@ using System; using System.Data; using System.Linq; +using NPoco; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; @@ -45,7 +46,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe .WithColumn("languageISOCode").AsString(10).Nullable() .WithColumn("languageCultureName").AsString(50).Nullable(); - var currentData = this.Context.Database.Fetch(new Sql().Select("*").From(SqlSyntax)); + var currentData = Context.Database.Fetch(Sql().SelectAll().From()); foreach (var languageDto in currentData) { Insert.IntoTable("umbracoLanguage_TEMP") diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateAndRemoveTemplateMasterColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateAndRemoveTemplateMasterColumn.cs index 8ef10b178f..c5ca312123 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateAndRemoveTemplateMasterColumn.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MigrateAndRemoveTemplateMasterColumn.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; @@ -74,7 +75,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe Execute.Code(database => { //NOTE: we are using dynamic because we need to get the data in a column that no longer exists in the schema - var templates = database.Fetch(new Sql().Select("*").From(SqlSyntax)); + var templates = database.Fetch(Sql().SelectAll().From()); foreach (var template in templates) { var sql = string.Format(SqlSyntax.UpdateData, diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs index de7c4e09aa..19d44ba3f9 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenThreeZero/MovePublicAccessXmlDataToDb.cs @@ -2,6 +2,7 @@ using System; using System.IO; using System.Linq; using System.Xml.Linq; +using NPoco; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -25,7 +26,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe if (tables.InvariantContains("umbracoAccess")) { //don't run if data is already there. - var dataExists = Context.Database.Fetch(new Sql().Select("*").From(SqlSyntax)); + var dataExists = Context.Database.Fetch(Sql().SelectAll().From()); if (dataExists.Any()) return; } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AddChangeDocumentTypePermission.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AddChangeDocumentTypePermission.cs index 6c4f6ea97b..3b82a02c47 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AddChangeDocumentTypePermission.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AddChangeDocumentTypePermission.cs @@ -1,4 +1,5 @@ using System.Linq; +using NPoco; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; @@ -34,7 +35,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero if (adminUserType.DefaultPermissions.Contains("7") == false) { adminUserType.DefaultPermissions = adminUserType.DefaultPermissions + "7"; - database.Save(adminUserType); + database.Save(adminUserType); } } @@ -50,7 +51,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero if (adminUserType.DefaultPermissions.Contains("7")) { adminUserType.DefaultPermissions = adminUserType.DefaultPermissions.Replace("7", ""); - database.Save(adminUserType); + database.Save(adminUserType); } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/UpdateToNewMemberPropertyAliases.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/UpdateToNewMemberPropertyAliases.cs index a0b64a9830..311672f237 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/UpdateToNewMemberPropertyAliases.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/UpdateToNewMemberPropertyAliases.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using NPoco; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs index 9a2b47d79d..b96aedaca2 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixZeroOne/UpdatePropertyTypesAndGroups.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using NPoco; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Rdbms; diff --git a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs similarity index 87% rename from src/Umbraco.Core/Persistence/PetaPocoExtensions.cs rename to src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs index 4c8d72ceec..b7e3407176 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs +++ b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions.cs @@ -1,331 +1,337 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Data.SqlClient; -using System.Linq; -using System.Text.RegularExpressions; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence -{ - public static class PetaPocoExtensions - { - // NOTE - // - // proper way to do it with TSQL and SQLCE - // IF EXISTS (SELECT ... FROM table WITH (UPDLOCK,HOLDLOCK)) WHERE ...) - // BEGIN - // UPDATE table SET ... WHERE ... - // END - // ELSE - // BEGIN - // INSERT INTO table (...) VALUES (...) - // END - // - // works in READ COMMITED, TSQL & SQLCE lock the constraint even if it does not exist, so INSERT is OK - // - // proper way to do it with MySQL - // IF EXISTS (SELECT ... FROM table WHERE ... FOR UPDATE) - // BEGIN - // UPDATE table SET ... WHERE ... - // END - // ELSE - // BEGIN - // INSERT INTO table (...) VALUES (...) - // END - // - // MySQL locks the constraint ONLY if it exists, so INSERT may fail... - // in theory, happens in READ COMMITTED but not REPEATABLE READ - // http://www.percona.com/blog/2012/08/28/differences-between-read-committed-and-repeatable-read-transaction-isolation-levels/ - // but according to - // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html - // it won't work for exact index value (only ranges) so really... - // - // MySQL should do - // INSERT INTO table (...) VALUES (...) ON DUPLICATE KEY UPDATE ... - // - // also the lock is released when the transaction is committed - // not sure if that can have unexpected consequences on our code? - // - // so... for the time being, let's do with that somewhat crazy solution below... - - /// - /// Safely inserts a record, or updates if it exists, based on a unique constraint. - /// - /// - /// - /// The action that executed, either an insert or an update. If an insert occurred and a PK value got generated, the poco object - /// passed in will contain the updated value. - /// - /// We cannot rely on database-specific options such as MySql ON DUPLICATE KEY UPDATE or MSSQL MERGE WHEN MATCHED because SQLCE - /// does not support any of them. Ideally this should be achieved with proper transaction isolation levels but that would mean revisiting - /// isolation levels globally. We want to keep it simple for the time being and manage it manually. - /// We handle it by trying to update, then insert, etc. until something works, or we get bored. - /// Note that with proper transactions, if T2 begins after T1 then we are sure that the database will contain T2's value - /// once T1 and T2 have completed. Whereas here, it could contain T1's value. - /// - internal static RecordPersistenceType InsertOrUpdate(this Database db, T poco) - where T : class - { - return db.InsertOrUpdate(poco, null, null); - } - - /// - /// Safely inserts a record, or updates if it exists, based on a unique constraint. - /// - /// - /// - /// - /// If the entity has a composite key they you need to specify the update command explicitly - /// The action that executed, either an insert or an update. If an insert occurred and a PK value got generated, the poco object - /// passed in will contain the updated value. - /// - /// We cannot rely on database-specific options such as MySql ON DUPLICATE KEY UPDATE or MSSQL MERGE WHEN MATCHED because SQLCE - /// does not support any of them. Ideally this should be achieved with proper transaction isolation levels but that would mean revisiting - /// isolation levels globally. We want to keep it simple for the time being and manage it manually. - /// We handle it by trying to update, then insert, etc. until something works, or we get bored. - /// Note that with proper transactions, if T2 begins after T1 then we are sure that the database will contain T2's value - /// once T1 and T2 have completed. Whereas here, it could contain T1's value. - /// - internal static RecordPersistenceType InsertOrUpdate(this Database db, - T poco, - string updateCommand, - object updateArgs) - where T : class - { - if (poco == null) - throw new ArgumentNullException("poco"); - - // try to update - var rowCount = updateCommand.IsNullOrWhiteSpace() - ? db.Update(poco) - : db.Update(updateCommand, updateArgs); - if (rowCount > 0) - return RecordPersistenceType.Update; - - // failed: does not exist, need to insert - // RC1 race cond here: another thread may insert a record with the same constraint - - var i = 0; - while (i++ < 4) - { - try - { - // try to insert - db.Insert(poco); - return RecordPersistenceType.Insert; - } - catch (SqlException) // TODO: need to find out if all db will throw that exception - probably OK - { - // failed: exists (due to race cond RC1) - // RC2 race cond here: another thread may remove the record - - // try to update - rowCount = updateCommand.IsNullOrWhiteSpace() - ? db.Update(poco) - : db.Update(updateCommand, updateArgs); - if (rowCount > 0) - return RecordPersistenceType.Update; - - // failed: does not exist (due to race cond RC2), need to insert - // loop - } - } - - // this can go on forever... have to break at some point and report an error. - throw new DataException("Record could not be inserted or updated."); - } - - /// - /// This will escape single @ symbols for peta poco values so it doesn't think it's a parameter - /// - /// - /// - public static string EscapeAtSymbols(string value) - { - if (value.Contains("@")) - { - //this fancy regex will only match a single @ not a double, etc... - var regex = new Regex("(?(this Database db, ISqlSyntaxProvider sqlSyntax, IEnumerable collection) - { - //don't do anything if there are no records. - if (collection.Any() == false) - return; - - using (var tr = db.GetTransaction()) - { - db.BulkInsertRecords(sqlSyntax, collection, tr, true); - } - } - - /// - /// Performs the bulk insertion in the context of a current transaction with an optional parameter to complete the transaction - /// when finished - /// - /// - /// - /// - /// - /// - /// - public static void BulkInsertRecords(this Database db, ISqlSyntaxProvider sqlSyntax, IEnumerable collection, Transaction tr, bool commitTrans = false) - { - //don't do anything if there are no records. - if (collection.Any() == false) - return; - - try - { - //if it is sql ce or it is a sql server version less than 2008, we need to do individual inserts. - var sqlServerSyntax = sqlSyntax as SqlServerSyntaxProvider; - if ((sqlServerSyntax != null && (int)sqlServerSyntax.VersionName.Value < (int)SqlServerVersionName.V2008) - || sqlSyntax is SqlCeSyntaxProvider) - { - //SqlCe doesn't support bulk insert statements! - - foreach (var poco in collection) - { - db.Insert(poco); - } - } - else - { - string[] sqlStatements; - var cmds = db.GenerateBulkInsertCommand(collection, db.Connection, out sqlStatements); - for (var i = 0; i < sqlStatements.Length; i++) - { - using (var cmd = cmds[i]) - { - cmd.CommandText = sqlStatements[i]; - cmd.ExecuteNonQuery(); - } - } - } - - if (commitTrans) - { - tr.Complete(); - } - } - catch - { - if (commitTrans) - { - tr.Dispose(); - } - throw; - } - } - - /// - /// Creates a bulk insert command - /// - /// - /// - /// - /// - /// - /// Sql commands with populated command parameters required to execute the sql statement - /// - /// The limits for number of parameters are 2100 (in sql server, I think there's many more allowed in mysql). So - /// we need to detect that many params and split somehow. - /// For some reason the 2100 limit is not actually allowed even though the exception from sql server mentions 2100 as a max, perhaps it is 2099 - /// that is max. I've reduced it to 2000 anyways. - /// - internal static IDbCommand[] GenerateBulkInsertCommand( - this Database db, - IEnumerable collection, - IDbConnection connection, - out string[] sql) - { - //A filter used below a few times to get all columns except result cols and not the primary key if it is auto-incremental - Func, bool> includeColumn = (data, column) => - { - if (column.Value.ResultColumn) return false; - if (data.TableInfo.AutoIncrement && column.Key == data.TableInfo.PrimaryKey) return false; - return true; - }; - - var pd = Database.PocoData.ForType(typeof(T)); - var tableName = db.EscapeTableName(pd.TableInfo.TableName); - - //get all columns to include and format for sql - var cols = string.Join(", ", - pd.Columns - .Where(c => includeColumn(pd, c)) - .Select(c => tableName + "." + db.EscapeSqlIdentifier(c.Key)).ToArray()); - - var itemArray = collection.ToArray(); - - //calculate number of parameters per item - var paramsPerItem = pd.Columns.Count(i => includeColumn(pd, i)); - - //Example calc: - // Given: we have 4168 items in the itemArray, each item contains 8 command parameters (values to be inserterted) - // 2100 / 8 = 262.5 - // Math.Floor(2100 / 8) = 262 items per trans - // 4168 / 262 = 15.908... = there will be 16 trans in total - - //all items will be included if we have disabled db parameters - var itemsPerTrans = Math.Floor(2000.00 / paramsPerItem); - //there will only be one transaction if we have disabled db parameters - var numTrans = Math.Ceiling(itemArray.Length / itemsPerTrans); - - var sqlQueries = new List(); - var commands = new List(); - - for (var tIndex = 0; tIndex < numTrans; tIndex++) - { - var itemsForTrans = itemArray - .Skip(tIndex * (int)itemsPerTrans) - .Take((int)itemsPerTrans); - - var cmd = db.CreateCommand(connection, ""); - var pocoValues = new List(); - var index = 0; - foreach (var poco in itemsForTrans) - { - var values = new List(); - //get all columns except result cols and not the primary key if it is auto-incremental - foreach (var i in pd.Columns.Where(x => includeColumn(pd, x))) - { - db.AddParam(cmd, i.Value.GetValue(poco), "@"); - values.Add(string.Format("{0}{1}", "@", index++)); - } - pocoValues.Add("(" + string.Join(",", values.ToArray()) + ")"); - } - - var sqlResult = string.Format("INSERT INTO {0} ({1}) VALUES {2}", tableName, cols, string.Join(", ", pocoValues)); - sqlQueries.Add(sqlResult); - commands.Add(cmd); - } - - sql = sqlQueries.ToArray(); - - return commands.ToArray(); - } - - public static void TruncateTable(this Database db, ISqlSyntaxProvider sqlSyntax, string tableName) - { - var sql = new Sql(string.Format( - sqlSyntax.TruncateTable, - sqlSyntax.GetQuotedTableName(tableName))); - db.Execute(sql); - } - - - } - - +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Data.SqlClient; +using System.Linq; +using System.Text.RegularExpressions; +using NPoco; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence +{ + /// + /// Provides extension methods to NPoco Database class. + /// + public static class NPocoDatabaseExtensions + { + // NOTE + // + // proper way to do it with TSQL and SQLCE + // IF EXISTS (SELECT ... FROM table WITH (UPDLOCK,HOLDLOCK)) WHERE ...) + // BEGIN + // UPDATE table SET ... WHERE ... + // END + // ELSE + // BEGIN + // INSERT INTO table (...) VALUES (...) + // END + // + // works in READ COMMITED, TSQL & SQLCE lock the constraint even if it does not exist, so INSERT is OK + // + // proper way to do it with MySQL + // IF EXISTS (SELECT ... FROM table WHERE ... FOR UPDATE) + // BEGIN + // UPDATE table SET ... WHERE ... + // END + // ELSE + // BEGIN + // INSERT INTO table (...) VALUES (...) + // END + // + // MySQL locks the constraint ONLY if it exists, so INSERT may fail... + // in theory, happens in READ COMMITTED but not REPEATABLE READ + // http://www.percona.com/blog/2012/08/28/differences-between-read-committed-and-repeatable-read-transaction-isolation-levels/ + // but according to + // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html + // it won't work for exact index value (only ranges) so really... + // + // MySQL should do + // INSERT INTO table (...) VALUES (...) ON DUPLICATE KEY UPDATE ... + // + // also the lock is released when the transaction is committed + // not sure if that can have unexpected consequences on our code? + // + // so... for the time being, let's do with that somewhat crazy solution below... + + /// + /// Safely inserts a record, or updates if it exists, based on a unique constraint. + /// + /// + /// + /// The action that executed, either an insert or an update. If an insert occurred and a PK value got generated, the poco object + /// passed in will contain the updated value. + /// + /// We cannot rely on database-specific options such as MySql ON DUPLICATE KEY UPDATE or MSSQL MERGE WHEN MATCHED because SQLCE + /// does not support any of them. Ideally this should be achieved with proper transaction isolation levels but that would mean revisiting + /// isolation levels globally. We want to keep it simple for the time being and manage it manually. + /// We handle it by trying to update, then insert, etc. until something works, or we get bored. + /// Note that with proper transactions, if T2 begins after T1 then we are sure that the database will contain T2's value + /// once T1 and T2 have completed. Whereas here, it could contain T1's value. + /// + internal static RecordPersistenceType InsertOrUpdate(this Database db, T poco) + where T : class + { + return db.InsertOrUpdate(poco, null, null); + } + + /// + /// Safely inserts a record, or updates if it exists, based on a unique constraint. + /// + /// + /// + /// + /// If the entity has a composite key they you need to specify the update command explicitly + /// The action that executed, either an insert or an update. If an insert occurred and a PK value got generated, the poco object + /// passed in will contain the updated value. + /// + /// We cannot rely on database-specific options such as MySql ON DUPLICATE KEY UPDATE or MSSQL MERGE WHEN MATCHED because SQLCE + /// does not support any of them. Ideally this should be achieved with proper transaction isolation levels but that would mean revisiting + /// isolation levels globally. We want to keep it simple for the time being and manage it manually. + /// We handle it by trying to update, then insert, etc. until something works, or we get bored. + /// Note that with proper transactions, if T2 begins after T1 then we are sure that the database will contain T2's value + /// once T1 and T2 have completed. Whereas here, it could contain T1's value. + /// + internal static RecordPersistenceType InsertOrUpdate(this Database db, + T poco, + string updateCommand, + object updateArgs) + where T : class + { + if (poco == null) + throw new ArgumentNullException("poco"); + + // try to update + var rowCount = updateCommand.IsNullOrWhiteSpace() + ? db.Update(poco) + : db.Update(updateCommand, updateArgs); + if (rowCount > 0) + return RecordPersistenceType.Update; + + // failed: does not exist, need to insert + // RC1 race cond here: another thread may insert a record with the same constraint + + var i = 0; + while (i++ < 4) + { + try + { + // try to insert + db.Insert(poco); + return RecordPersistenceType.Insert; + } + catch (SqlException) // TODO: need to find out if all db will throw that exception - probably OK + { + // failed: exists (due to race cond RC1) + // RC2 race cond here: another thread may remove the record + + // try to update + rowCount = updateCommand.IsNullOrWhiteSpace() + ? db.Update(poco) + : db.Update(updateCommand, updateArgs); + if (rowCount > 0) + return RecordPersistenceType.Update; + + // failed: does not exist (due to race cond RC2), need to insert + // loop + } + } + + // this can go on forever... have to break at some point and report an error. + throw new DataException("Record could not be inserted or updated."); + } + + /// + /// This will escape single @ symbols for npoco values so it doesn't think it's a parameter + /// + /// + /// + public static string EscapeAtSymbols(string value) + { + if (value.Contains("@")) + { + //this fancy regex will only match a single @ not a double, etc... + var regex = new Regex("(?(this Database db, ISqlSyntaxProvider sqlSyntax, IEnumerable collection) + { + //don't do anything if there are no records. + if (collection.Any() == false) + return; + + using (var tr = db.GetTransaction()) + { + db.BulkInsertRecords(sqlSyntax, collection, tr, true); + } + } + + /// + /// Performs the bulk insertion in the context of a current transaction with an optional parameter to complete the transaction + /// when finished + /// + /// + /// + /// + /// + /// + /// + public static void BulkInsertRecords(this Database db, ISqlSyntaxProvider sqlSyntax, IEnumerable collection, ITransaction tr, bool commitTrans = false) + { + //don't do anything if there are no records. + if (collection.Any() == false) + return; + + try + { + //if it is sql ce or it is a sql server version less than 2008, we need to do individual inserts. + var sqlServerSyntax = sqlSyntax as SqlServerSyntaxProvider; + if ((sqlServerSyntax != null && (int)sqlServerSyntax.VersionName.Value < (int)SqlServerVersionName.V2008) + || sqlSyntax is SqlCeSyntaxProvider) + { + //SqlCe doesn't support bulk insert statements! + + foreach (var poco in collection) + { + db.Insert(poco); + } + } + else + { + string[] sqlStatements; + var cmds = db.GenerateBulkInsertCommand(collection, db.Connection, out sqlStatements); + for (var i = 0; i < sqlStatements.Length; i++) + { + using (var cmd = cmds[i]) + { + cmd.CommandText = sqlStatements[i]; + cmd.ExecuteNonQuery(); + } + } + } + + if (commitTrans) + { + tr.Complete(); + } + } + catch + { + if (commitTrans) + { + tr.Dispose(); + } + throw; + } + } + + /// + /// Creates a bulk insert command + /// + /// + /// + /// + /// + /// + /// Sql commands with populated command parameters required to execute the sql statement + /// + /// The limits for number of parameters are 2100 (in sql server, I think there's many more allowed in mysql). So + /// we need to detect that many params and split somehow. + /// For some reason the 2100 limit is not actually allowed even though the exception from sql server mentions 2100 as a max, perhaps it is 2099 + /// that is max. I've reduced it to 2000 anyways. + /// + internal static IDbCommand[] GenerateBulkInsertCommand( + this Database db, + IEnumerable collection, + DbConnection connection, + out string[] sql) + { + //A filter used below a few times to get all columns except result cols and not the primary key if it is auto-incremental + Func, bool> includeColumn = (data, column) => + { + if (column.Value.ResultColumn) return false; + if (data.TableInfo.AutoIncrement && column.Key == data.TableInfo.PrimaryKey) return false; + return true; + }; + + var pd = db.PocoDataFactory.ForType(typeof(T)); + var tableName = db.DatabaseType.EscapeTableName(pd.TableInfo.TableName); + + //get all columns to include and format for sql + var cols = string.Join(", ", + pd.Columns + .Where(c => includeColumn(pd, c)) + .Select(c => tableName + "." + db.DatabaseType.EscapeSqlIdentifier(c.Key)).ToArray()); + + var itemArray = collection.ToArray(); + + //calculate number of parameters per item + var paramsPerItem = pd.Columns.Count(i => includeColumn(pd, i)); + + //Example calc: + // Given: we have 4168 items in the itemArray, each item contains 8 command parameters (values to be inserterted) + // 2100 / 8 = 262.5 + // Math.Floor(2100 / 8) = 262 items per trans + // 4168 / 262 = 15.908... = there will be 16 trans in total + + //all items will be included if we have disabled db parameters + var itemsPerTrans = Math.Floor(2000.00 / paramsPerItem); + //there will only be one transaction if we have disabled db parameters + var numTrans = Math.Ceiling(itemArray.Length / itemsPerTrans); + + var sqlQueries = new List(); + var commands = new List(); + + for (var tIndex = 0; tIndex < numTrans; tIndex++) + { + var itemsForTrans = itemArray + .Skip(tIndex * (int)itemsPerTrans) + .Take((int)itemsPerTrans); + + var cmd = db.CreateCommand(connection, ""); + var pocoValues = new List(); + var index = 0; + foreach (var poco in itemsForTrans) + { + var values = new List(); + //get all columns except result cols and not the primary key if it is auto-incremental + var prefix = db.DatabaseType.GetParameterPrefix(cmd.Connection.ConnectionString); + foreach (var i in pd.Columns.Where(x => includeColumn(pd, x))) + { + db.AddParameter(cmd, i.Value.GetValue(poco)); + values.Add(prefix + index++); + } + pocoValues.Add("(" + string.Join(",", values.ToArray()) + ")"); + } + + var sqlResult = string.Format("INSERT INTO {0} ({1}) VALUES {2}", tableName, cols, string.Join(", ", pocoValues)); + sqlQueries.Add(sqlResult); + commands.Add(cmd); + } + + sql = sqlQueries.ToArray(); + + return commands.ToArray(); + } + + public static void TruncateTable(this Database db, ISqlSyntaxProvider sqlSyntax, string tableName) + { + var sql = new Sql(string.Format( + sqlSyntax.TruncateTable, + sqlSyntax.GetQuotedTableName(tableName))); + db.Execute(sql); + } + + public static IsolationLevel GetCurrentTransactionIsolationLevel(this Database database) + { + var transaction = database.Transaction; + return transaction == null ? IsolationLevel.Unspecified : transaction.IsolationLevel; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs deleted file mode 100644 index a174255746..0000000000 --- a/src/Umbraco.Core/Persistence/PetaPoco.cs +++ /dev/null @@ -1,2504 +0,0 @@ -/* PetaPoco v4.0.3 - A Tiny ORMish thing for your POCO's. - * Copyright © 2011 Topten Software. All Rights Reserved. - * - * Apache License 2.0 - http://www.toptensoftware.com/petapoco/license - * - * Special thanks to Rob Conery (@robconery) for original inspiration (ie:Massive) and for - * use of Subsonic's T4 templates, Rob Sullivan (@DataChomp) for hard core DBA advice - * and Adam Schroder (@schotime) for lots of suggestions, improvements and Oracle support - */ - -// Define PETAPOCO_NO_DYNAMIC in your project settings on .NET 3.5 - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Caching; -using System.Security; -using System.Security.Permissions; -using System.Text; -using System.Configuration; -using System.Data.Common; -using System.Data; -using System.Text.RegularExpressions; -using System.Reflection; -using System.Reflection.Emit; -using System.Linq.Expressions; - -namespace Umbraco.Core.Persistence -{ - // Poco's marked [Explicit] require all column properties to be marked - [AttributeUsage(AttributeTargets.Class)] - public class ExplicitColumnsAttribute : Attribute - { - } - // For non-explicit pocos, causes a property to be ignored - [AttributeUsage(AttributeTargets.Property)] - public class IgnoreAttribute : Attribute - { - } - - // For explicit pocos, marks property as a column and optionally supplies column name - [AttributeUsage(AttributeTargets.Property)] - public class ColumnAttribute : Attribute - { - public ColumnAttribute() { } - public ColumnAttribute(string name) { Name = name; } - public string Name { get; set; } - } - - // For explicit pocos, marks property as a result column and optionally supplies column name - [AttributeUsage(AttributeTargets.Property)] - public class ResultColumnAttribute : ColumnAttribute - { - public ResultColumnAttribute() { } - public ResultColumnAttribute(string name) : base(name) { } - } - - // Specify the table name of a poco - [AttributeUsage(AttributeTargets.Class)] - public class TableNameAttribute : Attribute - { - public TableNameAttribute(string tableName) - { - Value = tableName; - } - public string Value { get; private set; } - } - - // Specific the primary key of a poco class (and optional sequence name for Oracle) - [AttributeUsage(AttributeTargets.Class)] - public class PrimaryKeyAttribute : Attribute - { - public PrimaryKeyAttribute(string primaryKey) - { - Value = primaryKey; - autoIncrement = true; - } - - public string Value { get; private set; } - public string sequenceName { get; set; } - public bool autoIncrement { get; set; } - } - - [AttributeUsage(AttributeTargets.Property)] - public class AutoJoinAttribute : Attribute - { - public AutoJoinAttribute() { } - } - - // Results from paged request - public class Page - { - public long CurrentPage { get; set; } - public long TotalPages { get; set; } - public long TotalItems { get; set; } - public long ItemsPerPage { get; set; } - public List Items { get; set; } - public object Context { get; set; } - } - - // Pass as parameter value to force to DBType.AnsiString - public class AnsiString - { - public AnsiString(string str) - { - Value = str; - } - public string Value { get; private set; } - } - - // Used by IMapper to override table bindings for an object - public class TableInfo - { - public string TableName { get; set; } - public string PrimaryKey { get; set; } - public bool AutoIncrement { get; set; } - public string SequenceName { get; set; } - } - - // Optionally provide an implementation of this to Database.Mapper - public interface IMapper - { - void GetTableInfo(Type t, TableInfo ti); - bool MapPropertyToColumn(Type t, PropertyInfo pi, ref string columnName, ref bool resultColumn); - Func GetFromDbConverter(PropertyInfo pi, Type SourceType); - Func GetToDbConverter(Type SourceType); - } - - // This will be merged with IMapper in the next major version - public interface IMapper2 : IMapper - { - Func GetFromDbConverter(Type DestType, Type SourceType); - } - - // Database class ... this is where most of the action happens - public class Database : IDisposable - { - public Database(IDbConnection connection) - { - _sharedConnection = connection; - _connectionString = connection.ConnectionString; - _sharedConnectionDepth = 2; // Prevent closing external connection - CommonConstruct(); - } - - public Database(string connectionString, string providerName) - { - _connectionString = connectionString; - _providerName = providerName; - CommonConstruct(); - } - - public Database(string connectionString, DbProviderFactory provider) - { - _connectionString = connectionString; - _factory = provider; - CommonConstruct(); - } - - public Database(string connectionStringName) - { - // Use first? - if (connectionStringName == "") - connectionStringName = ConfigurationManager.ConnectionStrings[0].Name; - - // Work out connection string and provider name - var providerName = "System.Data.SqlClient"; - if (ConfigurationManager.ConnectionStrings[connectionStringName] != null) - { - if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName)) - providerName = ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName; - } - else - { - throw new InvalidOperationException("Can't find a connection string with the name '" + connectionStringName + "'"); - } - - // Store factory and connection string - _connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; - _providerName = providerName; - CommonConstruct(); - } - - enum DBType - { - SqlServer, - SqlServerCE, - MySql, - PostgreSQL, - Oracle, - SQLite - } - DBType _dbType = DBType.SqlServer; - - // Common initialization - private void CommonConstruct() - { - _transactionDepth = 0; - EnableAutoSelect = true; - EnableNamedParams = true; - ForceDateTimesToUtc = true; - - if (_providerName != null) - _factory = DbProviderFactories.GetFactory(_providerName); - - string dbtype = (_factory == null ? _sharedConnection.GetType() : _factory.GetType()).Name; - - if (dbtype.StartsWith("MySql")) _dbType = DBType.MySql; - else if (dbtype.StartsWith("SqlCe")) _dbType = DBType.SqlServerCE; - else if (dbtype.StartsWith("Npgsql")) _dbType = DBType.PostgreSQL; - else if (dbtype.StartsWith("Oracle")) _dbType = DBType.Oracle; - else if (dbtype.StartsWith("SQLite")) _dbType = DBType.SQLite; - - if (_dbType == DBType.MySql && _connectionString != null && _connectionString.IndexOf("Allow User Variables=true") >= 0) - _paramPrefix = "?"; - if (_dbType == DBType.Oracle) - _paramPrefix = ":"; - - // by default use MSSQL default ReadCommitted level - //TODO change to RepeatableRead - but that is breaking - _isolationLevel = IsolationLevel.ReadCommitted; - } - - // Automatically close one open shared connection - public void Dispose() - { - // Automatically close one open connection reference - // (Works with KeepConnectionAlive and manually opening a shared connection) - CloseSharedConnection(); - } - - // Set to true to keep the first opened connection alive until this object is disposed - public bool KeepConnectionAlive { get; set; } - - // Open a connection (can be nested) - public void OpenSharedConnection() - { - if (_sharedConnectionDepth == 0) - { - _sharedConnection = _factory.CreateConnection(); - _sharedConnection.ConnectionString = _connectionString; - _sharedConnection.OpenWithRetry();//Changed .Open() => .OpenWithRetry() extension method - - // ensure we have the proper isolation level, as levels can leak in pools - // read http://stackoverflow.com/questions/9851415/sql-server-isolation-level-leaks-across-pooled-connections - // and http://stackoverflow.com/questions/641120/what-exec-sp-reset-connection-shown-in-sql-profiler-means - // - // NPoco has that code in OpenSharedConnectionImp (commented out?) - //using (var cmd = _sharedConnection.CreateCommand()) - //{ - // cmd.CommandText = GetSqlForTransactionLevel(_isolationLevel); - // cmd.CommandTimeout = CommandTimeout; - // cmd.ExecuteNonQuery(); - //} - // - // although MSDN documentation for SQL CE clearly states that the above method - // should work, it fails & reports an error parsing the query on 'TRANSACTION', - // and Google is no help (others have faced the same issue... no solution). So, - // switching to another method that does work on all databases. - var tr = _sharedConnection.BeginTransaction(_isolationLevel); - tr.Commit(); - tr.Dispose(); - - _sharedConnection = OnConnectionOpened(_sharedConnection); - - if (KeepConnectionAlive) - _sharedConnectionDepth++; // Make sure you call Dispose - } - _sharedConnectionDepth++; - } - - // Close a previously opened connection - public void CloseSharedConnection() - { - if (_sharedConnectionDepth > 0) - { - _sharedConnectionDepth--; - if (_sharedConnectionDepth == 0) - { - OnConnectionClosing(_sharedConnection); - _sharedConnection.Dispose(); - _sharedConnection = null; - } - } - } - - // Access to our shared connection - public IDbConnection Connection - { - get { return _sharedConnection; } - } - - // Helper to create a transaction scope - public Transaction GetTransaction() - { - return GetTransaction(_isolationLevel); - } - - public Transaction GetTransaction(IsolationLevel isolationLevel) - { - return new Transaction(this, isolationLevel); - } - - public IsolationLevel CurrentTransactionIsolationLevel - { - get { return _transaction == null ? IsolationLevel.Unspecified : _transaction.IsolationLevel; } - } - - // Use by derived repo generated by T4 templates - public virtual void OnBeginTransaction() { } - public virtual void OnEndTransaction() { } - - // Start a new transaction, can be nested, every call must be - // matched by a call to AbortTransaction or CompleteTransaction - // Use `using (var scope=db.Transaction) { scope.Complete(); }` to ensure correct semantics - public void BeginTransaction() - { - BeginTransaction(_isolationLevel); - } - - public void BeginTransaction(IsolationLevel isolationLevel) - { - _transactionDepth++; - - if (_transactionDepth == 1) - { - OpenSharedConnection(); - _transaction = _sharedConnection.BeginTransaction(isolationLevel); - _transactionCancelled = false; - OnBeginTransaction(); - } - else if (isolationLevel > _transaction.IsolationLevel) - throw new Exception("Already in a transaction with a lower isolation level."); - } - - // Internal helper to cleanup transaction stuff - void CleanupTransaction() - { - OnEndTransaction(); - - if (_transactionCancelled) - _transaction.Rollback(); - else - _transaction.Commit(); - - _transaction.Dispose(); - _transaction = null; - - CloseSharedConnection(); - } - - // Abort the entire outer most transaction scope - public void AbortTransaction() - { - _transactionCancelled = true; - //TODO what shall we do if transactionDepth is already zero? - if ((--_transactionDepth) == 0) - CleanupTransaction(); - } - - // Complete the transaction - public void CompleteTransaction() - { - //TODO what shall we do if transactionDepth is already zero? - if ((--_transactionDepth) == 0) - CleanupTransaction(); - } - - // in NPoco this is in DatabaseType - private static string GetSqlForTransactionLevel(IsolationLevel isolationLevel) - { - switch (isolationLevel) - { - case IsolationLevel.ReadCommitted: - return "SET TRANSACTION ISOLATION LEVEL READ COMMITTED"; - case IsolationLevel.ReadUncommitted: - return "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED"; - case IsolationLevel.RepeatableRead: - return "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ"; - case IsolationLevel.Serializable: - return "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"; - case IsolationLevel.Snapshot: - return "SET TRANSACTION ISOLATION LEVEL SNAPSHOT"; - default: - return "SET TRANSACTION ISOLATION LEVEL READ COMMITTED"; - } - } - - // Helper to handle named parameters from object properties - static Regex rxParams = new Regex(@"(? args_dest) - { - return rxParams.Replace(_sql, m => - { - string param = m.Value.Substring(1); - - object arg_val; - - int paramIndex; - if (int.TryParse(param, out paramIndex)) - { - // Numbered parameter - if (paramIndex < 0 || paramIndex >= args_src.Length) - throw new ArgumentOutOfRangeException(string.Format("Parameter '@{0}' specified but only {1} parameters supplied (in `{2}`)", paramIndex, args_src.Length, _sql)); - arg_val = args_src[paramIndex]; - } - else - { - // Look for a property on one of the arguments with this name - bool found = false; - arg_val = null; - foreach (var o in args_src) - { - var pi = o.GetType().GetProperty(param); - if (pi != null) - { - arg_val = pi.GetValue(o, null); - found = true; - break; - } - } - - if (!found) - throw new ArgumentException(string.Format("Parameter '@{0}' specified but none of the passed arguments have a property with this name (in '{1}')", param, _sql)); - } - - // Expand collections to parameter lists - if ((arg_val as System.Collections.IEnumerable) != null && - (arg_val as string) == null && - (arg_val as byte[]) == null) - { - var sb = new StringBuilder(); - foreach (var i in arg_val as System.Collections.IEnumerable) - { - sb.Append((sb.Length == 0 ? "@" : ",@") + args_dest.Count.ToString()); - args_dest.Add(i); - } - return sb.ToString(); - } - else - { - args_dest.Add(arg_val); - return "@" + (args_dest.Count - 1).ToString(); - } - } - ); - } - - // Add a parameter to a DB command - internal void AddParam(IDbCommand cmd, object item, string ParameterPrefix) - { - // Convert value to from poco type to db type - if (Database.Mapper != null && item!=null) - { - var fn = Database.Mapper.GetToDbConverter(item.GetType()); - if (fn!=null) - item = fn(item); - } - - // Support passed in parameters - var idbParam = item as IDbDataParameter; - if (idbParam != null) - { - idbParam.ParameterName = string.Format("{0}{1}", ParameterPrefix, cmd.Parameters.Count); - cmd.Parameters.Add(idbParam); - return; - } - - var p = cmd.CreateParameter(); - p.ParameterName = string.Format("{0}{1}", ParameterPrefix, cmd.Parameters.Count); - if (item == null) - { - p.Value = DBNull.Value; - } - else - { - var t = item.GetType(); - if (t.IsEnum) // PostgreSQL .NET driver wont cast enum to int - { - p.Value = (int)item; - } - else if (t == typeof(Guid)) - { - p.Value = item.ToString(); - p.DbType = DbType.String; - p.Size = 40; - } - else if (t == typeof(string)) - { - // out of memory exception occurs if trying to save more than 4000 characters to SQL Server CE NText column. - //Set before attempting to set Size, or Size will always max out at 4000 - if ((item as string).Length + 1 > 4000 && p.GetType().Name == "SqlCeParameter") - p.GetType().GetProperty("SqlDbType").SetValue(p, SqlDbType.NText, null); - - p.Size = (item as string).Length + 1; - if(p.Size < 4000) - p.Size = Math.Max((item as string).Length + 1, 4000); // Help query plan caching by using common size - - p.Value = item; - } - else if (t == typeof(AnsiString)) - { - // Thanks @DataChomp for pointing out the SQL Server indexing performance hit of using wrong string type on varchar - p.Size = Math.Max((item as AnsiString).Value.Length + 1, 4000); - p.Value = (item as AnsiString).Value; - p.DbType = DbType.AnsiString; - } - else if (t == typeof(bool) && _dbType != DBType.PostgreSQL) - { - p.Value = ((bool)item) ? 1 : 0; - } - else if (item.GetType().Name == "SqlGeography") //SqlGeography is a CLR Type - { - p.GetType().GetProperty("UdtTypeName").SetValue(p, "geography", null); //geography is the equivalent SQL Server Type - p.Value = item; - } - - else if (item.GetType().Name == "SqlGeometry") //SqlGeometry is a CLR Type - { - p.GetType().GetProperty("UdtTypeName").SetValue(p, "geometry", null); //geography is the equivalent SQL Server Type - p.Value = item; - } - else - { - p.Value = item; - } - } - - cmd.Parameters.Add(p); - } - - // Create a command - static Regex rxParamsPrefix = new Regex(@"(?(); - sql = ProcessParams(sql, args, new_args); - args = new_args.ToArray(); - } - - // Perform parameter prefix replacements - if (_paramPrefix != "@") - sql = rxParamsPrefix.Replace(sql, m => _paramPrefix + m.Value.Substring(1)); - sql = sql.Replace("@@", "@"); // <- double @@ escapes a single @ - - // Create the command and add parameters - IDbCommand cmd = connection.CreateCommand(); - cmd.Connection = connection; - cmd.CommandText = sql; - cmd.Transaction = _transaction; - foreach (var item in args) - { - AddParam(cmd, item, _paramPrefix); - } - - if (_dbType == DBType.Oracle) - { - cmd.GetType().GetProperty("BindByName").SetValue(cmd, true, null); - } - - if (!String.IsNullOrEmpty(sql)) - DoPreExecute(cmd); - - return cmd; - } - - // Override this to log/capture exceptions - public virtual void OnException(Exception x) - { - System.Diagnostics.Debug.WriteLine(x.ToString()); - System.Diagnostics.Debug.WriteLine(LastCommand); - } - - // Override this to log commands, or modify command before execution - public virtual IDbConnection OnConnectionOpened(IDbConnection conn) { return conn; } - public virtual void OnConnectionClosing(IDbConnection conn) { } - public virtual void OnExecutingCommand(IDbCommand cmd) { } - public virtual void OnExecutedCommand(IDbCommand cmd) { } - - // Execute a non-query command - public int Execute(string sql, params object[] args) - { - try - { - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, sql, args)) - { - var retv=cmd.ExecuteNonQueryWithRetry(); - OnExecutedCommand(cmd); - return retv; - } - } - finally - { - CloseSharedConnection(); - } - } - catch (Exception x) - { - OnException(x); - throw; - } - } - - public int Execute(Sql sql) - { - return Execute(sql.SQL, sql.Arguments); - } - - // Execute and cast a scalar property - public T ExecuteScalar(string sql, params object[] args) - { - try - { - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, sql, args)) - { - object val = cmd.ExecuteScalarWithRetry(); - OnExecutedCommand(cmd); - - if (val == null || val == DBNull.Value) - return default(T); - - Type t = typeof(T); - Type u = Nullable.GetUnderlyingType(t); - - return (T)Convert.ChangeType(val, u ?? t); - } - } - finally - { - CloseSharedConnection(); - } - } - catch (Exception x) - { - OnException(x); - throw; - } - } - - public T ExecuteScalar(Sql sql) - { - return ExecuteScalar(sql.SQL, sql.Arguments); - } - - Regex rxSelect = new Regex(@"\A\s*(SELECT|EXECUTE|CALL)\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline); - Regex rxFrom = new Regex(@"\A\s*FROM\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline); - string AddSelectClause(string sql) - { - if (sql.StartsWith(";")) - return sql.Substring(1); - - if (!rxSelect.IsMatch(sql)) - { - var pd = PocoData.ForType(typeof(T)); - var tableName = EscapeTableName(pd.TableInfo.TableName); - string cols = string.Join(", ", (from c in pd.QueryColumns select tableName + "." + EscapeSqlIdentifier(c)).ToArray()); - if (!rxFrom.IsMatch(sql)) - sql = string.Format("SELECT {0} FROM {1} {2}", cols, tableName, sql); - else - sql = string.Format("SELECT {0} {1}", cols, sql); - } - return sql; - } - - public bool EnableAutoSelect { get; set; } - public bool EnableNamedParams { get; set; } - public bool ForceDateTimesToUtc { get; set; } - - // Return a typed list of pocos - public List Fetch(string sql, params object[] args) - { - return Query(sql, args).ToList(); - } - - public List Fetch(Sql sql) - { - return Fetch(sql.SQL, sql.Arguments); - } - - static Regex rxColumns = new Regex(@"\A\s*SELECT\s+((?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?(?:\s*,\s*(?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); - static Regex rxDistinct = new Regex(@"\ADISTINCT\s", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); - public static bool SplitSqlForPaging(string sql, out string sqlCount, out string sqlSelectRemoved, out string sqlOrderBy) - { - sqlSelectRemoved = null; - sqlCount = null; - sqlOrderBy = null; - - // Extract the columns from "SELECT FROM" - var m = rxColumns.Match(sql); - if (!m.Success) - return false; - - // Save column list and replace with COUNT(*) - Group g = m.Groups[1]; - sqlSelectRemoved = sql.Substring(g.Index); - - if (rxDistinct.IsMatch(sqlSelectRemoved)) - sqlCount = sql.Substring(0, g.Index) + "COUNT(" + m.Groups[1].ToString().Trim() + ") " + sql.Substring(g.Index + g.Length); - else - sqlCount = sql.Substring(0, g.Index) + "COUNT(*) " + sql.Substring(g.Index + g.Length); - - - // Look for an "ORDER BY " clause - m = rxOrderBy.Match(sqlCount); - if (!m.Success) - { - sqlOrderBy = null; - } - else - { - g = m.Groups[0]; - sqlOrderBy = g.ToString(); - sqlCount = sqlCount.Substring(0, g.Index) + sqlCount.Substring(g.Index + g.Length); - } - - return true; - } - - public void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage) - { - // Add auto select clause - if (EnableAutoSelect) - sql = AddSelectClause(sql); - - // Split the SQL into the bits we need - string sqlSelectRemoved, sqlOrderBy; - if (!SplitSqlForPaging(sql, out sqlCount, out sqlSelectRemoved, out sqlOrderBy)) - throw new Exception("Unable to parse SQL statement for paged query"); - if (_dbType == DBType.Oracle && sqlSelectRemoved.StartsWith("*")) - throw new Exception("Query must alias '*' when performing a paged query.\neg. select t.* from table t order by t.id"); - - // Build the SQL for the actual final result - if (_dbType == DBType.SqlServer || _dbType == DBType.Oracle) - { - sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, ""); - if (rxDistinct.IsMatch(sqlSelectRemoved)) - { - sqlSelectRemoved = "peta_inner.* FROM (SELECT " + sqlSelectRemoved + ") peta_inner"; - } - sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>@{2} AND peta_rn<=@{3}", - sqlOrderBy==null ? "ORDER BY (SELECT NULL)" : sqlOrderBy, sqlSelectRemoved, args.Length, args.Length + 1); - args = args.Concat(new object[] { skip, skip+take }).ToArray(); - } - else if (_dbType == DBType.SqlServerCE) - { - sqlPage = string.Format("{0}\nOFFSET @{1} ROWS FETCH NEXT @{2} ROWS ONLY", sql, args.Length, args.Length + 1); - args = args.Concat(new object[] { skip, take }).ToArray(); - } - else - { - sqlPage = string.Format("{0}\nLIMIT @{1} OFFSET @{2}", sql, args.Length, args.Length + 1); - args = args.Concat(new object[] { take, skip }).ToArray(); - } - - } - - // Fetch a page - public Page Page(long page, long itemsPerPage, string sql, params object[] args) - { - string sqlCount, sqlPage; - BuildPageQueries((page-1)*itemsPerPage, itemsPerPage, sql, ref args, out sqlCount, out sqlPage); - - // Save the one-time command time out and use it for both queries - int saveTimeout = OneTimeCommandTimeout; - - // Setup the paged result - var result = new Page(); - result.CurrentPage = page; - result.ItemsPerPage = itemsPerPage; - result.TotalItems = ExecuteScalar(sqlCount, args); - result.TotalPages = result.TotalItems / itemsPerPage; - if ((result.TotalItems % itemsPerPage) != 0) - result.TotalPages++; - - OneTimeCommandTimeout = saveTimeout; - - // Get the records - result.Items = Fetch(sqlPage, args); - - // Done - return result; - } - - public Page Page(long page, long itemsPerPage, Sql sql) - { - return Page(page, itemsPerPage, sql.SQL, sql.Arguments); - } - - - public List Fetch(long page, long itemsPerPage, string sql, params object[] args) - { - return SkipTake((page - 1) * itemsPerPage, itemsPerPage, sql, args); - } - - public List Fetch(long page, long itemsPerPage, Sql sql) - { - return SkipTake((page - 1) * itemsPerPage, itemsPerPage, sql.SQL, sql.Arguments); - } - - public List SkipTake(long skip, long take, string sql, params object[] args) - { - string sqlCount, sqlPage; - BuildPageQueries(skip, take, sql, ref args, out sqlCount, out sqlPage); - return Fetch(sqlPage, args); - } - - public List SkipTake(long skip, long take, Sql sql) - { - return SkipTake(skip, take, sql.SQL, sql.Arguments); - } - - // Return an enumerable collection of pocos - public IEnumerable Query(string sql, params object[] args) - { - if (EnableAutoSelect) - sql = AddSelectClause(sql); - - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, sql, args)) - { - IDataReader r; - var pd = PocoData.ForType(typeof(T)); - try - { - r = cmd.ExecuteReaderWithRetry(); - OnExecutedCommand(cmd); - } - catch (Exception x) - { - OnException(x); - throw; - } - var factory = pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, ForceDateTimesToUtc, 0, r.FieldCount, r) as Func; - using (r) - { - while (true) - { - T poco; - try - { - if (!r.Read()) - yield break; - poco = factory(r); - } - catch (Exception x) - { - OnException(x); - throw; - } - - yield return poco; - } - } - } - } - finally - { - CloseSharedConnection(); - } - } - - // Multi Fetch - public List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); } - public List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); } - public List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); } - public List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); } - - // Multi Query - public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2) }, cb, sql, args); } - public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3)}, cb, sql, args); } - public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4)}, cb, sql, args); } - public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, cb, sql, args); } - - // Multi Fetch (SQL builder) - public List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); } - public List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); } - public List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); } - public List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); } - - // Multi Query (SQL builder) - public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2) }, cb, sql.SQL, sql.Arguments); } - public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql.SQL, sql.Arguments); } - public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql.SQL, sql.Arguments); } - public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, cb, sql.SQL, sql.Arguments); } - - // Multi Fetch (Simple) - public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } - public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } - public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } - public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } - - // Multi Query (Simple) - public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2) }, null, sql, args); } - public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql, args); } - public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql, args); } - public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, null, sql, args); } - - // Multi Fetch (Simple) (SQL builder) - public List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); } - public List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); } - public List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); } - public List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); } - - // Multi Query (Simple) (SQL builder) - public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2) }, null, sql.SQL, sql.Arguments); } - public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql.SQL, sql.Arguments); } - public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql.SQL, sql.Arguments); } - public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, null, sql.SQL, sql.Arguments); } - - // Automagically guess the property relationships between various POCOs and create a delegate that will set them up - Delegate GetAutoMapper(Type[] types) - { - // Build a key - var kb = new StringBuilder(); - foreach (var t in types) - { - kb.Append(t.ToString()); - kb.Append(":"); - } - var key = kb.ToString(); - - // Check cache - RWLock.EnterReadLock(); - try - { - Delegate mapper; - if (AutoMappers.TryGetValue(key, out mapper)) - return mapper; - } - finally - { - RWLock.ExitReadLock(); - } - - // Create it - RWLock.EnterWriteLock(); - try - { - // Try again - Delegate mapper; - if (AutoMappers.TryGetValue(key, out mapper)) - return mapper; - - // Create a method - var m = new DynamicMethod("petapoco_automapper", types[0], types, true); - var il = m.GetILGenerator(); - - for (int i = 1; i < types.Length; i++) - { - bool handled = false; - for (int j = i - 1; j >= 0; j--) - { - // Find the property - var candidates = from p in types[j].GetProperties() where p.PropertyType == types[i] select p; - if (candidates.Any() == false) - continue; - if (candidates.Count() > 1) - throw new InvalidOperationException(string.Format("Can't auto join {0} as {1} has more than one property of type {0}", types[i], types[j])); - - // Generate code - il.Emit(OpCodes.Ldarg_S, j); - il.Emit(OpCodes.Ldarg_S, i); - il.Emit(OpCodes.Callvirt, candidates.First().GetSetMethod(true)); - handled = true; - } - - if (!handled) - throw new InvalidOperationException(string.Format("Can't auto join {0}", types[i])); - } - - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ret); - - // Cache it - var del = m.CreateDelegate(Expression.GetFuncType(types.Concat(types.Take(1)).ToArray())); - AutoMappers.Add(key, del); - return del; - } - finally - { - RWLock.ExitWriteLock(); - } - } - - // Find the split point in a result set for two different pocos and return the poco factory for the first - Delegate FindSplitPoint(Type typeThis, Type typeNext, string sql, IDataReader r, ref int pos) - { - // Last? - if (typeNext == null) - return PocoData.ForType(typeThis).GetFactory(sql, _sharedConnection.ConnectionString, ForceDateTimesToUtc, pos, r.FieldCount - pos, r); - - // Get PocoData for the two types - PocoData pdThis = PocoData.ForType(typeThis); - PocoData pdNext = PocoData.ForType(typeNext); - - // Find split point - int firstColumn = pos; - var usedColumns = new Dictionary(); - for (; pos < r.FieldCount; pos++) - { - // Split if field name has already been used, or if the field doesn't exist in current poco but does in the next - string fieldName = r.GetName(pos); - if (usedColumns.ContainsKey(fieldName) || (!pdThis.Columns.ContainsKey(fieldName) && pdNext.Columns.ContainsKey(fieldName))) - { - return pdThis.GetFactory(sql, _sharedConnection.ConnectionString, ForceDateTimesToUtc, firstColumn, pos - firstColumn, r); - } - usedColumns.Add(fieldName, true); - } - - throw new InvalidOperationException(string.Format("Couldn't find split point between {0} and {1}", typeThis, typeNext)); - } - - - // Instance data used by the Multipoco factory delegate - essentially a list of the nested poco factories to call - public class MultiPocoFactory - { - - public MultiPocoFactory(IEnumerable dels) - { - Delegates = new List(dels); - } - private List Delegates { get; set; } - private Delegate GetItem(int index) { return Delegates[index]; } - - /// - /// Calls the delegate at the specified index and returns its values - /// - /// - /// - /// - private object CallDelegate(int index, IDataReader reader) - { - var d = GetItem(index); - var output = d.DynamicInvoke(reader); - return output; - } - - /// - /// Calls the callback delegate and passes in the output of all delegates as the parameters - /// - /// - /// - /// - /// - /// - public TRet CallCallback(Delegate callback, IDataReader dr, int count) - { - var args = new List(); - for(var i = 0;i CreateMultiPocoFactory(Type[] types, string sql, IDataReader r) - { - // Call each delegate - var dels = new List(); - int pos = 0; - for (int i=0; i mpFactory.CallCallback(arg3, reader, types.Length); - } - - // Various cached stuff - static Dictionary MultiPocoFactories = new Dictionary(); - static Dictionary AutoMappers = new Dictionary(); - static System.Threading.ReaderWriterLockSlim RWLock = new System.Threading.ReaderWriterLockSlim(); - - // Get (or create) the multi-poco factory for a query - Func GetMultiPocoFactory(Type[] types, string sql, IDataReader r) - { - // Build a key string (this is crap, should address this at some point) - var kb = new StringBuilder(); - kb.Append(typeof(TRet).ToString()); - kb.Append(":"); - foreach (var t in types) - { - kb.Append(":"); - kb.Append(t.ToString()); - } - kb.Append(":"); kb.Append(_sharedConnection.ConnectionString); - kb.Append(":"); kb.Append(ForceDateTimesToUtc); - kb.Append(":"); kb.Append(sql); - string key = kb.ToString(); - - // Check cache - RWLock.EnterReadLock(); - try - { - object oFactory; - if (MultiPocoFactories.TryGetValue(key, out oFactory)) - { - //mpFactory = oFactory; - return (Func)oFactory; - } - } - finally - { - RWLock.ExitReadLock(); - } - - // Cache it - RWLock.EnterWriteLock(); - try - { - // Check again - object oFactory; ; - if (MultiPocoFactories.TryGetValue(key, out oFactory)) - { - return (Func)oFactory; - } - - // Create the factory - var factory = CreateMultiPocoFactory(types, sql, r); - - MultiPocoFactories.Add(key, factory); - return factory; - } - finally - { - RWLock.ExitWriteLock(); - } - - } - - // Actual implementation of the multi-poco query - public IEnumerable Query(Type[] types, Delegate cb, string sql, params object[] args) - { - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, sql, args)) - { - IDataReader r; - try - { - r = cmd.ExecuteReaderWithRetry(); - OnExecutedCommand(cmd); - } - catch (Exception x) - { - OnException(x); - throw; - } - var factory = GetMultiPocoFactory(types, sql, r); - if (cb == null) - cb = GetAutoMapper(types.ToArray()); - bool bNeedTerminator=false; - using (r) - { - while (true) - { - TRet poco; - try - { - if (!r.Read()) - break; - poco = factory(r, cb); - } - catch (Exception x) - { - OnException(x); - throw; - } - - if (poco != null) - yield return poco; - else - bNeedTerminator = true; - } - if (bNeedTerminator) - { - var poco = (TRet)(cb as Delegate).DynamicInvoke(new object[types.Length]); - if (poco != null) - yield return poco; - else - yield break; - } - } - } - } - finally - { - CloseSharedConnection(); - } - } - - - public IEnumerable Query(Sql sql) - { - return Query(sql.SQL, sql.Arguments); - } - - public bool Exists(object primaryKey) - { - return FirstOrDefault(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey) != null; - } - public T Single(object primaryKey) - { - return Single(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey); - } - public T SingleOrDefault(object primaryKey) - { - return SingleOrDefault(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey); - } - public T Single(string sql, params object[] args) - { - return Query(sql, args).Single(); - } - public T SingleOrDefault(string sql, params object[] args) - { - return Query(sql, args).SingleOrDefault(); - } - public T First(string sql, params object[] args) - { - return Query(sql, args).First(); - } - public T FirstOrDefault(string sql, params object[] args) - { - return Query(sql, args).FirstOrDefault(); - } - - public T Single(Sql sql) - { - return Query(sql).Single(); - } - public T SingleOrDefault(Sql sql) - { - return Query(sql).SingleOrDefault(); - } - public T First(Sql sql) - { - return Query(sql).First(); - } - public T FirstOrDefault(Sql sql) - { - return Query(sql).FirstOrDefault(); - } - - public string EscapeTableName(string str) - { - // Assume table names with "dot", or opening sq is already escaped - return str.IndexOf('.') >= 0 ? str : EscapeSqlIdentifier(str); - } - public string EscapeSqlIdentifier(string str) - { - switch (_dbType) - { - case DBType.MySql: - return string.Format("`{0}`", str); - - case DBType.PostgreSQL: - case DBType.Oracle: - return string.Format("\"{0}\"", str); - - default: - return string.Format("[{0}]", str); - } - } - - public object Insert(string tableName, string primaryKeyName, object poco) - { - return Insert(tableName, primaryKeyName, true, poco); - } - - // Insert a poco into a table. If the poco has a property with the same name - // as the primary key the id of the new record is assigned to it. Either way, - // the new id is returned. - public object Insert(string tableName, string primaryKeyName, bool autoIncrement, object poco) - { - try - { - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, "")) - { - var pd = PocoData.ForObject(poco, primaryKeyName); - var names = new List(); - var values = new List(); - var index = 0; - foreach (var i in pd.Columns) - { - // Don't insert result columns - if (i.Value.ResultColumn) - continue; - - // Don't insert the primary key (except under oracle where we need bring in the next sequence value) - if (autoIncrement && primaryKeyName != null && string.Compare(i.Key, primaryKeyName, true)==0) - { - if (_dbType == DBType.Oracle && !string.IsNullOrEmpty(pd.TableInfo.SequenceName)) - { - names.Add(i.Key); - values.Add(string.Format("{0}.nextval", pd.TableInfo.SequenceName)); - } - continue; - } - - names.Add(EscapeSqlIdentifier(i.Key)); - values.Add(string.Format("{0}{1}", _paramPrefix, index++)); - AddParam(cmd, i.Value.GetValue(poco), _paramPrefix); - } - - cmd.CommandText = string.Format("INSERT INTO {0} ({1}) VALUES ({2})", - EscapeTableName(tableName), - string.Join(",", names.ToArray()), - string.Join(",", values.ToArray()) - ); - - if (!autoIncrement) - { - DoPreExecute(cmd); - cmd.ExecuteNonQueryWithRetry(); - OnExecutedCommand(cmd); - return true; - } - - - object id; - switch (_dbType) - { - case DBType.SqlServerCE: - DoPreExecute(cmd); - cmd.ExecuteNonQueryWithRetry(); - OnExecutedCommand(cmd); - id = ExecuteScalar("SELECT @@@IDENTITY AS NewID;"); - break; - case DBType.SqlServer: - cmd.CommandText += ";\nSELECT SCOPE_IDENTITY() AS NewID;"; - DoPreExecute(cmd); - id = cmd.ExecuteScalarWithRetry(); - OnExecutedCommand(cmd); - break; - case DBType.PostgreSQL: - if (primaryKeyName != null) - { - cmd.CommandText += string.Format("returning {0} as NewID", EscapeSqlIdentifier(primaryKeyName)); - DoPreExecute(cmd); - id = cmd.ExecuteScalarWithRetry(); - } - else - { - id = -1; - DoPreExecute(cmd); - cmd.ExecuteNonQueryWithRetry(); - } - OnExecutedCommand(cmd); - break; - case DBType.Oracle: - if (primaryKeyName != null) - { - cmd.CommandText += string.Format(" returning {0} into :newid", EscapeSqlIdentifier(primaryKeyName)); - var param = cmd.CreateParameter(); - param.ParameterName = ":newid"; - param.Value = DBNull.Value; - param.Direction = ParameterDirection.ReturnValue; - param.DbType = DbType.Int64; - cmd.Parameters.Add(param); - DoPreExecute(cmd); - cmd.ExecuteNonQueryWithRetry(); - id = param.Value; - } - else - { - id = -1; - DoPreExecute(cmd); - cmd.ExecuteNonQueryWithRetry(); - } - OnExecutedCommand(cmd); - break; - case DBType.SQLite: - if (primaryKeyName != null) - { - cmd.CommandText += ";\nSELECT last_insert_rowid();"; - DoPreExecute(cmd); - id = cmd.ExecuteScalarWithRetry(); - } - else - { - id = -1; - DoPreExecute(cmd); - cmd.ExecuteNonQueryWithRetry(); - } - OnExecutedCommand(cmd); - break; - default: - cmd.CommandText += ";\nSELECT @@IDENTITY AS NewID;"; - DoPreExecute(cmd); - id = cmd.ExecuteScalarWithRetry(); - OnExecutedCommand(cmd); - break; - } - - - // Assign the ID back to the primary key property - if (primaryKeyName != null) - { - PocoColumn pc; - if (pd.Columns.TryGetValue(primaryKeyName, out pc)) - { - pc.SetValue(poco, pc.ChangeType(id)); - } - } - - return id; - } - } - finally - { - CloseSharedConnection(); - } - } - catch (Exception x) - { - OnException(x); - throw; - } - } - - // Insert an annotated poco object - public object Insert(object poco) - { - var pd = PocoData.ForType(poco.GetType()); - return Insert(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, pd.TableInfo.AutoIncrement, poco); - } - - public int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue) - { - return Update(tableName, primaryKeyName, poco, primaryKeyValue, null); - } - - - // Update a record with values from a poco. primary key value can be either supplied or read from the poco - public int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns) - { - try - { - OpenSharedConnection(); - try - { - using (var cmd = CreateCommand(_sharedConnection, "")) - { - var sb = new StringBuilder(); - var index = 0; - var pd = PocoData.ForObject(poco,primaryKeyName); - if (columns == null) - { - foreach (var i in pd.Columns) - { - // Don't update the primary key, but grab the value if we don't have it - if (string.Compare(i.Key, primaryKeyName, true) == 0) - { - if (primaryKeyValue == null) - primaryKeyValue = i.Value.GetValue(poco); - continue; - } - - // Dont update result only columns - if (i.Value.ResultColumn) - continue; - - // Build the sql - if (index > 0) - sb.Append(", "); - sb.AppendFormat("{0} = {1}{2}", EscapeSqlIdentifier(i.Key), _paramPrefix, index++); - - // Store the parameter in the command - AddParam(cmd, i.Value.GetValue(poco), _paramPrefix); - } - } - else - { - foreach (var colname in columns) - { - var pc = pd.Columns[colname]; - - // Build the sql - if (index > 0) - sb.Append(", "); - sb.AppendFormat("{0} = {1}{2}", EscapeSqlIdentifier(colname), _paramPrefix, index++); - - // Store the parameter in the command - AddParam(cmd, pc.GetValue(poco), _paramPrefix); - } - - // Grab primary key value - if (primaryKeyValue == null) - { - var pc = pd.Columns[primaryKeyName]; - primaryKeyValue = pc.GetValue(poco); - } - - } - - cmd.CommandText = string.Format("UPDATE {0} SET {1} WHERE {2} = {3}{4}", - EscapeTableName(tableName), sb.ToString(), EscapeSqlIdentifier(primaryKeyName), _paramPrefix, index++); - AddParam(cmd, primaryKeyValue, _paramPrefix); - - DoPreExecute(cmd); - - // Do it - var retv=cmd.ExecuteNonQueryWithRetry(); - OnExecutedCommand(cmd); - return retv; - } - } - finally - { - CloseSharedConnection(); - } - } - catch (Exception x) - { - OnException(x); - throw; - } - } - - public int Update(string tableName, string primaryKeyName, object poco) - { - return Update(tableName, primaryKeyName, poco, null); - } - - public int Update(string tableName, string primaryKeyName, object poco, IEnumerable columns) - { - return Update(tableName, primaryKeyName, poco, null, columns); - } - - public int Update(object poco, IEnumerable columns) - { - return Update(poco, null, columns); - } - - public int Update(object poco) - { - return Update(poco, null, null); - } - - public int Update(object poco, object primaryKeyValue) - { - return Update(poco, primaryKeyValue, null); - } - public int Update(object poco, object primaryKeyValue, IEnumerable columns) - { - var pd = PocoData.ForType(poco.GetType()); - return Update(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco, primaryKeyValue, columns); - } - - public int Update(string sql, params object[] args) - { - var pd = PocoData.ForType(typeof(T)); - return Execute(string.Format("UPDATE {0} {1}", EscapeTableName(pd.TableInfo.TableName), sql), args); - } - - public int Update(Sql sql) - { - var pd = PocoData.ForType(typeof(T)); - return Execute(new Sql(string.Format("UPDATE {0}", EscapeTableName(pd.TableInfo.TableName))).Append(sql)); - } - - public int Delete(string tableName, string primaryKeyName, object poco) - { - return Delete(tableName, primaryKeyName, poco, null); - } - - public int Delete(string tableName, string primaryKeyName, object poco, object primaryKeyValue) - { - // If primary key value not specified, pick it up from the object - if (primaryKeyValue == null) - { - var pd = PocoData.ForObject(poco,primaryKeyName); - PocoColumn pc; - if (pd.Columns.TryGetValue(primaryKeyName, out pc)) - { - primaryKeyValue = pc.GetValue(poco); - } - } - - // Do it - var sql = string.Format("DELETE FROM {0} WHERE {1}=@0", EscapeTableName(tableName), EscapeSqlIdentifier(primaryKeyName)); - return Execute(sql, primaryKeyValue); - } - - public int Delete(object poco) - { - var pd = PocoData.ForType(poco.GetType()); - return Delete(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco); - } - - public int Delete(object pocoOrPrimaryKey) - { - if (pocoOrPrimaryKey.GetType() == typeof(T)) - return Delete(pocoOrPrimaryKey); - var pd = PocoData.ForType(typeof(T)); - return Delete(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, null, pocoOrPrimaryKey); - } - - public int Delete(string sql, params object[] args) - { - var pd = PocoData.ForType(typeof(T)); - return Execute(string.Format("DELETE FROM {0} {1}", EscapeTableName(pd.TableInfo.TableName), sql), args); - } - - public int Delete(Sql sql) - { - var pd = PocoData.ForType(typeof(T)); - return Execute(new Sql(string.Format("DELETE FROM {0}", EscapeTableName(pd.TableInfo.TableName))).Append(sql)); - } - - // Check if a poco represents a new record - public bool IsNew(string primaryKeyName, object poco) - { - var pd = PocoData.ForObject(poco, primaryKeyName); - object pk; - PocoColumn pc; - if (pd.Columns.TryGetValue(primaryKeyName, out pc)) - { - pk = pc.GetValue(poco); - } -#if !PETAPOCO_NO_DYNAMIC - else if (poco.GetType() == typeof(System.Dynamic.ExpandoObject)) - { - return true; - } -#endif - else - { - var pi = poco.GetType().GetProperty(primaryKeyName); - if (pi == null) - throw new ArgumentException(string.Format("The object doesn't have a property matching the primary key column name '{0}'", primaryKeyName)); - pk = pi.GetValue(poco, null); - } - - if (pk == null) - return true; - - var type = pk.GetType(); - - if (type.IsValueType) - { - // Common primary key types - if (type == typeof(long)) - return (long)pk == 0; - else if (type == typeof(ulong)) - return (ulong)pk == 0; - else if (type == typeof(int)) - return (int)pk == 0; - else if (type == typeof(uint)) - return (uint)pk == 0; - - // Create a default instance and compare - return pk == Activator.CreateInstance(pk.GetType()); - } - else - { - return pk == null; - } - } - - public bool IsNew(object poco) - { - var pd = PocoData.ForType(poco.GetType()); - if (!pd.TableInfo.AutoIncrement) - throw new InvalidOperationException("IsNew() and Save() are only supported on tables with auto-increment/identity primary key columns"); - return IsNew(pd.TableInfo.PrimaryKey, poco); - } - - // Insert new record or Update existing record - public void Save(string tableName, string primaryKeyName, object poco) - { - if (IsNew(primaryKeyName, poco)) - { - Insert(tableName, primaryKeyName, true, poco); - } - else - { - Update(tableName, primaryKeyName, poco); - } - } - - public void Save(object poco) - { - var pd = PocoData.ForType(poco.GetType()); - Save(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco); - } - - public int CommandTimeout { get; set; } - public int OneTimeCommandTimeout { get; set; } - - void DoPreExecute(IDbCommand cmd) - { - // Setup command timeout - if (OneTimeCommandTimeout != 0) - { - cmd.CommandTimeout = OneTimeCommandTimeout; - OneTimeCommandTimeout = 0; - } - else if (CommandTimeout!=0) - { - cmd.CommandTimeout = CommandTimeout; - } - - // Call hook - OnExecutingCommand(cmd); - - // Save it - _lastSql = cmd.CommandText; - _lastArgs = (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray(); - } - - public string LastSQL { get { return _lastSql; } } - public object[] LastArgs { get { return _lastArgs; } } - public string LastCommand - { - get { return FormatCommand(_lastSql, _lastArgs); } - } - - public string FormatCommand(IDbCommand cmd) - { - return FormatCommand(cmd.CommandText, (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray()); - } - - public string FormatCommand(string sql, object[] args) - { - var sb = new StringBuilder(); - if (sql == null) - return ""; - sb.Append(sql); - if (args != null && args.Length > 0) - { - sb.Append("\n"); - for (int i = 0; i < args.Length; i++) - { - sb.AppendFormat("\t -> {0}{1} [{2}] = \"{3}\"\n", _paramPrefix, i, args[i].GetType().Name, args[i]); - } - sb.Remove(sb.Length - 1, 1); - } - return sb.ToString(); - } - - - public static IMapper Mapper - { - get; - set; - } - - public class PocoColumn - { - public string ColumnName; - public PropertyInfo PropertyInfo; - public bool ResultColumn; - public virtual void SetValue(object target, object val) { PropertyInfo.SetValue(target, val, null); } - public virtual object GetValue(object target) { return PropertyInfo.GetValue(target, null); } - public virtual object ChangeType(object val) { return Convert.ChangeType(val, PropertyInfo.PropertyType); } - } - public class ExpandoColumn : PocoColumn - { - public override void SetValue(object target, object val) { (target as IDictionary)[ColumnName]=val; } - public override object GetValue(object target) - { - object val=null; - (target as IDictionary).TryGetValue(ColumnName, out val); - return val; - } - public override object ChangeType(object val) { return val; } - } - - /// - /// Container for a Memory cache object - /// - /// - /// Better to have one memory cache instance than many so it's memory management can be handled more effectively - /// http://stackoverflow.com/questions/8463962/using-multiple-instances-of-memorycache - /// - internal class ManagedCache - { - public ObjectCache GetCache() - { - return ObjectCache; - } - - static readonly ObjectCache ObjectCache = new MemoryCache("NPoco"); - - } - - public class PocoData - { - //USE ONLY FOR TESTING - internal static bool UseLongKeys = false; - //USE ONLY FOR TESTING - default is one hr - internal static int SlidingExpirationSeconds = 3600; - - public static PocoData ForObject(object o, string primaryKeyName) - { - var t = o.GetType(); -#if !PETAPOCO_NO_DYNAMIC - if (t == typeof(System.Dynamic.ExpandoObject)) - { - var pd = new PocoData(); - pd.TableInfo = new TableInfo(); - pd.Columns = new Dictionary(StringComparer.OrdinalIgnoreCase); - pd.Columns.Add(primaryKeyName, new ExpandoColumn() { ColumnName = primaryKeyName }); - pd.TableInfo.PrimaryKey = primaryKeyName; - pd.TableInfo.AutoIncrement = true; - foreach (var col in (o as IDictionary).Keys) - { - if (col!=primaryKeyName) - pd.Columns.Add(col, new ExpandoColumn() { ColumnName = col }); - } - return pd; - } - else -#endif - return ForType(t); - } - - public static PocoData ForType(Type t) - { -#if !PETAPOCO_NO_DYNAMIC - if (t == typeof(System.Dynamic.ExpandoObject)) - throw new InvalidOperationException("Can't use dynamic types with this method"); -#endif - // Check cache - InnerLock.EnterReadLock(); - PocoData pd; - try - { - if (m_PocoDatas.TryGetValue(t, out pd)) - return pd; - } - finally - { - InnerLock.ExitReadLock(); - } - - - // Cache it - InnerLock.EnterWriteLock(); - try - { - // Check again - if (m_PocoDatas.TryGetValue(t, out pd)) - return pd; - - // Create it - pd = new PocoData(t); - - m_PocoDatas.Add(t, pd); - } - finally - { - InnerLock.ExitWriteLock(); - } - - return pd; - } - - public PocoData() - { - } - - public PocoData(Type t) - { - type = t; - TableInfo=new TableInfo(); - - // Get the table name - var a = t.GetCustomAttributes(typeof(TableNameAttribute), true); - TableInfo.TableName = a.Length == 0 ? t.Name : (a[0] as TableNameAttribute).Value; - - // Get the primary key - a = t.GetCustomAttributes(typeof(PrimaryKeyAttribute), true); - TableInfo.PrimaryKey = a.Length == 0 ? "ID" : (a[0] as PrimaryKeyAttribute).Value; - TableInfo.SequenceName = a.Length == 0 ? null : (a[0] as PrimaryKeyAttribute).sequenceName; - TableInfo.AutoIncrement = a.Length == 0 ? false : (a[0] as PrimaryKeyAttribute).autoIncrement; - - // Call column mapper - if (Database.Mapper != null) - Database.Mapper.GetTableInfo(t, TableInfo); - - // Work out bound properties - bool ExplicitColumns = t.GetCustomAttributes(typeof(ExplicitColumnsAttribute), true).Length > 0; - Columns = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var pi in t.GetProperties()) - { - // Work out if properties is to be included - var ColAttrs = pi.GetCustomAttributes(typeof(ColumnAttribute), true); - if (ExplicitColumns) - { - if (ColAttrs.Length == 0) - continue; - } - else - { - if (pi.GetCustomAttributes(typeof(IgnoreAttribute), true).Length != 0) - continue; - } - - var pc = new PocoColumn(); - pc.PropertyInfo = pi; - - // Work out the DB column name - if (ColAttrs.Length > 0) - { - var colattr = (ColumnAttribute)ColAttrs[0]; - pc.ColumnName = colattr.Name; - if ((colattr as ResultColumnAttribute) != null) - pc.ResultColumn = true; - } - if (pc.ColumnName == null) - { - pc.ColumnName = pi.Name; - if (Database.Mapper != null && !Database.Mapper.MapPropertyToColumn(t, pi, ref pc.ColumnName, ref pc.ResultColumn)) - continue; - } - - // Store it - Columns.Add(pc.ColumnName, pc); - } - - // Build column list for automatic select - QueryColumns = (from c in Columns where !c.Value.ResultColumn select c.Key).ToArray(); - - } - - static bool IsIntegralType(Type t) - { - var tc = Type.GetTypeCode(t); - return tc >= TypeCode.SByte && tc <= TypeCode.UInt64; - } - - - - // Create factory function that can convert a IDataReader record into a POCO - public Delegate GetFactory(string sql, string connString, bool ForceDateTimesToUtc, int firstColumn, int countColumns, IDataReader r) - { - - //TODO: It would be nice to remove the irrelevant SQL parts - for a mapping operation anything after the SELECT clause isn't required. - // This would ensure less duplicate entries that get cached, currently both of these queries would be cached even though they are - // returning the same structured data: - // SELECT * FROM MyTable ORDER BY MyColumn - // SELECT * FROM MyTable ORDER BY MyColumn DESC - - string key; - if (UseLongKeys) - { - key = string.Format("{0}:{1}:{2}:{3}:{4}", sql, connString, ForceDateTimesToUtc, firstColumn, countColumns); - } - else - { - //Create a hashed key, we don't want to store so much string data in memory - var combiner = new HashCodeCombiner(); - combiner.AddCaseInsensitiveString(sql); - combiner.AddCaseInsensitiveString(connString); - combiner.AddObject(ForceDateTimesToUtc); - combiner.AddInt(firstColumn); - combiner.AddInt(countColumns); - key = combiner.GetCombinedHashCode(); - } - - - var objectCache = _managedCache.GetCache(); - - Func factory = () => - { - // Create the method - var m = new DynamicMethod("petapoco_factory_" + objectCache.GetCount(), type, new Type[] { typeof(IDataReader) }, true); - var il = m.GetILGenerator(); - -#if !PETAPOCO_NO_DYNAMIC - if (type == typeof(object)) - { - // var poco=new T() - il.Emit(OpCodes.Newobj, typeof(System.Dynamic.ExpandoObject).GetConstructor(Type.EmptyTypes)); // obj - - MethodInfo fnAdd = typeof(IDictionary).GetMethod("Add"); - - // Enumerate all fields generating a set assignment for the column - for (int i = firstColumn; i < firstColumn + countColumns; i++) - { - var srcType = r.GetFieldType(i); - - il.Emit(OpCodes.Dup); // obj, obj - il.Emit(OpCodes.Ldstr, r.GetName(i)); // obj, obj, fieldname - - // Get the converter - Func converter = null; - if (Database.Mapper != null) - converter = Database.Mapper.GetFromDbConverter(null, srcType); - if (ForceDateTimesToUtc && converter == null && srcType == typeof(DateTime)) - converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); }; - - // Setup stack for call to converter - AddConverterToStack(il, converter); - - // r[i] - il.Emit(OpCodes.Ldarg_0); // obj, obj, fieldname, converter?, rdr - il.Emit(OpCodes.Ldc_I4, i); // obj, obj, fieldname, converter?, rdr,i - il.Emit(OpCodes.Callvirt, fnGetValue); // obj, obj, fieldname, converter?, value - - // Convert DBNull to null - il.Emit(OpCodes.Dup); // obj, obj, fieldname, converter?, value, value - il.Emit(OpCodes.Isinst, typeof(DBNull)); // obj, obj, fieldname, converter?, value, (value or null) - var lblNotNull = il.DefineLabel(); - il.Emit(OpCodes.Brfalse_S, lblNotNull); // obj, obj, fieldname, converter?, value - il.Emit(OpCodes.Pop); // obj, obj, fieldname, converter? - if (converter != null) - il.Emit(OpCodes.Pop); // obj, obj, fieldname, - il.Emit(OpCodes.Ldnull); // obj, obj, fieldname, null - if (converter != null) - { - var lblReady = il.DefineLabel(); - il.Emit(OpCodes.Br_S, lblReady); - il.MarkLabel(lblNotNull); - il.Emit(OpCodes.Callvirt, fnInvoke); - il.MarkLabel(lblReady); - } - else - { - il.MarkLabel(lblNotNull); - } - - il.Emit(OpCodes.Callvirt, fnAdd); - } - } - else -#endif - if (type.IsValueType || type == typeof(string) || type == typeof(byte[])) - { - // Do we need to install a converter? - var srcType = r.GetFieldType(0); - var converter = GetConverter(ForceDateTimesToUtc, null, srcType, type); - - // "if (!rdr.IsDBNull(i))" - il.Emit(OpCodes.Ldarg_0); // rdr - il.Emit(OpCodes.Ldc_I4_0); // rdr,0 - il.Emit(OpCodes.Callvirt, fnIsDBNull); // bool - var lblCont = il.DefineLabel(); - il.Emit(OpCodes.Brfalse_S, lblCont); - il.Emit(OpCodes.Ldnull); // null - var lblFin = il.DefineLabel(); - il.Emit(OpCodes.Br_S, lblFin); - - il.MarkLabel(lblCont); - - // Setup stack for call to converter - AddConverterToStack(il, converter); - - il.Emit(OpCodes.Ldarg_0); // rdr - il.Emit(OpCodes.Ldc_I4_0); // rdr,0 - il.Emit(OpCodes.Callvirt, fnGetValue); // value - - // Call the converter - if (converter != null) - il.Emit(OpCodes.Callvirt, fnInvoke); - - il.MarkLabel(lblFin); - il.Emit(OpCodes.Unbox_Any, type); // value converted - } - else - { - // var poco=new T() - il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null)); - - // Enumerate all fields generating a set assignment for the column - for (int i = firstColumn; i < firstColumn + countColumns; i++) - { - // Get the PocoColumn for this db column, ignore if not known - PocoColumn pc; - if (!Columns.TryGetValue(r.GetName(i), out pc)) - continue; - - // Get the source type for this column - var srcType = r.GetFieldType(i); - var dstType = pc.PropertyInfo.PropertyType; - - // "if (!rdr.IsDBNull(i))" - il.Emit(OpCodes.Ldarg_0); // poco,rdr - il.Emit(OpCodes.Ldc_I4, i); // poco,rdr,i - il.Emit(OpCodes.Callvirt, fnIsDBNull); // poco,bool - var lblNext = il.DefineLabel(); - il.Emit(OpCodes.Brtrue_S, lblNext); // poco - - il.Emit(OpCodes.Dup); // poco,poco - - // Do we need to install a converter? - var converter = GetConverter(ForceDateTimesToUtc, pc, srcType, dstType); - - // Fast - bool Handled = false; - if (converter == null) - { - var valuegetter = typeof(IDataRecord).GetMethod("Get" + srcType.Name, new Type[] { typeof(int) }); - if (valuegetter != null - && valuegetter.ReturnType == srcType - && (valuegetter.ReturnType == dstType || valuegetter.ReturnType == Nullable.GetUnderlyingType(dstType))) - { - il.Emit(OpCodes.Ldarg_0); // *,rdr - il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i - il.Emit(OpCodes.Callvirt, valuegetter); // *,value - - // Convert to Nullable - if (Nullable.GetUnderlyingType(dstType) != null) - { - il.Emit(OpCodes.Newobj, dstType.GetConstructor(new Type[] { Nullable.GetUnderlyingType(dstType) })); - } - - il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco - Handled = true; - } - } - - // Not so fast - if (!Handled) - { - // Setup stack for call to converter - AddConverterToStack(il, converter); - - // "value = rdr.GetValue(i)" - il.Emit(OpCodes.Ldarg_0); // *,rdr - il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i - il.Emit(OpCodes.Callvirt, fnGetValue); // *,value - - // Call the converter - if (converter != null) - il.Emit(OpCodes.Callvirt, fnInvoke); - - // Assign it - il.Emit(OpCodes.Unbox_Any, pc.PropertyInfo.PropertyType); // poco,poco,value - il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco - } - - il.MarkLabel(lblNext); - } - - var fnOnLoaded = RecurseInheritedTypes(type, (x) => x.GetMethod("OnLoaded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null)); - if (fnOnLoaded != null) - { - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Callvirt, fnOnLoaded); - } - } - - il.Emit(OpCodes.Ret); - - // return it - var del = m.CreateDelegate(Expression.GetFuncType(typeof(IDataReader), type)); - - return del; - }; - - //lazy usage of AddOrGetExisting ref: http://stackoverflow.com/questions/10559279/how-to-deal-with-costly-building-operations-using-memorycache/15894928#15894928 - var newValue = new Lazy(factory); - // the line belows returns existing item or adds the new value if it doesn't exist - var value = (Lazy)objectCache.AddOrGetExisting(key, newValue, new CacheItemPolicy - { - //sliding expiration of 1 hr, if the same key isn't used in this - // timeframe it will be removed from the cache - SlidingExpiration = new TimeSpan(0, 0, SlidingExpirationSeconds) - }); - return (value ?? newValue).Value; // Lazy handles the locking itself - - } - - private static void AddConverterToStack(ILGenerator il, Func converter) - { - if (converter != null) - { - // Add the converter - int converterIndex = m_Converters.Count; - m_Converters.Add(converter); - - // Generate IL to push the converter onto the stack - il.Emit(OpCodes.Ldsfld, fldConverters); - il.Emit(OpCodes.Ldc_I4, converterIndex); - il.Emit(OpCodes.Callvirt, fnListGetItem); // Converter - } - } - - private static Func GetConverter(bool forceDateTimesToUtc, PocoColumn pc, Type srcType, Type dstType) - { - Func converter = null; - - // Get converter from the mapper - if (Database.Mapper != null) - { - if (pc != null) - { - converter = Database.Mapper.GetFromDbConverter(pc.PropertyInfo, srcType); - } - else - { - var m2 = Database.Mapper as IMapper2; - if (m2 != null) - { - converter = m2.GetFromDbConverter(dstType, srcType); - } - } - } - - // Standard DateTime->Utc mapper - if (forceDateTimesToUtc && converter == null && srcType == typeof(DateTime) && (dstType == typeof(DateTime) || dstType == typeof(DateTime?))) - { - converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); }; - } - - // Forced type conversion including integral types -> enum - if (converter == null) - { - if (dstType.IsEnum && IsIntegralType(srcType)) - { - if (srcType != typeof(int)) - { - converter = delegate(object src) { return Convert.ChangeType(src, typeof(int), null); }; - } - } - else if (!dstType.IsAssignableFrom(srcType)) - { - converter = delegate(object src) { return Convert.ChangeType(src, dstType, null); }; - } - } - return converter; - } - - - static T RecurseInheritedTypes(Type t, Func cb) - { - while (t != null) - { - T info = cb(t); - if (info != null) - return info; - t = t.BaseType; - } - return default(T); - } - - ManagedCache _managedCache = new ManagedCache(); - static Dictionary m_PocoDatas = new Dictionary(); - static List> m_Converters = new List>(); - static MethodInfo fnGetValue = typeof(IDataRecord).GetMethod("GetValue", new Type[] { typeof(int) }); - static MethodInfo fnIsDBNull = typeof(IDataRecord).GetMethod("IsDBNull"); - static FieldInfo fldConverters = typeof(PocoData).GetField("m_Converters", BindingFlags.Static | BindingFlags.GetField | BindingFlags.NonPublic); - static MethodInfo fnListGetItem = typeof(List>).GetProperty("Item").GetGetMethod(); - static MethodInfo fnInvoke = typeof(Func).GetMethod("Invoke"); - public Type type; - public string[] QueryColumns { get; private set; } - public TableInfo TableInfo { get; private set; } - public Dictionary Columns { get; private set; } - static System.Threading.ReaderWriterLockSlim InnerLock = new System.Threading.ReaderWriterLockSlim(); - - /// - /// Returns a report of the current cache being utilized by PetaPoco - /// - /// - public static string PrintDebugCacheReport(out double totalBytes, out IEnumerable allKeys) - { - var managedCache = new ManagedCache(); - - var sb = new StringBuilder(); - sb.AppendLine("m_PocoDatas:"); - foreach (var pocoData in m_PocoDatas) - { - sb.AppendFormat("\t{0}\n", pocoData.Key); - sb.AppendFormat("\t\tTable:{0} - Col count:{1}\n", pocoData.Value.TableInfo.TableName, pocoData.Value.QueryColumns.Length); - } - - var cache = managedCache.GetCache(); - allKeys = cache.Select(x => x.Key).ToArray(); - - sb.AppendFormat("\tTotal Poco data count:{0}\n", allKeys.Count()); - - var keys = string.Join("", cache.Select(x => x.Key)); - //Bytes in .Net are stored as utf-16 = unicode little endian - totalBytes = Encoding.Unicode.GetByteCount(keys); - - sb.AppendFormat("\tTotal byte for keys:{0}\n", totalBytes); - - sb.AppendLine("\tAll Poco cache items:"); - - foreach (var item in cache) - { - sb.AppendFormat("\t\t Key -> {0}\n", item.Key); - sb.AppendFormat("\t\t Value -> {0}\n", item.Value); - } - - sb.AppendLine("-------------------END REPORT------------------------"); - return sb.ToString(); - } - } - - - // Member variables - string _connectionString; - string _providerName; - DbProviderFactory _factory; - IDbConnection _sharedConnection; - IDbTransaction _transaction; - int _sharedConnectionDepth; - int _transactionDepth; - bool _transactionCancelled; - string _lastSql; - object[] _lastArgs; - string _paramPrefix = "@"; - IsolationLevel _isolationLevel; - } - - // Transaction object helps maintain transaction depth counts - public class Transaction : IDisposable - { - public Transaction(Database db, IsolationLevel isolationLevel) - { - _db = db; - _db.BeginTransaction(isolationLevel); - } - - public virtual void Complete() - { - _db.CompleteTransaction(); - _db = null; - } - - public void Dispose() - { - //TODO prevent multiple calls to Dispose - if (_db != null) - _db.AbortTransaction(); - } - - Database _db; - } - - // Simple helper class for building SQL statments - public class Sql - { - public Sql() - { - } - - public Sql(string sql, params object[] args) - { - _sql = sql; - _args = args; - } - - string _sql; - object[] _args; - Sql _rhs; - string _sqlFinal; - object[] _argsFinal; - - private void Build() - { - // already built? - if (_sqlFinal != null) - return; - - // Build it - var sb = new StringBuilder(); - var args = new List(); - Build(sb, args, null); - _sqlFinal = sb.ToString(); - _argsFinal = args.ToArray(); - } - - public string SQL - { - get - { - Build(); - return _sqlFinal; - } - } - - public object[] Arguments - { - get - { - Build(); - return _argsFinal; - } - } - - public Sql Append(Sql sql) - { - if (_rhs != null) - _rhs.Append(sql); - else - _rhs = sql; - - return this; - } - - public Sql Append(string sql, params object[] args) - { - return Append(new Sql(sql, args)); - } - - static bool Is(Sql sql, string sqltype) - { - return sql != null && sql._sql != null && sql._sql.StartsWith(sqltype, StringComparison.InvariantCultureIgnoreCase); - } - - private void Build(StringBuilder sb, List args, Sql lhs) - { - if (!String.IsNullOrEmpty(_sql)) - { - // Add SQL to the string - if (sb.Length > 0) - { - sb.Append("\n"); - } - - var sql = Database.ProcessParams(_sql, _args, args); - - if (Is(lhs, "WHERE ") && Is(this, "WHERE ")) - sql = "AND " + sql.Substring(6); - if (Is(lhs, "ORDER BY ") && Is(this, "ORDER BY ")) - sql = ", " + sql.Substring(9); - - sb.Append(sql); - } - - // Now do rhs - if (_rhs != null) - _rhs.Build(sb, args, this); - } - - public Sql Where(string sql, params object[] args) - { - return Append(new Sql("WHERE (" + sql + ")", args)); - } - - public Sql OrderBy(params object[] columns) - { - return Append(new Sql("ORDER BY " + String.Join(", ", (from x in columns select x.ToString()).ToArray()))); - } - - public Sql Select(params object[] columns) - { - return Append(new Sql("SELECT " + String.Join(", ", (from x in columns select x.ToString()).ToArray()))); - } - - public Sql From(params object[] tables) - { - return Append(new Sql("FROM " + String.Join(", ", (from x in tables select x.ToString()).ToArray()))); - } - - public Sql GroupBy(params object[] columns) - { - return Append(new Sql("GROUP BY " + String.Join(", ", (from x in columns select x.ToString()).ToArray()))); - } - - private SqlJoinClause Join(string JoinType, string table) - { - return new SqlJoinClause(Append(new Sql(JoinType + table))); - } - - public SqlJoinClause InnerJoin(string table) { return Join("INNER JOIN ", table); } - public SqlJoinClause LeftJoin(string table) { return Join("LEFT JOIN ", table); } - public SqlJoinClause LeftOuterJoin(string table) { return Join("LEFT OUTER JOIN ", table); } - public SqlJoinClause RightJoin(string table) { return Join("RIGHT JOIN ", table); } - - public class SqlJoinClause - { - private readonly Sql _sql; - - public SqlJoinClause(Sql sql) - { - _sql = sql; - } - - public Sql On(string onClause, params object[] args) - { - return _sql.Append("ON " + onClause, args); - } - } - } - -} diff --git a/src/Umbraco.Core/Persistence/PetaPocoCommandExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoCommandExtensions.cs deleted file mode 100644 index f1b41f2420..0000000000 --- a/src/Umbraco.Core/Persistence/PetaPocoCommandExtensions.cs +++ /dev/null @@ -1,277 +0,0 @@ -using System; -using System.Data; -using System.Data.SqlClient; -using Umbraco.Core.Persistence.FaultHandling; - -namespace Umbraco.Core.Persistence -{ - /// - /// Provides a set of extension methods adding retry capabilities into the standard implementation, which is used in PetaPoco. - /// - public static class PetaPocoCommandExtensions - { - #region ExecuteNonQueryWithRetry method implementations - /// - /// Executes a Transact-SQL statement against the connection and returns the number of rows affected. Uses the default retry policy when executing the command. - /// - /// The command object that is required as per extension method declaration. - /// The number of rows affected. - public static int ExecuteNonQueryWithRetry(this IDbCommand command) - { - var connectionString = command.Connection.ConnectionString ?? string.Empty; - return ExecuteNonQueryWithRetry(command, RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(connectionString)); - } - - /// - /// Executes a Transact-SQL statement against the connection and returns the number of rows affected. Uses the specified retry policy when executing the command. - /// - /// The command object that is required as per extension method declaration. - /// The retry policy defining whether to retry a command if a connection fails while executing the command. - /// The number of rows affected. - public static int ExecuteNonQueryWithRetry(this IDbCommand command, RetryPolicy retryPolicy) - { - var connectionString = command.Connection.ConnectionString ?? string.Empty; - return ExecuteNonQueryWithRetry(command, retryPolicy, RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(connectionString)); - } - - /// - /// Executes a Transact-SQL statement against the connection and returns the number of rows affected. Uses the specified retry policy when executing the command. - /// Uses a separate specified retry policy when establishing a connection. - /// - /// The command object that is required as per extension method declaration. - /// The command retry policy defining whether to retry a command if it fails while executing. - /// The connection retry policy defining whether to re-establish a connection if it drops while executing the command. - /// The number of rows affected. - public static int ExecuteNonQueryWithRetry(this IDbCommand command, RetryPolicy cmdRetryPolicy, RetryPolicy conRetryPolicy) - { - //GuardConnectionIsNotNull(command); - - // Check if retry policy was specified, if not, use the default retry policy. - return (cmdRetryPolicy ?? RetryPolicy.NoRetry).ExecuteAction(() => - { - var hasOpenConnection = EnsureValidConnection(command, conRetryPolicy); - - try - { - return command.ExecuteNonQuery(); - } - finally - { - if (hasOpenConnection && command.Connection != null && command.Connection.State == ConnectionState.Open) - { - //Connection is closed in PetaPoco, so no need to do it here (?) - //command.Connection.Close(); - } - } - }); - } - #endregion - - #region ExecuteReaderWithRetry method implementations - /// - /// Sends the specified command to the connection and builds a SqlDataReader object containing the results. - /// Uses the default retry policy when executing the command. - /// - /// The command object that is required as per extension method declaration. - /// A System.Data.IDataReader object. - public static IDataReader ExecuteReaderWithRetry(this IDbCommand command) - { - var connectionString = command.Connection.ConnectionString ?? string.Empty; - return ExecuteReaderWithRetry(command, RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(connectionString)); - } - - /// - /// Sends the specified command to the connection and builds a SqlDataReader object containing the results. - /// Uses the specified retry policy when executing the command. - /// - /// The command object that is required as per extension method declaration. - /// The retry policy defining whether to retry a command if a connection fails while executing the command. - /// A System.Data.IDataReader object. - public static IDataReader ExecuteReaderWithRetry(this IDbCommand command, RetryPolicy retryPolicy) - { - var connectionString = command.Connection.ConnectionString ?? string.Empty; - return ExecuteReaderWithRetry(command, retryPolicy, RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(connectionString)); - } - - /// - /// Sends the specified command to the connection and builds a SqlDataReader object containing the results. - /// Uses the specified retry policy when executing the command. Uses a separate specified retry policy when - /// establishing a connection. - /// - /// The command object that is required as per extension method declaration. - /// The command retry policy defining whether to retry a command if it fails while executing. - /// The connection retry policy defining whether to re-establish a connection if it drops while executing the command. - /// A System.Data.IDataReader object. - public static IDataReader ExecuteReaderWithRetry(this IDbCommand command, RetryPolicy cmdRetryPolicy, RetryPolicy conRetryPolicy) - { - //GuardConnectionIsNotNull(command); - - // Check if retry policy was specified, if not, use the default retry policy. - return (cmdRetryPolicy ?? RetryPolicy.NoRetry).ExecuteAction(() => - { - var hasOpenConnection = EnsureValidConnection(command, conRetryPolicy); - - try - { - return command.ExecuteReader(); - } - catch (Exception) - { - if (hasOpenConnection && command.Connection != null && command.Connection.State == ConnectionState.Open) - { - //command.Connection.Close(); - } - - throw; - } - }); - } - - /// - /// Sends the specified command to the connection and builds a SqlDataReader object using one of the - /// CommandBehavior values. Uses the default retry policy when executing the command. - /// - /// The command object that is required as per extension method declaration. - /// One of the System.Data.CommandBehavior values. - /// A System.Data.IDataReader object. - public static IDataReader ExecuteReaderWithRetry(this IDbCommand command, CommandBehavior behavior) - { - var connectionString = command.Connection.ConnectionString ?? string.Empty; - return ExecuteReaderWithRetry(command, behavior, RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(connectionString)); - } - - /// - /// Sends the specified command to the connection and builds a SqlDataReader object using one of the - /// CommandBehavior values. Uses the specified retry policy when executing the command. - /// - /// The command object that is required as per extension method declaration. - /// One of the System.Data.CommandBehavior values. - /// The retry policy defining whether to retry a command if a connection fails while executing the command. - /// A System.Data.SqlClient.SqlDataReader object. - public static IDataReader ExecuteReaderWithRetry(this IDbCommand command, CommandBehavior behavior, RetryPolicy retryPolicy) - { - var connectionString = command.Connection.ConnectionString ?? string.Empty; - return ExecuteReaderWithRetry(command, behavior, retryPolicy, RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(connectionString)); - } - - /// - /// Sends the specified command to the connection and builds a SqlDataReader object using one of the - /// CommandBehavior values. Uses the specified retry policy when executing the command. - /// Uses a separate specified retry policy when establishing a connection. - /// - /// The command object that is required as per extension method declaration. - /// One of the System.Data.CommandBehavior values. - /// The command retry policy defining whether to retry a command if it fails while executing. - /// The connection retry policy defining whether to re-establish a connection if it drops while executing the command. - /// A System.Data.IDataReader object. - public static IDataReader ExecuteReaderWithRetry(this IDbCommand command, CommandBehavior behavior, RetryPolicy cmdRetryPolicy, RetryPolicy conRetryPolicy) - { - //GuardConnectionIsNotNull(command); - - // Check if retry policy was specified, if not, use the default retry policy. - return (cmdRetryPolicy ?? RetryPolicy.NoRetry).ExecuteAction(() => - { - var hasOpenConnection = EnsureValidConnection(command, conRetryPolicy); - - try - { - return command.ExecuteReader(behavior); - } - catch (Exception) - { - if (hasOpenConnection && command.Connection != null && command.Connection.State == ConnectionState.Open) - { - //command.Connection.Close(); - } - - throw; - } - }); - } - #endregion - - #region ExecuteScalarWithRetry method implementations - /// - /// Executes the query, and returns the first column of the first row in the result set returned by the query. Additional columns or rows are ignored. - /// Uses the default retry policy when executing the command. - /// - /// The command object that is required as per extension method declaration. - /// The first column of the first row in the result set, or a null reference if the result set is empty. Returns a maximum of 2033 characters. - public static object ExecuteScalarWithRetry(this IDbCommand command) - { - var connectionString = command.Connection.ConnectionString ?? string.Empty; - return ExecuteScalarWithRetry(command, RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(connectionString)); - } - - /// - /// Executes the query, and returns the first column of the first row in the result set returned by the query. Additional columns or rows are ignored. - /// Uses the specified retry policy when executing the command. - /// - /// The command object that is required as per extension method declaration. - /// The retry policy defining whether to retry a command if a connection fails while executing the command. - /// The first column of the first row in the result set, or a null reference if the result set is empty. Returns a maximum of 2033 characters. - public static object ExecuteScalarWithRetry(this IDbCommand command, RetryPolicy retryPolicy) - { - var connectionString = command.Connection.ConnectionString ?? string.Empty; - return ExecuteScalarWithRetry(command, retryPolicy, RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(connectionString)); - } - /// - /// Executes the query, and returns the first column of the first row in the result set returned by the query. Additional columns or rows are ignored. - /// Uses the specified retry policy when executing the command. Uses a separate specified retry policy when establishing a connection. - /// - /// The command object that is required as per extension method declaration. - /// The command retry policy defining whether to retry a command if it fails while executing. - /// The connection retry policy defining whether to re-establish a connection if it drops while executing the command. - /// The first column of the first row in the result set, or a null reference if the result set is empty. Returns a maximum of 2033 characters. - public static object ExecuteScalarWithRetry(this IDbCommand command, RetryPolicy cmdRetryPolicy, RetryPolicy conRetryPolicy) - { - //GuardConnectionIsNotNull(command); - - // Check if retry policy was specified, if not, use the default retry policy. - return (cmdRetryPolicy ?? RetryPolicy.NoRetry).ExecuteAction(() => - { - var hasOpenConnection = EnsureValidConnection(command, conRetryPolicy); - - try - { - return command.ExecuteScalar(); - } - finally - { - if (hasOpenConnection && command.Connection != null && command.Connection.State == ConnectionState.Open) - { - //Connection is closed in PetaPoco, so no need to do it here (?) - //command.Connection.Close(); - } - } - }); - } - #endregion - - /// - /// Ensure a valid connection in case a connection hasn't been opened by PetaPoco (which shouldn't be possible by the way). - /// - /// - /// - /// - private static bool EnsureValidConnection(IDbCommand command, RetryPolicy retryPolicy) - { - if (command != null) - { - //GuardConnectionIsNotNull(command); - - // Verify whether or not the connection is valid and is open. This code may be retried therefore - // it is important to ensure that a connection is re-established should it have previously failed. - if (command.Connection.State != ConnectionState.Open) - { - // Attempt to open the connection using the retry policy that matches the policy for SQL commands. - command.Connection.OpenWithRetry(retryPolicy); - - return true; - } - } - - return false; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/PetaPocoConnectionExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoConnectionExtensions.cs deleted file mode 100644 index e13608bc31..0000000000 --- a/src/Umbraco.Core/Persistence/PetaPocoConnectionExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Data; -using Umbraco.Core.Persistence.FaultHandling; - -namespace Umbraco.Core.Persistence -{ - /// - /// Provides a set of extension methods adding retry capabilities into the standard interface, which is used in PetaPoco. - /// - public static class PetaPocoConnectionExtensions - { - /// - /// Opens a database connection with the connection settings specified in the ConnectionString property of the connection object. - /// Uses the default retry policy when opening the connection. - /// - /// The connection object that is required as per extension method declaration. - public static void OpenWithRetry(this IDbConnection connection) - { - var connectionString = connection.ConnectionString ?? string.Empty; - var retryPolicy = RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(connectionString); - OpenWithRetry(connection, retryPolicy); - } - - /// - /// Opens a database connection with the connection settings specified in the ConnectionString property of the connection object. - /// Uses the specified retry policy when opening the connection. - /// - /// The connection object that is required as per extension method declaration. - /// The retry policy defining whether to retry a request if the connection fails to be opened. - public static void OpenWithRetry(this IDbConnection connection, RetryPolicy retryPolicy) - { - // Check if retry policy was specified, if not, use the default retry policy. - (retryPolicy != null ? retryPolicy : RetryPolicy.NoRetry).ExecuteAction(connection.Open); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs deleted file mode 100644 index 8603481376..0000000000 --- a/src/Umbraco.Core/Persistence/PetaPocoSqlExtensions.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Collections; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Persistence -{ - /// - /// Extension methods adding strong types to PetaPoco's Sql Builder - /// - public static class PetaPocoSqlExtensions - { - - public static Sql From(this Sql sql, ISqlSyntaxProvider sqlSyntax) - { - var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; - - return sql.From(sqlSyntax.GetQuotedTableName(tableName)); - } - - public static Sql Where(this Sql sql, ISqlSyntaxProvider sqlSyntax, Expression> predicate) - { - var expresionist = new PocoToSqlExpressionHelper(sqlSyntax); - var whereExpression = expresionist.Visit(predicate); - return sql.Where(whereExpression, expresionist.GetSqlParameters()); - } - - public static Sql WhereIn(this Sql sql, ISqlSyntaxProvider sqlSyntax, Expression> fieldSelector, IEnumerable values) - { - var expresionist = new PocoToSqlExpressionHelper(sqlSyntax); - var fieldExpression = expresionist.Visit(fieldSelector); - return sql.Where(fieldExpression + " IN (@values)", new {@values = values}); - } - - public static Sql OrderBy(this Sql sql, ISqlSyntaxProvider sqlSyntax, Expression> columnMember) - { - var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; - var columnName = column.FirstAttribute().Name; - - var type = typeof(TColumn); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; - - //need to ensure the order by is in brackets, see: https://github.com/toptensoftware/PetaPoco/issues/177 - var syntax = string.Format("({0}.{1})", - sqlSyntax.GetQuotedTableName(tableName), - sqlSyntax.GetQuotedColumnName(columnName)); - - return sql.OrderBy(syntax); - } - - public static Sql OrderByDescending(this Sql sql, ISqlSyntaxProvider sqlSyntax, Expression> columnMember) - { - var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; - var columnName = column.FirstAttribute().Name; - - var type = typeof(TColumn); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; - - var syntax = string.Format("{0}.{1} DESC", - sqlSyntax.GetQuotedTableName(tableName), - sqlSyntax.GetQuotedColumnName(columnName)); - - return sql.OrderBy(syntax); - } - - public static Sql GroupBy(this Sql sql, ISqlSyntaxProvider sqlProvider, Expression> columnMember) - { - var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; - var columnName = column.FirstAttribute().Name; - - return sql.GroupBy(sqlProvider.GetQuotedColumnName(columnName)); - } - - public static Sql.SqlJoinClause InnerJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) - { - var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; - - return sql.InnerJoin(sqlSyntax.GetQuotedTableName(tableName)); - } - - public static Sql.SqlJoinClause LeftJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) - { - var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; - - return sql.LeftJoin(sqlSyntax.GetQuotedTableName(tableName)); - } - - public static Sql.SqlJoinClause LeftOuterJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) - { - var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; - - return sql.LeftOuterJoin(sqlSyntax.GetQuotedTableName(tableName)); - } - - public static Sql.SqlJoinClause RightJoin(this Sql sql, ISqlSyntaxProvider sqlSyntax) - { - var type = typeof(T); - var tableNameAttribute = type.FirstAttribute(); - string tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; - - return sql.RightJoin(sqlSyntax.GetQuotedTableName(tableName)); - } - - public static Sql On(this Sql.SqlJoinClause sql, ISqlSyntaxProvider sqlSyntax, Expression> leftMember, - Expression> rightMember, params object[] args) - { - var leftType = typeof(TLeft); - var rightType = typeof(TRight); - var leftTableName = leftType.FirstAttribute().Value; - var rightTableName = rightType.FirstAttribute().Value; - - var left = ExpressionHelper.FindProperty(leftMember) as PropertyInfo; - var right = ExpressionHelper.FindProperty(rightMember) as PropertyInfo; - var leftColumnName = left.FirstAttribute().Name; - var rightColumnName = right.FirstAttribute().Name; - - string onClause = string.Format("{0}.{1} = {2}.{3}", - sqlSyntax.GetQuotedTableName(leftTableName), - sqlSyntax.GetQuotedColumnName(leftColumnName), - sqlSyntax.GetQuotedTableName(rightTableName), - sqlSyntax.GetQuotedColumnName(rightColumnName)); - return sql.On(onClause); - } - - public static Sql OrderByDescending(this Sql sql, params object[] columns) - { - return sql.Append(new Sql("ORDER BY " + String.Join(", ", (from x in columns select x + " DESC").ToArray()))); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs index 05d9646b18..50e1ea0106 100644 --- a/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/BaseExpressionHelper.cs @@ -10,6 +10,8 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Querying { + // fixme.npoco - are we basically duplicating entire parts of NPoco just because of SqlSyntax ?! + internal abstract class BaseExpressionHelper : BaseExpressionHelper { protected BaseExpressionHelper(ISqlSyntaxProvider sqlSyntax) @@ -151,13 +153,13 @@ namespace Umbraco.Core.Persistence.Querying { // deal with (x != true|false) - most common var constRight = b.Right as ConstantExpression; - if (constRight != null && constRight.Type == typeof(bool)) + if (constRight != null && constRight.Type == typeof (bool)) return ((bool) constRight.Value) ? VisitNot(b.Left) : VisitNotNot(b.Left); right = Visit(b.Right); // deal with (true|false != x) - why not var constLeft = b.Left as ConstantExpression; - if (constLeft != null && constLeft.Type == typeof(bool)) + if (constLeft != null && constLeft.Type == typeof (bool)) return ((bool) constLeft.Value) ? VisitNot(b.Right) : VisitNotNot(b.Right); left = Visit(b.Left); } @@ -578,11 +580,11 @@ namespace Umbraco.Core.Persistence.Querying /// /// Logic that is shared with the expression helpers /// - internal class BaseExpressionHelper + internal abstract class BaseExpressionHelper { public ISqlSyntaxProvider SqlSyntax { get; private set; } - public BaseExpressionHelper(ISqlSyntaxProvider sqlSyntax) + protected BaseExpressionHelper(ISqlSyntaxProvider sqlSyntax) { SqlSyntax = sqlSyntax; } diff --git a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs index 32e86f9512..958854727a 100644 --- a/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs +++ b/src/Umbraco.Core/Persistence/Querying/PocoToSqlExpressionHelper.cs @@ -1,63 +1,51 @@ using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using System.Linq.Expressions; -using System.Text; -using Umbraco.Core.Persistence.SqlSyntax; +using NPoco; namespace Umbraco.Core.Persistence.Querying { internal class PocoToSqlExpressionHelper : BaseExpressionHelper { - private readonly Database.PocoData _pd; + private readonly PocoData _pd; - public PocoToSqlExpressionHelper(ISqlSyntaxProvider sqlSyntaxProvider) - : base(sqlSyntaxProvider) + public PocoToSqlExpressionHelper(SqlContext sqlContext) + : base(sqlContext.SqlSyntax) { - _pd = new Database.PocoData(typeof(T)); + _pd = sqlContext.PocoDataFactory.ForType(typeof (T)); } protected override string VisitMemberAccess(MemberExpression m) { - if (m.Expression != null && - m.Expression.NodeType == ExpressionType.Parameter - && m.Expression.Type == typeof(T)) + if (m.Expression != null && m.Expression.NodeType == ExpressionType.Parameter && m.Expression.Type == typeof (T)) { - string field = GetFieldName(_pd, m.Member.Name); + var field = GetFieldName(_pd, m.Member.Name); return field; } if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert) { - string field = GetFieldName(_pd, m.Member.Name); + var field = GetFieldName(_pd, m.Member.Name); return field; } var member = Expression.Convert(m, typeof(object)); var lambda = Expression.Lambda>(member); var getter = lambda.Compile(); - object o = getter(); + var o = getter(); SqlParameters.Add(o); - return string.Format("@{0}", SqlParameters.Count - 1); - - //return GetQuotedValue(o, o != null ? o.GetType() : null); + return $"@{SqlParameters.Count - 1}"; } - protected virtual string GetFieldName(Database.PocoData pocoData, string name) + protected virtual string GetFieldName(PocoData pocoData, string name) { - var column = pocoData.Columns.FirstOrDefault(x => x.Value.PropertyInfo.Name == name); - return string.Format("{0}.{1}", - SqlSyntax.GetQuotedTableName(pocoData.TableInfo.TableName), - SqlSyntax.GetQuotedColumnName(column.Value.ColumnName)); - } + var column = pocoData.Columns.FirstOrDefault(x => x.Value.MemberInfoData.Name == name); + var tableName = SqlSyntax.GetQuotedTableName(pocoData.TableInfo.TableName); + var columnName = SqlSyntax.GetQuotedColumnName(column.Value.ColumnName); - //protected bool IsFieldName(string quotedExp) - //{ - // return true; - //} + return $"{tableName}.{columnName}"; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs b/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs index b012293340..94434fca74 100644 --- a/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs +++ b/src/Umbraco.Core/Persistence/Querying/SqlTranslator.cs @@ -1,4 +1,5 @@ using System; +using NPoco; namespace Umbraco.Core.Persistence.Querying { @@ -8,9 +9,9 @@ namespace Umbraco.Core.Persistence.Querying /// internal class SqlTranslator { - private readonly Sql _sql; + private readonly Sql _sql; - public SqlTranslator(Sql sql, IQuery query) + public SqlTranslator(Sql sql, IQuery query) { if (sql == null) throw new Exception("Sql cannot be null"); @@ -22,7 +23,7 @@ namespace Umbraco.Core.Persistence.Querying } } - public Sql Translate() + public Sql Translate() { return _sql; } diff --git a/src/Umbraco.Core/Persistence/Relators/AccessRulesRelator.cs b/src/Umbraco.Core/Persistence/Relators/AccessRulesRelator.cs deleted file mode 100644 index bcb33f3597..0000000000 --- a/src/Umbraco.Core/Persistence/Relators/AccessRulesRelator.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Relators -{ - internal class AccessRulesRelator - { - internal AccessDto Current; - - internal AccessDto Map(AccessDto a, AccessRuleDto p) - { - // Terminating call. Since we can return null from this function - // we need to be ready for PetaPoco to callback later with null - // parameters - if (a == null) - return Current; - - // Is this the same AccessDto as the current one we're processing - if (Current != null && Current.Id == a.Id) - { - // Yes, just add this AccessRuleDto to the current AccessDto's collection - if (p.Id != default(Guid)) - { - Current.Rules.Add(p); - } - - - // Return null to indicate we're not done with this AccessDto yet - return null; - } - - // This is a different AccessDto to the current one, or this is the - // first time through and we don't have a Tab yet - - // Save the current AccessDto - var prev = Current; - - // Setup the new current AccessDto - Current = a; - Current.Rules = new List(); - if (p.Id != default(Guid)) - { - Current.Rules.Add(p); - } - - // Return the now populated previous AccessDto (or null if first time through) - return prev; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Relators/DictionaryLanguageTextRelator.cs b/src/Umbraco.Core/Persistence/Relators/DictionaryLanguageTextRelator.cs deleted file mode 100644 index 6e2173471c..0000000000 --- a/src/Umbraco.Core/Persistence/Relators/DictionaryLanguageTextRelator.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Relators -{ - internal class DictionaryLanguageTextRelator - { - internal DictionaryDto Current; - - internal DictionaryDto Map(DictionaryDto a, LanguageTextDto p) - { - // Terminating call. Since we can return null from this function - // we need to be ready for PetaPoco to callback later with null - // parameters - if (a == null) - return Current; - - // Is this the same DictionaryItem as the current one we're processing - if (Current != null && Current.UniqueId == a.UniqueId) - { - // Yes, just add this LanguageTextDto to the current DictionaryItem's collection - Current.LanguageTextDtos.Add(p); - - // Return null to indicate we're not done with this DictionaryItem yet - return null; - } - - // This is a different DictionaryItem to the current one, or this is the - // first time through and we don't have a Tab yet - - // Save the current DictionaryItem - var prev = Current; - - // Setup the new current DictionaryItem - Current = a; - Current.LanguageTextDtos = new List(); - Current.LanguageTextDtos.Add(p); - - // Return the now populated previous DictionaryItem (or null if first time through) - return prev; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Relators/GroupPropertyTypeRelator.cs b/src/Umbraco.Core/Persistence/Relators/GroupPropertyTypeRelator.cs deleted file mode 100644 index 616882f068..0000000000 --- a/src/Umbraco.Core/Persistence/Relators/GroupPropertyTypeRelator.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Relators -{ - internal class GroupPropertyTypeRelator - { - internal PropertyTypeGroupDto current; - - internal PropertyTypeGroupDto Map(PropertyTypeGroupDto a, PropertyTypeDto p, DataTypeDto d) - { - // Terminating call. Since we can return null from this function - // we need to be ready for PetaPoco to callback later with null - // parameters - if (a == null) - return current; - - //Set the PropertyTypeDto's DataTypeDto object - if (p.DataTypeId == d.DataTypeId) - p.DataTypeDto = d; - - // Is this the same Group as the current one we're processing - if (current != null && current.Id == a.Id) - { - // Yes, just add this PropertyType to the current Group's collection of PropertyTypes - current.PropertyTypeDtos.Add(p); - - // Return null to indicate we're not done with this Group yet - return null; - } - - // This is a different Group to the current one, or this is the - // first time through and we don't have a Tab yet - - // Save the current Group - var prev = current; - - // Setup the new current Group - current = a; - current.PropertyTypeDtos = new List(); - current.PropertyTypeDtos.Add(p); - - // Return the now populated previous Tab (or null if first time through) - return prev; - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Relators/MacroPropertyRelator.cs b/src/Umbraco.Core/Persistence/Relators/MacroPropertyRelator.cs deleted file mode 100644 index 2f457edfd8..0000000000 --- a/src/Umbraco.Core/Persistence/Relators/MacroPropertyRelator.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Relators -{ - //internal class TaskUserRelator - //{ - // internal TaskDto Current; - - // internal TaskDto Map(TaskDto a, UserDto p) - // { - // // Terminating call. Since we can return null from this function - // // we need to be ready for PetaPoco to callback later with null - // // parameters - // if (a == null) - // return Current; - - // // Is this the same TaskDto as the current one we're processing - // if (Current != null && Current.Id == a.Id) - // { - // // Yes, set the user - // Current.MacroPropertyDtos.Add(p); - - // // Return null to indicate we're not done with this Macro yet - // return null; - // } - - // // This is a different Macro to the current one, or this is the - // // first time through and we don't have one yet - - // // Save the current Macro - // var prev = Current; - - // // Setup the new current Macro - // Current = a; - // Current.MacroPropertyDtos = new List(); - // //this can be null since we are doing a left join - // if (p.Alias != null) - // { - // Current.MacroPropertyDtos.Add(p); - // } - - // // Return the now populated previous Macro (or null if first time through) - // return prev; - // } - //} - - internal class MacroPropertyRelator - { - internal MacroDto Current; - - internal MacroDto Map(MacroDto a, MacroPropertyDto p) - { - // Terminating call. Since we can return null from this function - // we need to be ready for PetaPoco to callback later with null - // parameters - if (a == null) - return Current; - - // Is this the same DictionaryItem as the current one we're processing - if (Current != null && Current.Id == a.Id) - { - // Yes, just add this MacroPropertyDtos to the current item's collection - Current.MacroPropertyDtos.Add(p); - - // Return null to indicate we're not done with this Macro yet - return null; - } - - // This is a different Macro to the current one, or this is the - // first time through and we don't have one yet - - // Save the current Macro - var prev = Current; - - // Setup the new current Macro - Current = a; - Current.MacroPropertyDtos = new List(); - //this can be null since we are doing a left join - if (p.Alias != null) - { - Current.MacroPropertyDtos.Add(p); - } - - // Return the now populated previous Macro (or null if first time through) - return prev; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Relators/PropertyTypePropertyGroupRelator.cs b/src/Umbraco.Core/Persistence/Relators/PropertyTypePropertyGroupRelator.cs deleted file mode 100644 index bf307ea594..0000000000 --- a/src/Umbraco.Core/Persistence/Relators/PropertyTypePropertyGroupRelator.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Relators -{ - internal class PropertyTypePropertyGroupRelator - { - internal MemberTypeReadOnlyDto Current; - - internal MemberTypeReadOnlyDto Map(MemberTypeReadOnlyDto a, PropertyTypeReadOnlyDto p, PropertyTypeGroupReadOnlyDto g) - { - // Terminating call. Since we can return null from this function - // we need to be ready for PetaPoco to callback later with null - // parameters - if (a == null) - return Current; - - // Is this the same MemberTypeReadOnlyDto as the current one we're processing - if (Current != null && Current.UniqueId == a.UniqueId) - { - //This property may already be added so we need to check for that - if (p.Id.HasValue && Current.PropertyTypes.Any(x => x.Id == p.Id.Value) == false) - { - // Add this PropertyTypeReadOnlyDto to the current MemberTypeReadOnlyDto's collection - Current.PropertyTypes.Add(p); - } - - if (g.Id.HasValue && Current.PropertyTypeGroups != null && Current.PropertyTypeGroups.Any(x => x.Id == g.Id.Value) == false) - { - Current.PropertyTypeGroups.Add(g); - } - - // Return null to indicate we're not done with this MemberTypeReadOnlyDto yet - return null; - } - - // This is a different MemberTypeReadOnlyDto to the current one, or this is the - // first time through and we don't have a Tab yet - - // Save the current MemberTypeReadOnlyDto - var prev = Current; - - // Setup the new current MemberTypeReadOnlyDto - Current = a; - Current.PropertyTypes = new List(); - //this can be null since we are doing a left join - if (p.Id.HasValue) - { - Current.PropertyTypes.Add(p); - } - - Current.PropertyTypeGroups = new List(); - if (g.Id.HasValue) - { - Current.PropertyTypeGroups.Add(g); - } - - // Return the now populated previous MemberTypeReadOnlyDto (or null if first time through) - return prev; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs b/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs deleted file mode 100644 index 923348e729..0000000000 --- a/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Relators -{ - internal class UserSectionRelator - { - internal UserDto Current; - - internal UserDto Map(UserDto a, User2AppDto p) - { - // Terminating call. Since we can return null from this function - // we need to be ready for PetaPoco to callback later with null - // parameters - if (a == null) - return Current; - - // Is this the same DictionaryItem as the current one we're processing - if (Current != null && Current.Id == a.Id) - { - if (p.AppAlias.IsNullOrWhiteSpace() == false) - { - // Yes, just add this User2AppDto to the current item's collection - Current.User2AppDtos.Add(p); - } - - // Return null to indicate we're not done with this User yet - return null; - } - - // This is a different User to the current one, or this is the - // first time through and we don't have one yet - - // Save the current User - var prev = Current; - - // Setup the new current User - Current = a; - Current.User2AppDtos = new List(); - //this can be null since we are doing a left join - if (p.AppAlias.IsNullOrWhiteSpace() == false) - { - Current.User2AppDtos.Add(p); - } - - // Return the now populated previous User (or null if first time through) - return prev; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/AuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/AuditRepository.cs index 710dbf1d30..4d3fc22a06 100644 --- a/src/Umbraco.Core/Persistence/Repositories/AuditRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/AuditRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; @@ -11,7 +12,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - internal class AuditRepository : PetaPocoRepositoryBase, IAuditRepository + internal class AuditRepository : NPocoRepositoryBase, IAuditRepository { public AuditRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) : base(work, cache, logger, sqlSyntax, mappingResolver) @@ -63,11 +64,17 @@ namespace Umbraco.Core.Persistence.Repositories return dtos.Select(x => new AuditItem(x.NodeId, x.Comment, Enum.Parse(x.Header), x.UserId)).ToArray(); } - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From(SqlSyntax); + var sql = Sql(); + + sql = isCount + ? sql.SelectCount() + : sql.Select(); + + sql + .From(); + return sql; } diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs index e40460ee7d..d07ee15926 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentPreviewRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Xml.Linq; +using NPoco; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; @@ -14,7 +15,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Private class to handle preview insert/update based on standard principles and units of work with transactions /// - internal class ContentPreviewRepository : PetaPocoRepositoryBase> + internal class ContentPreviewRepository : NPocoRepositoryBase> where TContent : IContentBase { public ContentPreviewRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) @@ -38,7 +39,7 @@ namespace Umbraco.Core.Persistence.Repositories throw new NotImplementedException(); } - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { throw new NotImplementedException(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 56f0af8490..c68fa123b1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -7,6 +7,8 @@ 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; @@ -63,10 +65,10 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false) .Where(GetBaseWhereClause(), new { Id = id }) - .Where(SqlSyntax, x => x.Newest) - .OrderByDescending(SqlSyntax, x => x.VersionDate); + .Where(x => x.Newest) + .OrderByDescending(x => x.VersionDate); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(sql).FirstOrDefault(); if (dto == null) return null; @@ -85,9 +87,9 @@ namespace Umbraco.Core.Persistence.Repositories } //we only want the newest ones with this method - sql.Where(SqlSyntax, x => x.Newest); + sql.Where(x => x.Newest); - return ProcessQuery(sql); + return MapQueryDtos(Database.Fetch(sql)); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -95,18 +97,18 @@ namespace Umbraco.Core.Persistence.Repositories var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate() - .Where(SqlSyntax, x => x.Newest) - .OrderByDescending(SqlSyntax, x => x.VersionDate) - .OrderBy(SqlSyntax, x => x.SortOrder); + .Where(x => x.Newest) + .OrderByDescending(x => x.VersionDate) + .OrderBy(x => x.SortOrder); - return ProcessQuery(sql); + return MapQueryDtos(Database.Fetch(sql)); } #endregion - #region Overrides of PetaPocoRepositoryBase + #region Overrides of NPocoRepositoryBase - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { var sqlx = string.Format("LEFT OUTER JOIN {0} {1} ON ({1}.{2}={0}.{2} AND {1}.{3}=1)", SqlSyntax.GetQuotedTableName("cmsDocument"), @@ -114,23 +116,32 @@ namespace Umbraco.Core.Persistence.Repositories SqlSyntax.GetQuotedColumnName("nodeId"), SqlSyntax.GetQuotedColumnName("published")); - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.VersionId, right => right.VersionId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + var sql = Sql(); - // cannot do this because PetaPoco does not know how to alias the table + sql = isCount + ? sql.SelectCount() + : sql.Select(r => + r.Select(rr => + rr.Select(rrr => + rrr.Select())) + .Select(tableAlias: "cmsDocument2")); + + sql + .From() + .InnerJoin() + .On(left => left.VersionId, right => right.VersionId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + + // cannot do this because NPoco does not know how to alias the table //.LeftOuterJoin() //.On(left => left.NodeId, right => right.NodeId) // so have to rely on writing our own SQL .Append(sqlx/*, new { @published = true }*/) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; } @@ -180,11 +191,11 @@ namespace Umbraco.Core.Persistence.Repositories //Remove all the data first, if anything fails after this it's no problem the transaction will be reverted if (contentTypeIds == null) { - var subQuery = new Sql() + var subQuery = Sql() .Select("DISTINCT cmsContentXml.nodeId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId); + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId); var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); Database.Execute(deleteSql); @@ -194,13 +205,13 @@ namespace Umbraco.Core.Persistence.Repositories foreach (var id in contentTypeIds) { var id1 = id; - var subQuery = new Sql() + var subQuery = Sql() .Select("cmsDocument.nodeId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, dto => dto.Published) - .Where(SqlSyntax, dto => dto.ContentTypeId == id1); + .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); @@ -228,7 +239,7 @@ namespace Umbraco.Core.Persistence.Repositories } } - private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, Transaction tr, int pageSize) + private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, ITransaction tr, int pageSize) { var pageIndex = 0; var total = long.MinValue; @@ -240,10 +251,9 @@ namespace Umbraco.Core.Persistence.Repositories // 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, - new Tuple("cmsDocument", "nodeId"), - ProcessQuery, "Path", Direction.Ascending); - + var descendants = GetPagedResultsByQuery(query, pageIndex, pageSize, out total, + MapQueryDtos, "Path", Direction.Ascending); + var xmlItems = (from descendant in descendants let xml = serializer(descendant) select new ContentXmlDto { NodeId = descendant.Id, Xml = xml.ToDataString() }).ToArray(); @@ -261,9 +271,9 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(SqlSyntax, x => x.VersionDate); + sql.OrderByDescending(x => x.VersionDate); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(sql).FirstOrDefault(); if (dto == null) return null; @@ -275,13 +285,13 @@ namespace Umbraco.Core.Persistence.Repositories public override void DeleteVersion(Guid versionId) { - var sql = new Sql() - .Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.VersionId, right => right.VersionId) - .Where(SqlSyntax, x => x.VersionId == versionId) - .Where(SqlSyntax, x => x.Newest != true); - var dto = Database.Fetch(sql).FirstOrDefault(); + var sql = Sql() + .SelectAll() + .From() + .InnerJoin().On(left => left.VersionId, right => right.VersionId) + .Where(x => x.VersionId == versionId) + .Where(x => x.Newest != true); + var dto = Database.Fetch(sql).FirstOrDefault(); if (dto == null) return; @@ -295,14 +305,14 @@ namespace Umbraco.Core.Persistence.Repositories public override void DeleteVersions(int id, DateTime versionDate) { - var sql = new Sql() - .Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.VersionId, right => right.VersionId) - .Where(SqlSyntax, x => x.NodeId == id) - .Where(SqlSyntax, x => x.VersionDate < versionDate) - .Where(SqlSyntax, x => x.Newest != true); - var list = Database.Fetch(sql); + var sql = Sql() + .SelectAll() + .From() + .InnerJoin().On(left => left.VersionId, right => right.VersionId) + .Where(x => x.NodeId == id) + .Where(x => x.VersionDate < versionDate) + .Where(x => x.Newest != true); + var list = Database.Fetch(sql); if (list.Any() == false) return; using (var transaction = Database.GetTransaction()) @@ -330,14 +340,14 @@ namespace Umbraco.Core.Persistence.Repositories protected override void PersistDeletedItem(IContent entity) { - //We need to clear out all access rules but we need to do this in a manual way since + //We need to clear out all access rules but we need to do this in a manual way since // nothing in that table is joined to a content id - var subQuery = new Sql() + var subQuery = Sql() .Select("umbracoAccessRule.accessId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.AccessId, right => right.Id) - .Where(SqlSyntax, dto => dto.NodeId == entity.Id); + .From() + .InnerJoin() + .On(left => left.AccessId, right => right.Id) + .Where(dto => dto.NodeId == entity.Id); Database.Execute(SqlSyntax.GetDeleteSubquery("umbracoAccessRule", "accessId", subQuery)); //now let the normal delete clauses take care of everything else @@ -377,7 +387,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); @@ -390,7 +400,7 @@ namespace Umbraco.Core.Persistence.Repositories entity.Level = level; //Assign the same permissions to it as the parent node - // http://issues.umbraco.org/issue/U4-2161 + // http://issues.umbraco.org/issue/U4-2161 var permissionsRepo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); var parentPermissions = permissionsRepo.GetPermissionsForEntity(entity.ParentId).ToArray(); //if there are parent permissions then assign them, otherwise leave null and permissions will become the @@ -661,12 +671,12 @@ namespace Umbraco.Core.Persistence.Repositories var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate() - .Where(SqlSyntax, x => x.Published) - .OrderBy(SqlSyntax, x => x.Level) - .OrderBy(SqlSyntax, x => x.SortOrder); + .Where(x => x.Published) + .OrderBy(x => x.Level) + .OrderBy(x => x.SortOrder); //NOTE: This doesn't allow properties to be part of the query - var dtos = Database.Fetch(sql); + var dtos = Database.Fetch(sql); foreach (var dto in dtos) { @@ -689,8 +699,8 @@ namespace Umbraco.Core.Persistence.Repositories public int CountPublished() { - var sql = GetBaseQuery(true).Where(SqlSyntax, x => x.Trashed == false) - .Where(SqlSyntax, x => x.Published == true); + var sql = GetBaseQuery(true).Where(x => x.Trashed == false) + .Where(x => x.Published == true); return Database.ExecuteScalar(sql); } @@ -716,7 +726,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// - /// + /// public void AssignEntityPermission(IContent entity, char permission, IEnumerable userIds) { var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); @@ -772,26 +782,14 @@ namespace Umbraco.Core.Persistence.Repositories public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, string filter = "") { - - //NOTE: This uses the GetBaseQuery method but that does not take into account the required 'newest' field which is - // what we always require for a paged result, so we'll ensure it's included in the filter - - var args = new List(); - var sbWhere = new StringBuilder("AND (cmsDocument.newest = 1)"); - + var filterSql = Sql().Append("AND (cmsDocument.newest = 1)"); if (filter.IsNullOrWhiteSpace() == false) - { - sbWhere.Append(" AND (cmsDocument." + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ")"); - args.Add("%" + filter + "%"); - } - - Func> filterCallback = () => new Tuple(sbWhere.ToString(), args.ToArray()); - - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - new Tuple("cmsDocument", "nodeId"), - ProcessQuery, orderBy, orderDirection, - filterCallback); + filterSql.Append("AND (cmsDocument." + SqlSyntax.GetQuotedColumnName("text") + " LIKE @0)", "%" + filter + "%"); + return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, + MapQueryDtos, + orderBy, orderDirection, + filterSql); } /// @@ -801,7 +799,7 @@ namespace Umbraco.Core.Persistence.Repositories /// public XElement GetContentXml(int contentId) { - var sql = new Sql().Select("*").From(SqlSyntax).Where(SqlSyntax, d => d.NodeId == 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); @@ -815,8 +813,8 @@ namespace Umbraco.Core.Persistence.Repositories /// public XElement GetContentPreviewXml(int contentId, Guid version) { - var sql = new Sql().Select("*").From(SqlSyntax) - .Where(SqlSyntax, d => d.NodeId == contentId && d.VersionId == 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); @@ -848,11 +846,8 @@ namespace Umbraco.Core.Persistence.Repositories return base.GetDatabaseFieldNameForOrderBy(orderBy); } - private IEnumerable ProcessQuery(Sql sql) + private IEnumerable MapQueryDtos(List dtos) { - //NOTE: This doesn't allow properties to be part of the query - var dtos = Database.Fetch(sql); - //nothing found if (dtos.Any() == false) return Enumerable.Empty(); @@ -884,7 +879,7 @@ namespace Umbraco.Core.Persistence.Repositories d.dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, d.contentType)); - var propertyData = GetPropertyCollection(sql, docDefs); + var propertyData = GetPropertyCollection(docDefs.ToArray()); return dtosWithContentTypes.Select(d => CreateContentFromDto( d.dto, @@ -950,7 +945,7 @@ namespace Umbraco.Core.Persistence.Repositories var docDef = new DocumentDefinition(dto.NodeId, versionId, content.UpdateDate, content.CreateDate, contentType); - var properties = GetPropertyCollection(docSql, new[] { docDef }); + var properties = GetPropertyCollection(new[] { docDef }); content.Properties = properties[dto.NodeId]; @@ -965,10 +960,10 @@ namespace Umbraco.Core.Persistence.Repositories if (EnsureUniqueNaming == false) return nodeName; - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId && x.ParentId == parentId && x.Text.StartsWith(nodeName)); + var sql = Sql() + .SelectAll() + .From() + .Where(x => x.NodeObjectType == NodeObjectTypeId && x.ParentId == parentId && x.Text.StartsWith(nodeName)); int uniqueNumber = 1; var currentName = nodeName; diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index 5055f60ab9..5856bea59a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -6,6 +6,7 @@ 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; @@ -16,7 +17,6 @@ using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Services; @@ -28,7 +28,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Exposes shared functionality /// - internal abstract class ContentTypeBaseRepository : PetaPocoRepositoryBase, IReadRepository + internal abstract class ContentTypeBaseRepository : NPocoRepositoryBase, IReadRepository where TEntity : class, IContentTypeComposition { protected ContentTypeBaseRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) @@ -92,24 +92,21 @@ namespace Umbraco.Core.Persistence.Repositories /// protected IEnumerable PerformGetByQuery(IQuery query) { - var sqlClause = new Sql(); - sqlClause.Select("*") - .From(SqlSyntax) - .RightJoin(SqlSyntax) - .On(SqlSyntax, left => left.Id, right => right.PropertyTypeGroupId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.DataTypeId, right => right.DataTypeId); + 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(SqlSyntax, x => x.PropertyTypeGroupId); + .OrderBy(x => x.PropertyTypeGroupId); - var dtos = Database.Fetch(new GroupPropertyTypeRelator().Map, sql); - - foreach (var dto in dtos.DistinctBy(x => x.ContentTypeNodeId)) - { - yield return dto.ContentTypeNodeId; - } + return Database + .FetchOneToMany(x => x.PropertyTypeDtos, sql) + .Select(x => x.ContentTypeNodeId).Distinct(); } protected virtual PropertyType CreatePropertyType(string propertyEditorAlias, DataTypeDatabaseType dbType, string propertyTypeAlias) @@ -145,7 +142,7 @@ AND umbracoNode.nodeObjectType = @objectType", 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); @@ -272,15 +269,15 @@ AND umbracoNode.id <> @id", compositionBase.RemovedContentTypeKeyTracker.Any()) { //Find Content based on the current ContentType - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == new Guid(Constants.ObjectTypes.Document)) - .Where(SqlSyntax, x => x.ContentTypeId == entity.Id); + 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); + 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) { @@ -294,13 +291,13 @@ AND umbracoNode.id <> @id", { var nodeId = contentDto.NodeId; var propertyTypeId = propertyType.Id; - var propertySql = new Sql().Select("cmsPropertyData.id") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, - left => left.PropertyTypeId, right => right.Id) - .Where(SqlSyntax, x => x.NodeId == nodeId) - .Where(SqlSyntax, x => x.Id == propertyTypeId); + 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)); @@ -424,30 +421,32 @@ AND umbracoNode.id <> @id", protected IEnumerable GetAllowedContentTypeIds(int id) { - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .LeftJoin(SqlSyntax) - .On(SqlSyntax, left => left.AllowedId, right => right.NodeId) - .Where(SqlSyntax, x => x.Id == id); + var sql = Sql() + .SelectAll() + .From() + .LeftJoin() + .On(left => left.AllowedId, right => right.NodeId) + .Where(x => x.Id == id); - var allowedContentTypeDtos = Database.Fetch(sql); + 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 = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .LeftJoin(SqlSyntax) - .On(SqlSyntax, left => left.Id, right => right.PropertyTypeGroupId) - .LeftJoin(SqlSyntax) - .On(SqlSyntax, left => left.DataTypeId, right => right.DataTypeId) - .Where(SqlSyntax, x => x.ContentTypeNodeId == id) - .OrderBy(SqlSyntax, x => x.Id); + 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(new GroupPropertyTypeRelator().Map, sql); + + var dtos = Database + .Fetch(sql); var propertyGroupFactory = new PropertyGroupFactory(id, createDate, updateDate, CreatePropertyType); var propertyGroups = propertyGroupFactory.BuildEntity(dtos); @@ -456,14 +455,14 @@ AND umbracoNode.id <> @id", protected PropertyTypeCollection GetPropertyTypeCollection(int id, DateTime createDate, DateTime updateDate) { - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.DataTypeId, right => right.DataTypeId) - .Where(SqlSyntax, x => x.ContentTypeId == id); + var sql = Sql() + .SelectAll() + .From() + .InnerJoin() + .On(left => left.DataTypeId, right => right.DataTypeId) + .Where(x => x.ContentTypeId == id); - var dtos = Database.Fetch(sql); + var dtos = Database.Fetch(sql); //TODO Move this to a PropertyTypeFactory var list = new List(); @@ -531,11 +530,11 @@ AND umbracoNode.id <> @id", //we cannot try to assign a data type of it's empty if (propertyType.PropertyEditorAlias.IsNullOrWhiteSpace() == false) { - var sql = new Sql() - .Select("*") - .From(SqlSyntax) + var sql = Sql() + .SelectAll() + .From() .Where("propertyEditorAlias = @propertyEditorAlias", new { propertyEditorAlias = propertyType.PropertyEditorAlias }) - .OrderBy(SqlSyntax, typeDto => typeDto.DataTypeId); + .OrderBy(typeDto => typeDto.DataTypeId); var datatype = Database.FirstOrDefault(sql); //we cannot assign a data type if one was not found if (datatype != null) @@ -849,7 +848,7 @@ AND umbracoNode.id <> @id", return mediaType; } - internal static IEnumerable MapContentTypes(Database db, ISqlSyntaxProvider sqlSyntax, + internal static IEnumerable MapContentTypes(Database db, ISqlSyntaxProvider sqlSyntax, out IDictionary> associatedTemplates, out IDictionary> parentContentTypeIds) { diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index 05dfebc786..02941434db 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Events; using Umbraco.Core.Exceptions; @@ -12,7 +13,6 @@ using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Services; @@ -39,7 +39,7 @@ namespace Umbraco.Core.Persistence.Repositories { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( - RuntimeCache, GetEntityId, () => PerformGetAll(), + RuntimeCache, GetEntityId, () => PerformGetAll(), //allow this cache to expire expires:true)); } @@ -67,9 +67,9 @@ namespace Umbraco.Core.Persistence.Repositories { var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate(); + var sql = translator.Translate(); - var dtos = Database.Fetch(sql); + var dtos = Database.Fetch(sql); return //This returns a lookup from the GetAll cached looup @@ -79,7 +79,7 @@ namespace Umbraco.Core.Persistence.Repositories //order the result by name .OrderBy(x => x.Name); } - + /// /// Gets all entities of the specified query /// @@ -112,10 +112,11 @@ namespace Umbraco.Core.Persistence.Repositories /// public IEnumerable GetAllContentTypeAliases(params Guid[] objectTypes) { - var sql = new Sql().Select("cmsContentType.alias") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.NodeId, dto => dto.NodeId); + var sql = Sql() + .Select("cmsContentType.alias") + .From() + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.NodeId); if (objectTypes.Any()) { @@ -125,17 +126,23 @@ namespace Umbraco.Core.Persistence.Repositories return Database.Fetch(sql); } - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); + var sql = Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .LeftJoin(SqlSyntax) - .On(SqlSyntax, left => left.ContentTypeNodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + sql = isCount + ? sql.SelectCount() + : sql.Select(r => + r.Select(rr => + rr.Select())); + + sql + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .LeftJoin() + .On(left => left.ContentTypeNodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; } @@ -169,7 +176,7 @@ namespace Umbraco.Core.Persistence.Repositories { get { return new Guid(Constants.ObjectTypes.DocumentType); } } - + /// /// Deletes a content type /// @@ -188,18 +195,19 @@ namespace Umbraco.Core.Persistence.Repositories PersistDeletedItem((IEntity)child); } - //Before we call the base class methods to run all delete clauses, we need to first + //Before we call the base class methods to run all delete clauses, we need to first // delete all of the property data associated with this document type. Normally this will // be done in the ContentTypeService by deleting all associated content first, but in some cases // like when we switch a document type, there is property data left over that is linked // to the previous document type. So we need to ensure it's removed. - var sql = new Sql().Select("DISTINCT cmsPropertyData.propertytypeid") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.PropertyTypeId, dto => dto.Id) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.NodeId, dto => dto.ContentTypeId) - .Where(SqlSyntax, dto => dto.NodeId == entity.Id); + var sql = Sql() + .Select("DISTINCT cmsPropertyData.propertytypeid") + .From() + .InnerJoin() + .On(dto => dto.PropertyTypeId, dto => dto.Id) + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.ContentTypeId) + .Where(dto => dto.NodeId == entity.Id); //Delete all cmsPropertyData where propertytypeid EXISTS in the subquery above Database.Execute(SqlSyntax.GetDeleteSubquery("cmsPropertyData", "propertytypeid", sql)); @@ -282,7 +290,7 @@ namespace Umbraco.Core.Persistence.Repositories entity.ResetDirtyProperties(); } - + protected override IContentType PerformGet(Guid id) { //use the underlying GetAll which will force cache all content types diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs index 4b7c8a273a..c6258fe9a8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentXmlRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Xml.Linq; +using NPoco; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; @@ -14,7 +15,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Internal class to handle content/published xml insert/update based on standard principles and units of work with transactions /// - internal class ContentXmlRepository : PetaPocoRepositoryBase> + internal class ContentXmlRepository : NPocoRepositoryBase> where TContent : IContentBase { public ContentXmlRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) @@ -38,7 +39,7 @@ namespace Umbraco.Core.Persistence.Repositories throw new NotImplementedException(); } - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { throw new NotImplementedException(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index 5bef1a2b5e..d6c9d05d54 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using System.Threading; +using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Events; using Umbraco.Core.Exceptions; @@ -25,7 +26,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Represents a repository for doing CRUD operations for /// - internal class DataTypeDefinitionRepository : PetaPocoRepositoryBase, IDataTypeDefinitionRepository + internal class DataTypeDefinitionRepository : NPocoRepositoryBase, IDataTypeDefinitionRepository { private readonly IContentTypeRepository _contentTypeRepository; private readonly DataTypePreValueRepository _preValRepository; @@ -56,10 +57,10 @@ namespace Umbraco.Core.Persistence.Repositories } else { - dataTypeSql.Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + dataTypeSql.Where(x => x.NodeObjectType == NodeObjectTypeId); } - var dtos = Database.Fetch(dataTypeSql); + var dtos = Database.Fetch(dataTypeSql); return dtos.Select(factory.BuildEntity).ToArray(); } @@ -71,7 +72,7 @@ namespace Umbraco.Core.Persistence.Repositories var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); - var dtos = Database.Fetch(sql); + var dtos = Database.Fetch(sql); return dtos.Select(factory.BuildEntity).ToArray(); } @@ -111,16 +112,22 @@ namespace Umbraco.Core.Persistence.Repositories #endregion - #region Overrides of PetaPocoRepositoryBase + #region Overrides of NPocoRepositoryBase - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.DataTypeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + var sql = Sql(); + + sql = isCount + ? sql.SelectCount() + : sql.Select(r => + r.Select()); + + sql + .From() + .InnerJoin() + .On(left => left.DataTypeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; } @@ -175,7 +182,7 @@ WHERE umbracoNode." + SqlSyntax.GetQuotedColumnName("text") + "= @name", new { n 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); @@ -275,8 +282,8 @@ AND umbracoNode.id <> @id", #endregion - - + + public PreValueCollection GetPreValuesCollectionByDataTypeId(int dataTypeId) { var cached = RuntimeCache.GetCacheItemsByKeySearch(GetPrefixedCacheKey(dataTypeId)); @@ -387,10 +394,11 @@ AND umbracoNode.id <> @id", if (dataType.HasIdentity) { //first just get all pre-values for this data type so we can compare them to see if we need to insert or update or replace - var sql = new Sql().Select("*") - .From(SqlSyntax) - .Where(SqlSyntax, dto => dto.DataTypeNodeId == dataType.Id) - .OrderBy(SqlSyntax, dto => dto.SortOrder); + var sql = Sql() + .SelectAll() + .From() + .Where(dto => dto.DataTypeNodeId == dataType.Id) + .OrderBy(dto => dto.SortOrder); currentVals = Database.Fetch(sql).ToArray(); } @@ -477,12 +485,12 @@ AND umbracoNode.id <> @id", private string EnsureUniqueNodeName(string nodeName, int id = 0) { - - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId && x.Text.StartsWith(nodeName)); + + var sql = Sql() + .SelectAll() + .From() + .Where(x => x.NodeObjectType == NodeObjectTypeId && x.Text.StartsWith(nodeName)); int uniqueNumber = 1; var currentName = nodeName; @@ -520,7 +528,7 @@ AND umbracoNode.id <> @id", /// /// Private class to handle pre-value crud based on standard principles and units of work with transactions /// - private class DataTypePreValueRepository : PetaPocoRepositoryBase + private class DataTypePreValueRepository : NPocoRepositoryBase { public DataTypePreValueRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) : base(work, cache, logger, sqlSyntax, mappingResolver) @@ -543,7 +551,7 @@ AND umbracoNode.id <> @id", throw new NotImplementedException(); } - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { throw new NotImplementedException(); } @@ -579,7 +587,7 @@ AND umbracoNode.id <> @id", } //NOTE: We used to check that the Alias was unique for the given DataTypeNodeId prevalues list, BUT - // in reality there is no need to check the uniqueness of this alias because the only way that this code executes is + // in reality there is no need to check the uniqueness of this alias because the only way that this code executes is // based on an IDictionary dictionary being passed to this repository and a dictionary // must have unique aliases by definition, so there is no need for this additional check @@ -599,10 +607,10 @@ AND umbracoNode.id <> @id", { throw new InvalidOperationException("Cannot update a pre value for a data type that has no identity"); } - + //NOTE: We used to check that the Alias was unique for the given DataTypeNodeId prevalues list, BUT // this causes issues when sorting the pre-values (http://issues.umbraco.org/issue/U4-5670) but in reality - // there is no need to check the uniqueness of this alias because the only way that this code executes is + // there is no need to check the uniqueness of this alias because the only way that this code executes is // based on an IDictionary dictionary being passed to this repository and a dictionary // must have unique aliases by definition, so there is no need for this additional check @@ -617,7 +625,7 @@ AND umbracoNode.id <> @id", Database.Update(dto); } - + } internal static class PreValueConverter diff --git a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs index 5d553801d3..c210a783fa 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -10,7 +11,6 @@ using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; @@ -19,7 +19,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Represents a repository for doing CRUD operations for /// - internal class DictionaryRepository : PetaPocoRepositoryBase, IDictionaryRepository + internal class DictionaryRepository : NPocoRepositoryBase, IDictionaryRepository { private readonly IMappingResolver _mappingResolver; @@ -51,9 +51,12 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false) .Where(GetBaseWhereClause(), new { Id = id }) - .OrderBy(SqlSyntax, x => x.UniqueId); + .OrderBy(x => x.UniqueId); + + var dto = Database + .FetchOneToMany(x => x.LanguageTextDtos, sql) + .FirstOrDefault(); - var dto = Database.Fetch(new DictionaryLanguageTextRelator().Map, sql).FirstOrDefault(); if (dto == null) return null; @@ -74,8 +77,9 @@ namespace Umbraco.Core.Persistence.Repositories sql.Where("cmsDictionary.pk in (@ids)", new { ids = ids }); } - return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql) - .Select(dto => ConvertFromDto(dto)); + return Database + .FetchOneToMany(x => x.LanguageTextDtos, sql) + .Select(dto => ConvertFromDto(dto)); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -83,30 +87,31 @@ namespace Umbraco.Core.Persistence.Repositories var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); - sql.OrderBy(SqlSyntax, x => x.UniqueId); + sql.OrderBy(x => x.UniqueId); - return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql) + return Database + .FetchOneToMany(x => x.LanguageTextDtos, sql) .Select(x => ConvertFromDto(x)); } #endregion - #region Overrides of PetaPocoRepositoryBase + #region Overrides of NPocoRepositoryBase - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); + var sql = Sql(); if (isCount) { - sql.Select("COUNT(*)") - .From(SqlSyntax); + sql.SelectCount() + .From(); } else { - sql.Select("*") - .From(SqlSyntax) - .LeftJoin(SqlSyntax) - .On(SqlSyntax, left => left.UniqueId, right => right.UniqueId); + sql.SelectAll() + .From() + .LeftJoin() + .On(left => left.UniqueId, right => right.UniqueId); } return sql; } @@ -226,7 +231,7 @@ namespace Umbraco.Core.Persistence.Repositories var entity = factory.BuildEntity(dto); var list = new List(); - foreach (var textDto in dto.LanguageTextDtos) + foreach (var textDto in dto.LanguageTextDtos.EmptyNull()) { if (textDto.LanguageId <= 0) continue; @@ -273,15 +278,16 @@ namespace Umbraco.Core.Persistence.Repositories .Select(@group => { var sqlClause = GetBaseQuery(false) - .Where(SqlSyntax, x => x.Parent != null) + .Where(x => x.Parent != null) .Where(string.Format("{0} IN (@parentIds)", SqlSyntax.GetQuotedColumnName("parent")), new { parentIds = @group }); var translator = new SqlTranslator(sqlClause, Query); var sql = translator.Translate(); - sql.OrderBy(SqlSyntax, x => x.UniqueId); + sql.OrderBy(x => x.UniqueId); - return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql) - .Select(x => ConvertFromDto(x)); + return Database + .FetchOneToMany(x=> x.LanguageTextDtos, sql) + .Select(ConvertFromDto); }); }; @@ -305,10 +311,11 @@ namespace Umbraco.Core.Persistence.Repositories protected override IEnumerable PerformFetch(Sql sql) { - return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql); + return Database + .FetchOneToMany(x => x.LanguageTextDtos, sql); } - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { return _dictionaryRepository.GetBaseQuery(isCount); } @@ -362,10 +369,11 @@ namespace Umbraco.Core.Persistence.Repositories protected override IEnumerable PerformFetch(Sql sql) { - return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql); + return Database + .FetchOneToMany(x => x.LanguageTextDtos, sql); } - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { return _dictionaryRepository.GetBaseQuery(isCount); } diff --git a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs index c24c0cd3fa..29de099ab9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Data; using System.Linq; +using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -17,7 +18,7 @@ namespace Umbraco.Core.Persistence.Repositories { //TODO: We need to get a readonly ISO code for the domain assigned - internal class DomainRepository : PetaPocoRepositoryBase, IDomainRepository + internal class DomainRepository : NPocoRepositoryBase, IDomainRepository { public DomainRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) : base(work, cache, logger, sqlSyntax, mappingResolver) @@ -57,19 +58,19 @@ namespace Umbraco.Core.Persistence.Repositories throw new NotSupportedException("This repository does not support this method"); } - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); + var sql = Sql(); if (isCount) { - sql.Select("COUNT(*)").From(SqlSyntax); + sql.SelectCount().From(); } else { sql.Select("umbracoDomains.*, umbracoLanguage.languageISOCode") - .From(SqlSyntax) - .LeftJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.DefaultLanguage, dto => dto.Id); + .From() + .LeftJoin() + .On(dto => dto.DefaultLanguage, dto => dto.Id); } return sql; diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs index 3e4c2f7ef6..353caedc00 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -17,7 +18,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// An internal repository for managing entity containers such as doc type, media type, data type containers. /// - internal class EntityContainerRepository : PetaPocoRepositoryBase + internal class EntityContainerRepository : NPocoRepositoryBase { private readonly Guid _containerObjectType; @@ -70,7 +71,7 @@ namespace Umbraco.Core.Persistence.Repositories .Where("nodeObjectType=@umbracoObjectTypeId", new { umbracoObjectTypeId = NodeObjectTypeId }) .Where(string.Format("{0} IN (@ids)", SqlSyntax.GetQuotedColumnName("id")), new { ids = @group }); - sql.OrderBy(SqlSyntax, x => x.Level); + sql.OrderBy(x => x.Level); return Database.Fetch(sql).Select(CreateEntity); }); @@ -101,17 +102,14 @@ namespace Umbraco.Core.Persistence.Repositories return entity; } - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); + var sql = Sql(); if (isCount) - { - sql.Select("COUNT(*)").From(SqlSyntax); - } + sql.SelectCount(); else - { - sql.Select("*").From(SqlSyntax); - } + sql.SelectAll(); + sql.From(); return sql; } @@ -134,15 +132,15 @@ namespace Umbraco.Core.Persistence.Repositories { EnsureContainerType(entity); - var nodeDto = Database.FirstOrDefault(new Sql().Select("*") - .From(SqlSyntax) - .Where(SqlSyntax, dto => dto.NodeId == entity.Id && dto.NodeObjectType == entity.ContainerObjectType)); + var nodeDto = Database.FirstOrDefault(Sql().SelectAll() + .From() + .Where(dto => dto.NodeId == entity.Id && dto.NodeObjectType == entity.ContainerObjectType)); if (nodeDto == null) return; // move children to the parent so they are not orphans - var childDtos = Database.Fetch(new Sql().Select("*") - .From(SqlSyntax) + var childDtos = Database.Fetch(Sql().SelectAll() + .From() .Where("parentID=@parentID AND (nodeObjectType=@containedObjectType OR nodeObjectType=@containerObjectType)", new { @@ -169,9 +167,9 @@ namespace Umbraco.Core.Persistence.Repositories Mandate.ParameterNotNullOrEmpty(entity.Name, "entity.Name"); // guard against duplicates - var nodeDto = Database.FirstOrDefault(new Sql().Select("*") - .From(SqlSyntax) - .Where(SqlSyntax, dto => dto.ParentId == entity.ParentId && dto.Text == entity.Name && dto.NodeObjectType == entity.ContainerObjectType)); + var nodeDto = Database.FirstOrDefault(Sql().SelectAll() + .From() + .Where(dto => dto.ParentId == entity.ParentId && dto.Text == entity.Name && dto.NodeObjectType == entity.ContainerObjectType)); if (nodeDto != null) throw new InvalidOperationException("A container with the same name already exists."); @@ -180,9 +178,9 @@ namespace Umbraco.Core.Persistence.Repositories var path = "-1"; if (entity.ParentId > -1) { - var parentDto = Database.FirstOrDefault(new Sql().Select("*") - .From(SqlSyntax) - .Where(SqlSyntax, dto => dto.NodeId == entity.ParentId && dto.NodeObjectType == entity.ContainerObjectType)); + var parentDto = Database.FirstOrDefault(Sql().SelectAll() + .From() + .Where(dto => dto.NodeId == entity.ParentId && dto.NodeObjectType == entity.ContainerObjectType)); if (parentDto == null) throw new NullReferenceException("Could not find parent container with id " + entity.ParentId); @@ -209,7 +207,7 @@ namespace Umbraco.Core.Persistence.Repositories // insert, get the id, update the path with the id var id = Convert.ToInt32(Database.Insert(nodeDto)); nodeDto.Path = nodeDto.Path + "," + nodeDto.NodeId; - Database.Save(nodeDto); + Database.Save(nodeDto); // refresh the entity entity.Id = id; @@ -230,16 +228,16 @@ namespace Umbraco.Core.Persistence.Repositories Mandate.ParameterNotNullOrEmpty(entity.Name, "entity.Name"); // find container to update - var nodeDto = Database.FirstOrDefault(new Sql().Select("*") - .From(SqlSyntax) - .Where(SqlSyntax, dto => dto.NodeId == entity.Id && dto.NodeObjectType == entity.ContainerObjectType)); + var nodeDto = Database.FirstOrDefault(Sql().SelectAll() + .From() + .Where(dto => dto.NodeId == entity.Id && dto.NodeObjectType == entity.ContainerObjectType)); if (nodeDto == null) throw new InvalidOperationException("Could not find container with id " + entity.Id); // guard against duplicates - var dupNodeDto = Database.FirstOrDefault(new Sql().Select("*") - .From(SqlSyntax) - .Where(SqlSyntax, dto => dto.ParentId == entity.ParentId && dto.Text == entity.Name && dto.NodeObjectType == entity.ContainerObjectType)); + var dupNodeDto = Database.FirstOrDefault(Sql().SelectAll() + .From() + .Where(dto => dto.ParentId == entity.ParentId && dto.Text == entity.Name && dto.NodeObjectType == entity.ContainerObjectType)); if (dupNodeDto != null && dupNodeDto.NodeId != nodeDto.NodeId) throw new InvalidOperationException("A container with the same name already exists."); @@ -251,9 +249,9 @@ namespace Umbraco.Core.Persistence.Repositories nodeDto.Path = "-1"; if (entity.ParentId > -1) { - var parent = Database.FirstOrDefault(new Sql().Select("*") - .From(SqlSyntax) - .Where(SqlSyntax, dto => dto.NodeId == entity.ParentId && dto.NodeObjectType == entity.ContainerObjectType)); + var parent = Database.FirstOrDefault( Sql().SelectAll() + .From() + .Where(dto => dto.NodeId == entity.ParentId && dto.NodeObjectType == entity.ContainerObjectType)); if (parent == null) throw new NullReferenceException("Could not find parent container with id " + entity.ParentId); diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index 190c34fa57..d79fc8aec4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -1,12 +1,8 @@ using System; using System.Collections.Generic; -using System.Dynamic; -using System.Globalization; using System.Linq; -using System.Reflection; -using System.Text; +using NPoco; using Umbraco.Core.Models; -using Umbraco.Core; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Factories; @@ -14,7 +10,6 @@ using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Strings; namespace Umbraco.Core.Persistence.Repositories { @@ -26,13 +21,12 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class EntityRepository : DisposableObject, IEntityRepository { - private readonly IDatabaseUnitOfWork _work; private readonly ISqlSyntaxProvider _sqlSyntax; private readonly QueryFactory _queryFactory; public EntityRepository(IDatabaseUnitOfWork work, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) { - _work = work; + UnitOfWork = work; _sqlSyntax = sqlSyntax; _queryFactory = new QueryFactory(_sqlSyntax, mappingResolver); } @@ -40,30 +34,23 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Returns the Unit of Work added to the repository /// - protected internal IDatabaseUnitOfWork UnitOfWork - { - get { return _work; } - } + protected internal IDatabaseUnitOfWork UnitOfWork { get; } /// /// Internal for testing purposes /// - internal Guid UnitKey - { - get { return (Guid)_work.Key; } - } + internal Guid UnitKey => (Guid) UnitOfWork.Key; #region Query Methods - public Query Query - { - get { return _queryFactory.Create(); } - } + public Query Query => _queryFactory.Create(); + + public Sql Sql() { return NPoco.Sql.BuilderFor(new SqlContext(_sqlSyntax, UnitOfWork.Database));} public IUmbracoEntity GetByKey(Guid key) { var sql = GetBaseWhere(GetBase, false, false, key); - var nodeDto = _work.Database.FirstOrDefault(sql); + var nodeDto = UnitOfWork.Database.FirstOrDefault(sql); if (nodeDto == null) return null; @@ -75,39 +62,35 @@ namespace Umbraco.Core.Persistence.Repositories public IUmbracoEntity GetByKey(Guid key, Guid objectTypeId) { - bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); - bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); + var isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); + var isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); var sql = GetFullSqlForEntityType(key, isContent, isMedia, objectTypeId); - + if (isMedia) { //for now treat media differently //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields - var entities = _work.Database.Fetch( - new UmbracoEntityRelator().Map, sql); - - return entities.FirstOrDefault(); + return UnitOfWork.Database + .Fetch(sql) + .Transform(new UmbracoEntityRelator().MapAll) + .FirstOrDefault(); } - else - { - var nodeDto = _work.Database.FirstOrDefault(sql); - if (nodeDto == null) - return null; - var factory = new UmbracoEntityFactory(); - var entity = factory.BuildEntityFromDynamic(nodeDto); + var nodeDto = UnitOfWork.Database.FirstOrDefault(sql); + if (nodeDto == null) + return null; - return entity; - } - - + var factory = new UmbracoEntityFactory(); + var entity = factory.BuildEntityFromDynamic(nodeDto); + + return entity; } public virtual IUmbracoEntity Get(int id) { var sql = GetBaseWhere(GetBase, false, false, id); - var nodeDto = _work.Database.FirstOrDefault(sql); + var nodeDto = UnitOfWork.Database.FirstOrDefault(sql); if (nodeDto == null) return null; @@ -119,63 +102,49 @@ namespace Umbraco.Core.Persistence.Repositories public virtual IUmbracoEntity Get(int id, Guid objectTypeId) { - bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); - bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); + var isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); + var isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); var sql = GetFullSqlForEntityType(id, isContent, isMedia, objectTypeId); - + if (isMedia) { //for now treat media differently //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields - var entities = _work.Database.Fetch( - new UmbracoEntityRelator().Map, sql); - - return entities.FirstOrDefault(); - } - else - { - var nodeDto = _work.Database.FirstOrDefault(sql); - if (nodeDto == null) - return null; - - var factory = new UmbracoEntityFactory(); - var entity = factory.BuildEntityFromDynamic(nodeDto); - - return entity; + return UnitOfWork.Database + .Fetch(sql) + .Transform(new UmbracoEntityRelator().MapAll) + .FirstOrDefault(); } - + var nodeDto = UnitOfWork.Database.FirstOrDefault(sql); + if (nodeDto == null) + return null; + + var factory = new UmbracoEntityFactory(); + var entity = factory.BuildEntityFromDynamic(nodeDto); + + return entity; } public virtual IEnumerable GetAll(Guid objectTypeId, params int[] ids) { - if (ids.Any()) - { - return PerformGetAll(objectTypeId, sql1 => sql1.Where(" umbracoNode.id in (@ids)", new {ids = ids})); - } - else - { - return PerformGetAll(objectTypeId); - } + return ids.Any() + ? PerformGetAll(objectTypeId, sql1 => sql1.Where(" umbracoNode.id in (@ids)", new { /*ids =*/ ids })) + : PerformGetAll(objectTypeId); } public virtual IEnumerable GetAll(Guid objectTypeId, params Guid[] keys) { - if (keys.Any()) - { - return PerformGetAll(objectTypeId, sql1 => sql1.Where(" umbracoNode.uniqueID in (@keys)", new { keys = keys })); - } - else - { - return PerformGetAll(objectTypeId); - } + return keys.Any() + ? PerformGetAll(objectTypeId, sql1 => sql1.Where(" umbracoNode.uniqueID in (@keys)", new { /*keys =*/ keys })) + : PerformGetAll(objectTypeId); } private IEnumerable PerformGetAll(Guid objectTypeId, Action filter = null) { - bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); - bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); + var isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); + var isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, filter); var factory = new UmbracoEntityFactory(); @@ -184,23 +153,14 @@ namespace Umbraco.Core.Persistence.Repositories { //for now treat media differently //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields - var entities = _work.Database.Fetch( - new UmbracoEntityRelator().Map, sql); - foreach (var entity in entities) - { - yield return entity; - } + return UnitOfWork.Database + .Fetch(sql) + .Transform(new UmbracoEntityRelator().MapAll); } - else - { - var dtos = _work.Database.Fetch(sql); - foreach (var entity in dtos.Select(dto => factory.BuildEntityFromDynamic(dto))) - { - yield return entity; - } - } - } + var dtos = UnitOfWork.Database.Fetch(sql); + return dtos.Select(dto => (UmbracoEntity) factory.BuildEntityFromDynamic(dto)); + } public virtual IEnumerable GetByQuery(IQuery query) { @@ -208,7 +168,7 @@ namespace Umbraco.Core.Persistence.Repositories var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate().Append(GetGroupBy(false, false)); - var dtos = _work.Database.Fetch(sql); + var dtos = UnitOfWork.Database.Fetch(sql); var factory = new UmbracoEntityFactory(); var list = dtos.Select(factory.BuildEntityFromDynamic).Cast().ToList(); @@ -218,12 +178,11 @@ namespace Umbraco.Core.Persistence.Repositories public virtual IEnumerable GetByQuery(IQuery query, Guid objectTypeId) { - - bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); - bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); + var isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); + var isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, null, objectTypeId); - + var translator = new SqlTranslator(sqlClause, query); var entitySql = translator.Translate(); @@ -242,82 +201,80 @@ namespace Umbraco.Core.Persistence.Repositories } }); - //treat media differently for now + //treat media differently for now //TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields - var entities = _work.Database.Fetch( - new UmbracoEntityRelator().Map, mediaSql); - return entities; - } - else - { - //use dynamic so that we can get ALL properties from the SQL so we can chuck that data into our AdditionalData - var finalSql = entitySql.Append(GetGroupBy(isContent, false)); - var dtos = _work.Database.Fetch(finalSql); - return dtos.Select(factory.BuildEntityFromDynamic).Cast().ToList(); + return UnitOfWork.Database + .Fetch(mediaSql) + .Transform(new UmbracoEntityRelator().MapAll); } + + //use dynamic so that we can get ALL properties from the SQL so we can chuck that data into our AdditionalData + var finalSql = entitySql.Append(GetGroupBy(isContent, false)); + var dtos = UnitOfWork.Database.Fetch(finalSql); + return dtos.Select(factory.BuildEntityFromDynamic).Cast().ToList(); } public UmbracoObjectTypes GetObjectType(int id) { - var sql = new Sql().Select("nodeObjectType").From(_sqlSyntax).Where(_sqlSyntax, x => x.NodeId == id); - var nodeObjectTypeId = _work.Database.ExecuteScalar(sql); + var sql = Sql().Select("nodeObjectType").From().Where(x => x.NodeId == id); + var nodeObjectTypeId = UnitOfWork.Database.ExecuteScalar(sql); var objectTypeId = nodeObjectTypeId; return UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); } public UmbracoObjectTypes GetObjectType(Guid key) { - var sql = new Sql().Select("nodeObjectType").From(_sqlSyntax).Where(_sqlSyntax, x => x.UniqueId == key); - var nodeObjectTypeId = _work.Database.ExecuteScalar(sql); + var sql = Sql().Select("nodeObjectType").From().Where(x => x.UniqueId == key); + var nodeObjectTypeId = UnitOfWork.Database.ExecuteScalar(sql); var objectTypeId = nodeObjectTypeId; return UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); } #endregion - + #region Sql Statements - protected Sql GetFullSqlForEntityType(Guid key, bool isContent, bool isMedia, Guid objectTypeId) + protected Sql GetFullSqlForEntityType(Guid key, bool isContent, bool isMedia, Guid objectTypeId) { var entitySql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, key); - if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false)); - - return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false))); + return isMedia + ? GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false))) + : entitySql.Append(GetGroupBy(isContent, false)); } - protected Sql GetFullSqlForEntityType(int id, bool isContent, bool isMedia, Guid objectTypeId) + protected Sql GetFullSqlForEntityType(int id, bool isContent, bool isMedia, Guid objectTypeId) { var entitySql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, id); - if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false)); - - return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false))); + return isMedia + ? GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false))) + : entitySql.Append(GetGroupBy(isContent, false)); } - protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectTypeId, Action filter) + protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectTypeId, Action> filter) { var entitySql = GetBaseWhere(GetBase, isContent, isMedia, filter, objectTypeId); - if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false)); - - return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)), filter); + return isMedia + ? GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)), filter) + : entitySql.Append(GetGroupBy(isContent, false)); } - private Sql GetFullSqlForMedia(Sql entitySql, Action filter = null) + private Sql GetFullSqlForMedia(Sql entitySql, Action> filter = null) { //this will add any dataNvarchar property to the output which can be added to the additional properties - var joinSql = new Sql() + var joinSql = Sql() .Select("contentNodeId, versionId, dataNvarchar, dataNtext, propertyEditorAlias, alias as propertyTypeAlias") - .From(_sqlSyntax) - .InnerJoin(_sqlSyntax) - .On(_sqlSyntax, dto => dto.NodeId, dto => dto.NodeId) - .InnerJoin(_sqlSyntax) - .On(_sqlSyntax, dto => dto.Id, dto => dto.PropertyTypeId) - .InnerJoin(_sqlSyntax) - .On(_sqlSyntax, dto => dto.DataTypeId, dto => dto.DataTypeId) + .From() + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.NodeId) + .InnerJoin() + .On(dto => dto.Id, dto => dto.PropertyTypeId) + .InnerJoin() + .On(dto => dto.DataTypeId, dto => dto.DataTypeId) .Where("umbracoNode.nodeObjectType = @nodeObjectType", new {nodeObjectType = Constants.ObjectTypes.Media}); if (filter != null) @@ -325,21 +282,22 @@ namespace Umbraco.Core.Persistence.Repositories filter(joinSql); } - //We're going to create a query to query against the entity SQL + //We're going to create a query to query against the entity SQL // because we cannot group by nText columns and we have a COUNT in the entitySql we cannot simply left join // the entitySql query, we have to join the wrapped query to get the ntext in the result - - var wrappedSql = new Sql("SELECT * FROM (") + + var wrappedSql = Sql() + .Append("SELECT * FROM (") .Append(entitySql) - .Append(new Sql(") tmpTbl LEFT JOIN (")) + .Append(") tmpTbl LEFT JOIN (") .Append(joinSql) - .Append(new Sql(") as property ON id = property.contentNodeId")) + .Append(") as property ON id = property.contentNodeId") .OrderBy("sortOrder, id"); return wrappedSql; } - protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter) + protected virtual Sql GetBase(bool isContent, bool isMedia, Action> customFilter) { var columns = new List { @@ -369,10 +327,10 @@ namespace Umbraco.Core.Persistence.Repositories //Creates an SQL query to return a single row for the entity - var entitySql = new Sql() + var entitySql = Sql() .Select(columns.ToArray()) .From("umbracoNode umbracoNode"); - + if (isContent || isMedia) { entitySql.InnerJoin("cmsContent content").On("content.nodeId = umbracoNode.id") @@ -395,14 +353,14 @@ namespace Umbraco.Core.Persistence.Repositories return entitySql; } - protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Action filter, Guid nodeObjectType) + protected virtual Sql GetBaseWhere(Func>, Sql> baseQuery, bool isContent, bool isMedia, Action> filter, Guid nodeObjectType) { var sql = baseQuery(isContent, isMedia, filter) .Where("umbracoNode.nodeObjectType = @NodeObjectType", new { NodeObjectType = nodeObjectType }); return sql; } - protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, int id) + protected virtual Sql GetBaseWhere(Func>, Sql> baseQuery, bool isContent, bool isMedia, int id) { var sql = baseQuery(isContent, isMedia, null) .Where("umbracoNode.id = @Id", new { Id = id }) @@ -410,7 +368,7 @@ namespace Umbraco.Core.Persistence.Repositories return sql; } - protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Guid key) + protected virtual Sql GetBaseWhere(Func>, Sql> baseQuery, bool isContent, bool isMedia, Guid key) { var sql = baseQuery(isContent, isMedia, null) .Where("umbracoNode.uniqueID = @UniqueID", new { UniqueID = key }) @@ -418,7 +376,7 @@ namespace Umbraco.Core.Persistence.Repositories return sql; } - protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, int id) + protected virtual Sql GetBaseWhere(Func>, Sql> baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, int id) { var sql = baseQuery(isContent, isMedia, null) .Where("umbracoNode.id = @Id AND umbracoNode.nodeObjectType = @NodeObjectType", @@ -426,7 +384,7 @@ namespace Umbraco.Core.Persistence.Repositories return sql; } - protected virtual Sql GetBaseWhere(Func, Sql> baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, Guid key) + protected virtual Sql GetBaseWhere(Func>, Sql> baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, Guid key) { var sql = baseQuery(isContent, isMedia, null) .Where("umbracoNode.uniqueID = @UniqueID AND umbracoNode.nodeObjectType = @NodeObjectType", @@ -434,7 +392,7 @@ namespace Umbraco.Core.Persistence.Repositories return sql; } - protected virtual Sql GetGroupBy(bool isContent, bool isMedia, bool includeSort = true) + protected virtual Sql GetGroupBy(bool isContent, bool isMedia, bool includeSort = true) { var columns = new List { @@ -460,9 +418,8 @@ namespace Umbraco.Core.Persistence.Repositories columns.Add("contenttype.thumbnail"); columns.Add("contenttype.isContainer"); } - - var sql = new Sql() - .GroupBy(columns.ToArray()); + + var sql = Sql().GroupBy(columns.ToArray()); if (includeSort) { @@ -471,7 +428,7 @@ namespace Umbraco.Core.Persistence.Repositories return sql; } - + #endregion /// @@ -512,7 +469,7 @@ namespace Umbraco.Core.Persistence.Repositories [ResultColumn] public List UmbracoPropertyDtos { get; set; } } - + [ExplicitColumns] internal class UmbracoPropertyDto { @@ -542,52 +499,73 @@ namespace Umbraco.Core.Persistence.Repositories internal UmbracoEntity Current; private readonly UmbracoEntityFactory _factory = new UmbracoEntityFactory(); - internal UmbracoEntity Map(dynamic a, UmbracoPropertyDto p) + public IEnumerable MapAll(IEnumerable input) + { + UmbracoEntity entity; + + foreach (var x in input) + { + entity = Map(x); + if (entity != null) yield return entity; + } + + entity = Map((dynamic) null); + if (entity != null) yield return entity; + } + + // must be called one last time with null in order to return the last one! + public UmbracoEntity Map(dynamic a) { // Terminating call. Since we can return null from this function - // we need to be ready for PetaPoco to callback later with null + // we need to be ready for NPoco to callback later with null // parameters if (a == null) return Current; + string pPropertyEditorAlias = a.propertyEditorAlias; + var pExists = pPropertyEditorAlias != null; + string pPropertyAlias = a.propertyTypeAlias; + string pNTextValue = a.dataNtext; + string pNVarcharValue = a.dataNvarchar; + // Is this the same UmbracoEntity as the current one we're processing if (Current != null && Current.Key == a.uniqueID) { - if (p != null && p.PropertyAlias.IsNullOrWhiteSpace() == false) + if (pExists && pPropertyAlias.IsNullOrWhiteSpace() == false) { // Add this UmbracoProperty to the current additional data - Current.AdditionalData[p.PropertyAlias] = new UmbracoEntity.EntityProperty + Current.AdditionalData[pPropertyAlias] = new UmbracoEntity.EntityProperty { - PropertyEditorAlias = p.PropertyEditorAlias, - Value = p.NTextValue.IsNullOrWhiteSpace() - ? p.NVarcharValue - : p.NTextValue.ConvertToJsonIfPossible() - }; + PropertyEditorAlias = pPropertyEditorAlias, + Value = pNTextValue.IsNullOrWhiteSpace() + ? pNVarcharValue + : pNTextValue.ConvertToJsonIfPossible() + }; } // Return null to indicate we're not done with this UmbracoEntity yet return null; } - // This is a different UmbracoEntity to the current one, or this is the + // This is a different UmbracoEntity to the current one, or this is the // first time through and we don't have a Tab yet // Save the current UmbracoEntityDto var prev = Current; // Setup the new current UmbracoEntity - + Current = _factory.BuildEntityFromDynamic(a); - if (p != null && p.PropertyAlias.IsNullOrWhiteSpace() == false) + if (pExists && pPropertyAlias.IsNullOrWhiteSpace() == false) { //add the property/create the prop list if null - Current.AdditionalData[p.PropertyAlias] = new UmbracoEntity.EntityProperty + Current.AdditionalData[pPropertyAlias] = new UmbracoEntity.EntityProperty { - PropertyEditorAlias = p.PropertyEditorAlias, - Value = p.NTextValue.IsNullOrWhiteSpace() - ? p.NVarcharValue - : p.NTextValue.ConvertToJsonIfPossible() + PropertyEditorAlias = pPropertyEditorAlias, + Value = pNTextValue.IsNullOrWhiteSpace() + ? pNVarcharValue + : pNTextValue.ConvertToJsonIfPossible() }; } diff --git a/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs index 249ecef23d..aa5340232d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ExternalLoginRepository.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNet.Identity; +using NPoco; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Identity; @@ -14,7 +15,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - internal class ExternalLoginRepository : PetaPocoRepositoryBase, IExternalLoginRepository + internal class ExternalLoginRepository : NPocoRepositoryBase, IExternalLoginRepository { public ExternalLoginRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) : base(work, cache, logger, sqlSyntax, mappingResolver) @@ -122,17 +123,14 @@ namespace Umbraco.Core.Persistence.Repositories } } - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); + var sql = Sql(); if (isCount) - { - sql.Select("COUNT(*)").From(SqlSyntax); - } + sql.SelectCount(); else - { - sql.Select("*").From(SqlSyntax); - } + sql.SelectAll(); + sql.From(); return sql; } diff --git a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs index 8fbc122821..f0304ffa58 100644 --- a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -18,11 +19,11 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Represents a repository for doing CRUD operations for /// - internal class LanguageRepository : PetaPocoRepositoryBase, ILanguageRepository + internal class LanguageRepository : NPocoRepositoryBase, ILanguageRepository { public LanguageRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) : base(work, cache, logger, sqlSyntax, mappingResolver) - { + { } private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; @@ -54,9 +55,9 @@ namespace Umbraco.Core.Persistence.Repositories //this needs to be sorted since that is the way legacy worked - default language is the first one!! //even though legacy didn't sort, it should be by id - sql.OrderBy(SqlSyntax, dto => dto.Id); + sql.OrderBy(dto => dto.Id); + - return Database.Fetch(sql).Select(ConvertFromDto); } @@ -70,13 +71,18 @@ namespace Umbraco.Core.Persistence.Repositories #endregion - #region Overrides of PetaPocoRepositoryBase + #region Overrides of NPocoRepositoryBase - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From(SqlSyntax); + var sql = Sql(); + + sql = isCount + ? sql.SelectCount() + : sql.Select(); + + sql + .From(); return sql; } @@ -166,6 +172,6 @@ namespace Umbraco.Core.Persistence.Repositories return GetAll().FirstOrDefault(x => x.IsoCode.InvariantEquals(isoCode)); } - + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs index 69274032a2..f3dbf6ec34 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -9,13 +10,12 @@ using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - internal class MacroRepository : PetaPocoRepositoryBase, IMacroRepository + internal class MacroRepository : NPocoRepositoryBase, IMacroRepository { public MacroRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) @@ -28,7 +28,10 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var macroDto = Database.Fetch(new MacroPropertyRelator().Map, sql).FirstOrDefault(); + var macroDto = Database + .FetchOneToMany(x => x.MacroPropertyDtos, sql) + .FirstOrDefault(); + if (macroDto == null) return null; @@ -51,8 +54,10 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); - return ConvertFromDtos(Database.Fetch(new MacroPropertyRelator().Map, sql)) - .ToArray();// we don't want to re-iterate again! + return Database + .FetchOneToMany(x => x.MacroPropertyDtos, sql) + .Transform(ConvertFromDtos) + .ToArray(); // do it now and once } private IEnumerable PerformGetAllOnIds(params int[] ids) @@ -83,36 +88,23 @@ namespace Umbraco.Core.Persistence.Repositories var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); - var dtos = Database.Fetch(new MacroPropertyRelator().Map, sql); - - foreach (var dto in dtos) - { - yield return Get(dto.Id); - } + return Database + .FetchOneToMany(x => x.MacroPropertyDtos, sql) + .Select(x => Get(x.Id)); } - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); - if (isCount) - { - sql.Select("COUNT(*)").From(SqlSyntax); - } - else - { - return GetBaseQuery(); - } - return sql; + return isCount ? Sql().SelectCount().From() : GetBaseQuery(); } - private Sql GetBaseQuery() + private Sql GetBaseQuery() { - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .LeftJoin(SqlSyntax) - .On(SqlSyntax, left => left.Id, right => right.Macro); - return sql; + return Sql() + .SelectAll() + .From() + .LeftJoin() + .On(left => left.Id, right => right.Macro); } protected override string GetBaseWhereClause() @@ -125,7 +117,7 @@ namespace Umbraco.Core.Persistence.Repositories var list = new List { "DELETE FROM cmsMacroProperty WHERE macro = @Id", - "DELETE FROM cmsMacro WHERE id = @Id" + "DELETE FROM cmsMacro WHERE id = @Id" }; return list; } @@ -208,7 +200,7 @@ namespace Umbraco.Core.Persistence.Repositories } } - + } entity.ResetDirtyProperties(); @@ -230,7 +222,7 @@ namespace Umbraco.Core.Persistence.Repositories // { // return GetAll(new int[] {}); // } - + //} } } \ 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 2d1098164f..fbdbb8ca55 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; using System.Text.RegularExpressions; using System.Xml.Linq; +using NPoco; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Dynamics; @@ -54,9 +55,9 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(SqlSyntax, x => x.VersionDate); + sql.OrderByDescending(x => x.VersionDate); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(sql).FirstOrDefault(); if (dto == null) return null; @@ -82,25 +83,33 @@ namespace Umbraco.Core.Persistence.Repositories var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate() - .OrderBy(SqlSyntax, x => x.SortOrder); + .OrderBy(x => x.SortOrder); return ProcessQuery(sql); } #endregion - #region Overrides of PetaPocoRepositoryBase + #region Overrides of NPocoRepositoryBase - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + var sql = Sql(); + + sql = isCount + ? sql.SelectCount() + : sql.Select(r => + r.Select(rr => + rr.Select())); + + sql + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; } @@ -143,9 +152,9 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(SqlSyntax, x => x.VersionDate); + sql.OrderByDescending(x => x.VersionDate); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(sql).FirstOrDefault(); if (dto == null) return null; @@ -155,7 +164,7 @@ namespace Umbraco.Core.Persistence.Repositories var factory = new MediaFactory(mediaType, NodeObjectTypeId, dto.NodeId); var media = factory.BuildEntity(dto); - var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.VersionId, media.UpdateDate, media.CreateDate, mediaType) }); + var properties = GetPropertyCollection(new[] { new DocumentDefinition(dto.NodeId, dto.VersionId, media.UpdateDate, media.CreateDate, mediaType) }); media.Properties = properties[dto.NodeId]; @@ -175,12 +184,12 @@ namespace Umbraco.Core.Persistence.Repositories if (contentTypeIds == null) { var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media); - var subQuery = new Sql() + var subQuery = Sql() .Select("DISTINCT cmsContentXml.nodeId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, dto => dto.NodeObjectType == mediaObjectType); + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeObjectType == mediaObjectType); var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); Database.Execute(deleteSql); @@ -191,15 +200,15 @@ namespace Umbraco.Core.Persistence.Repositories { var id1 = id; var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media); - var subQuery = new Sql() + var subQuery = Sql() .Select("DISTINCT cmsContentXml.nodeId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, dto => dto.NodeObjectType == mediaObjectType) - .Where(SqlSyntax, dto => dto.ContentTypeId == id1); + .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); @@ -226,7 +235,7 @@ namespace Umbraco.Core.Persistence.Repositories } } - private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, Transaction tr, int pageSize) + private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, ITransaction tr, int pageSize) { var pageIndex = 0; var total = long.MinValue; @@ -262,23 +271,23 @@ namespace Umbraco.Core.Persistence.Repositories umbracoFileValue = string.Concat(mediaPath.Substring(0, underscoreIndex), mediaPath.Substring(dotIndex)); } - Func createSql = url => new Sql().Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.PropertyTypeId, right => right.Id) - .Where(SqlSyntax, x => x.Alias == "umbracoFile") - .Where(SqlSyntax, x => x.VarChar == url); + Func createSql = url => Sql().SelectAll() + .From() + .InnerJoin() + .On(left => left.PropertyTypeId, right => right.Id) + .Where(x => x.Alias == "umbracoFile") + .Where(x => x.VarChar == url); var sql = createSql(umbracoFileValue); - var propertyDataDto = Database.Fetch(sql).FirstOrDefault(); + var propertyDataDto = Database.Fetch(sql).FirstOrDefault(); - // If the stripped-down url returns null, we try again with the original url. + // If the stripped-down url returns null, we try again with the original url. // Previously, the function would fail on e.g. "my_x_image.jpg" if (propertyDataDto == null) { sql = createSql(mediaPath); - propertyDataDto = Database.Fetch(sql).FirstOrDefault(); + propertyDataDto = Database.Fetch(sql).FirstOrDefault(); } return propertyDataDto == null ? null : Get(propertyDataDto.NodeId); @@ -332,7 +341,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); @@ -484,28 +493,24 @@ namespace Umbraco.Core.Persistence.Repositories public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, string filter = "") { - var args = new List(); - var sbWhere = new StringBuilder(); - Func> filterCallback = null; - if (filter.IsNullOrWhiteSpace() == false) - { - sbWhere.Append("AND (umbracoNode." + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ")"); - args.Add("%" + filter + "%"); - filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray()); - } - - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - new Tuple("cmsContentVersion", "contentId"), - ProcessQuery, orderBy, orderDirection, - filterCallback); + var filterSql = filter.IsNullOrWhiteSpace() + ? null + : Sql().Append("AND (umbracoNode." + SqlSyntax.GetQuotedColumnName("text") + " LIKE @0)", "%" + filter + "%"); + return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, + MapQueryDtos, orderBy, orderDirection, + filterSql); } private IEnumerable ProcessQuery(Sql sql) { //NOTE: This doesn't allow properties to be part of the query - var dtos = Database.Fetch(sql); - + var dtos = Database.Fetch(sql); + return MapQueryDtos(dtos); + } + + private IEnumerable MapQueryDtos(List dtos) + { var ids = dtos.Select(x => x.ContentDto.ContentTypeId).ToArray(); //content types @@ -527,7 +532,7 @@ namespace Umbraco.Core.Persistence.Repositories d.contentType)) .ToArray(); - var propertyData = GetPropertyCollection(sql, docDefs); + var propertyData = GetPropertyCollection(docDefs); return dtosWithContentTypes.Select(d => CreateMediaFromDto( d.dto, @@ -573,7 +578,7 @@ namespace Umbraco.Core.Persistence.Repositories var docDef = new DocumentDefinition(dto.NodeId, versionId, media.UpdateDate, media.CreateDate, contentType); - var properties = GetPropertyCollection(docSql, new[] { docDef }); + var properties = GetPropertyCollection(new[] { docDef }); media.Properties = properties[dto.NodeId]; @@ -588,10 +593,10 @@ namespace Umbraco.Core.Persistence.Repositories if (EnsureUniqueNaming == false) return nodeName; - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId && x.ParentId == parentId && x.Text.StartsWith(nodeName)); + var sql = Sql() + .SelectAll() + .From() + .Where(x => x.NodeObjectType == NodeObjectTypeId && x.ParentId == parentId && x.Text.StartsWith(nodeName)); int uniqueNumber = 1; var currentName = nodeName; diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index 4e1654c2ad..3d7eedd246 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Events; using Umbraco.Core.Exceptions; @@ -66,7 +67,7 @@ namespace Umbraco.Core.Persistence.Repositories var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); - var dtos = Database.Fetch(sql); + var dtos = Database.Fetch(sql); return //This returns a lookup from the GetAll cached looup @@ -90,14 +91,21 @@ namespace Umbraco.Core.Persistence.Repositories : Enumerable.Empty(); } - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + var sql = Sql(); + + sql = isCount + ? sql.SelectCount() + : sql.Select(r => + r.Select()); + + sql + .From() + .InnerJoin() + .On( left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs index 291454d9d3..1ff51447ec 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -19,7 +20,7 @@ namespace Umbraco.Core.Persistence.Repositories { - internal class MemberGroupRepository : PetaPocoRepositoryBase, IMemberGroupRepository + internal class MemberGroupRepository : NPocoRepositoryBase, IMemberGroupRepository { public MemberGroupRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) : base(work, cache, logger, sqlSyntax, mappingResolver) @@ -40,24 +41,15 @@ namespace Umbraco.Core.Persistence.Repositories protected override IEnumerable PerformGetAll(params int[] ids) { + var sql = Sql() + .SelectAll() + .From() + .Where(dto => dto.NodeObjectType == NodeObjectTypeId); + if (ids.Any()) - { - var sql = new Sql() - .Select("*") - .From(SqlSyntax) - .Where(SqlSyntax, dto => dto.NodeObjectType == NodeObjectTypeId) - .Where("umbracoNode.id in (@ids)", new { ids = ids }); - return Database.Fetch(sql) - .Select(x => _modelFactory.BuildEntity(x)); - } - else - { - var sql = new Sql() - .From(SqlSyntax) - .Where(SqlSyntax, dto => dto.NodeObjectType == NodeObjectTypeId); - return Database.Fetch(sql) - .Select(x => _modelFactory.BuildEntity(x)); - } + sql.Where("umbracoNode.id in (@ids)", new { /*ids =*/ ids }); + + return Database.Fetch(sql).Select(x => _modelFactory.BuildEntity(x)); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -66,16 +58,21 @@ namespace Umbraco.Core.Persistence.Repositories var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); - return Database.Fetch(sql) - .Select(x => _modelFactory.BuildEntity(x)); + return Database.Fetch(sql).Select(x => _modelFactory.BuildEntity(x)); } - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From(SqlSyntax) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + var sql = Sql(); + + sql = isCount + ? sql.SelectCount() + : sql.Select(); + + sql + .From() + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; } @@ -110,7 +107,7 @@ namespace Umbraco.Core.Persistence.Repositories var group = (MemberGroup)entity; group.AddingEntity(); var dto = _modelFactory.BuildDto(group); - var o = Database.IsNew(dto) ? Convert.ToInt32(Database.Insert(dto)) : Database.Update(dto); + var o = Database.IsNew(dto) ? Convert.ToInt32(Database.Insert(dto)) : Database.Update(dto); group.Id = dto.NodeId; //Set Id on entity to ensure an Id is set //Update with new correct path and id @@ -176,13 +173,13 @@ namespace Umbraco.Core.Persistence.Repositories public IEnumerable GetMemberGroupsForMember(int memberId) { - var sql = new Sql(); - sql.Select("umbracoNode.*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.NodeId, dto => dto.MemberGroup) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId) - .Where(SqlSyntax, x => x.Member == memberId); + var sql = Sql() + .Select("umbracoNode.*") + .From() + .InnerJoin() + .On( dto => dto.NodeId, dto => dto.MemberGroup) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.Member == memberId); return Database.Fetch(sql) .DistinctBy(dto => dto.NodeId) @@ -192,28 +189,28 @@ namespace Umbraco.Core.Persistence.Repositories public IEnumerable GetMemberGroupsForMember(string username) { //find the member by username - var memberSql = new Sql(); var memberObjectType = new Guid(Constants.ObjectTypes.Member); - - memberSql.Select("umbracoNode.id") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.NodeId, dto => dto.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == memberObjectType) - .Where(SqlSyntax, x => x.LoginName == username); + + var memberSql = Sql() + .Select("umbracoNode.id") + .From() + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.NodeId) + .Where( x => x.NodeObjectType == memberObjectType) + .Where(x => x.LoginName == username); var memberIdUsername = Database.Fetch(memberSql).FirstOrDefault(); if (memberIdUsername.HasValue == false) { return Enumerable.Empty(); } - var sql = new Sql(); - sql.Select("umbracoNode.*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.NodeId, dto => dto.MemberGroup) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId) - .Where(SqlSyntax, x => x.Member == memberIdUsername.Value); + var sql = Sql() + .Select("umbracoNode.*") + .From() + .InnerJoin() + .On( dto => dto.NodeId, dto => dto.MemberGroup) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.Member == memberIdUsername.Value); return Database.Fetch(sql) .DistinctBy(dto => dto.NodeId) @@ -225,14 +222,15 @@ namespace Umbraco.Core.Persistence.Repositories using (var transaction = Database.GetTransaction()) { //first get the member ids based on the usernames - var memberSql = new Sql(); var memberObjectType = new Guid(Constants.ObjectTypes.Member); - memberSql.Select("umbracoNode.id") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.NodeId, dto => dto.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == memberObjectType) - .Where("cmsMember.LoginName in (@usernames)", new { usernames = usernames }); + + var memberSql = Sql() + .Select("umbracoNode.id") + .From() + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.NodeId) + .Where(x => x.NodeObjectType == memberObjectType) + .Where("cmsMember.LoginName in (@usernames)", new { /*usernames =*/ usernames }); var memberIds = Database.Fetch(memberSql).ToArray(); AssignRolesInternal(memberIds, roleNames); @@ -245,14 +243,15 @@ namespace Umbraco.Core.Persistence.Repositories using (var transaction = Database.GetTransaction()) { //first get the member ids based on the usernames - var memberSql = new Sql(); var memberObjectType = new Guid(Constants.ObjectTypes.Member); - memberSql.Select("umbracoNode.id") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.NodeId, dto => dto.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == memberObjectType) - .Where("cmsMember.LoginName in (@usernames)", new { usernames = usernames }); + + var memberSql = Sql() + .Select("umbracoNode.id") + .From() + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.NodeId) + .Where( x => x.NodeObjectType == memberObjectType) + .Where("cmsMember.LoginName in (@usernames)", new { /*usernames =*/ usernames }); var memberIds = Database.Fetch(memberSql).ToArray(); DissociateRolesInternal(memberIds, roleNames); @@ -276,10 +275,10 @@ namespace Umbraco.Core.Persistence.Repositories //create the missing roles first - var existingSql = new Sql() - .Select("*") - .From(SqlSyntax) - .Where(SqlSyntax, dto => dto.NodeObjectType == NodeObjectTypeId) + var existingSql = Sql() + .SelectAll() + .From() + .Where(dto => dto.NodeObjectType == NodeObjectTypeId) .Where("umbracoNode." + SqlSyntax.GetQuotedColumnName("text") + " in (@names)", new { names = roleNames }); var existingRoles = Database.Fetch(existingSql).Select(x => x.Text); var missingRoles = roleNames.Except(existingRoles); @@ -300,16 +299,16 @@ namespace Umbraco.Core.Persistence.Repositories //get the groups that are currently assigned to any of these members - var assignedSql = new Sql(); - assignedSql.Select(string.Format( + var assignedSql = Sql() + .Select(string.Format( "{0},{1},{2}", SqlSyntax.GetQuotedColumnName("text"), SqlSyntax.GetQuotedColumnName("Member"), SqlSyntax.GetQuotedColumnName("MemberGroup"))) - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.NodeId, dto => dto.MemberGroup) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId) + .From() + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.MemberGroup) + .Where(x => x.NodeObjectType == NodeObjectTypeId) .Where("cmsMember2MemberGroup.Member in (@ids)", new { ids = memberIds }); var currentlyAssigned = Database.Fetch(assignedSql).ToArray(); @@ -343,15 +342,15 @@ namespace Umbraco.Core.Persistence.Repositories private void DissociateRolesInternal(int[] memberIds, string[] roleNames) { - var existingSql = new Sql() - .Select("*") - .From(SqlSyntax) - .Where(SqlSyntax, dto => dto.NodeObjectType == NodeObjectTypeId) + var existingSql = Sql() + .SelectAll() + .From() + .Where(dto => dto.NodeObjectType == NodeObjectTypeId) .Where("umbracoNode." + SqlSyntax.GetQuotedColumnName("text") + " in (@names)", new { names = roleNames }); var existingRolesIds = Database.Fetch(existingSql).Select(x => x.NodeId).ToArray(); Database.Execute("DELETE FROM cmsMember2MemberGroup WHERE Member IN (@memberIds) AND MemberGroup IN (@memberGroups)", - new { memberIds = memberIds, memberGroups = existingRolesIds }); + new { /*memberIds =*/ memberIds, memberGroups = existingRolesIds }); } private class AssignedRolesDto diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index e1b2b877e3..a97c716894 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -5,6 +5,7 @@ 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; @@ -16,7 +17,6 @@ using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Dynamics; @@ -53,9 +53,9 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(SqlSyntax, x => x.VersionDate); + sql.OrderByDescending(x => x.VersionDate); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(sql).FirstOrDefault(); if (dto == null) return null; @@ -74,8 +74,8 @@ namespace Umbraco.Core.Persistence.Repositories sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); } - return ProcessQuery(sql); - + return MapQueryDtos(Database.Fetch(sql)); + } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -93,42 +93,51 @@ namespace Umbraco.Core.Persistence.Repositories var translator = new SqlTranslator(sqlWithProps, query); var sql = translator.Translate(); - baseQuery.Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)) - .OrderBy(SqlSyntax, x => x.SortOrder); + baseQuery.Append("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments) + .OrderBy(x => x.SortOrder); - return ProcessQuery(baseQuery); + return MapQueryDtos(Database.Fetch(baseQuery)); } else { var translator = new SqlTranslator(baseQuery, query); var sql = translator.Translate() - .OrderBy(SqlSyntax, x => x.SortOrder); + .OrderBy(x => x.SortOrder); - return ProcessQuery(sql); + return MapQueryDtos(Database.Fetch(sql)); } } #endregion - #region Overrides of PetaPocoRepositoryBase + #region Overrides of NPocoRepositoryBase - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + var sql = Sql(); + + sql = isCount + ? sql.SelectCount() + : sql.Select(r => + r.Select(rr => + rr.Select(rrr => + rrr.Select()))); + + sql + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) //We're joining the type so we can do a query against the member type - not sure if this adds much overhead or not? // the execution plan says it doesn't so we'll go with that and in that case, it might be worth joining the content // types by default on the document and media repo's so we can query by content type there too. - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.ContentTypeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; } @@ -138,21 +147,20 @@ namespace Umbraco.Core.Persistence.Repositories return "umbracoNode.id = @Id"; } - protected Sql GetNodeIdQueryWithPropertyData() + protected Sql GetNodeIdQueryWithPropertyData() { - var sql = new Sql(); - sql.Select("DISTINCT(umbracoNode.id)") - .From(SqlSyntax) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.ContentTypeId) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.ContentTypeId, right => right.ContentTypeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.DataTypeId, right => right.DataTypeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.PropertyTypeId, right => right.Id) + return Sql() + .Select("DISTINCT(umbracoNode.id)") + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .LeftJoin().On(left => left.ContentTypeId, right => right.ContentTypeId) + .LeftJoin().On(left => left.DataTypeId, right => right.DataTypeId) + .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) .Append("AND cmsPropertyData.versionId = cmsContentVersion.VersionId") - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); - return sql; + .Where(x => x.NodeObjectType == NodeObjectTypeId); } protected override IEnumerable GetDeleteClauses() @@ -208,7 +216,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); @@ -239,7 +247,7 @@ namespace Umbraco.Core.Persistence.Repositories //Add Properties // - don't try to save the property if it doesn't exist (or doesn't have an ID) on the content type // - this can occur if the member type doesn't contain the built-in properties that the - // - member object contains. + // - member object contains. var propsToPersist = entity.Properties.Where(x => x.PropertyType.HasIdentity).ToArray(); var propertyDataDtos = propertyFactory.BuildDto(propsToPersist); var keyDictionary = new Dictionary(); @@ -309,9 +317,9 @@ namespace Umbraco.Core.Persistence.Repositories //Updates the current version - cmsContentVersion //Assumes a Version guid exists and Version date (modified date) has been set/updated Database.Update(dto.ContentVersionDto); - + //Updates the cmsMember entry if it has changed - + //NOTE: these cols are the REAL column names in the db var changedCols = new List(); @@ -331,19 +339,19 @@ namespace Umbraco.Core.Persistence.Repositories //only update the changed cols if (changedCols.Count > 0) { - Database.Update(dto, changedCols); + Database.Update(dto, changedCols); } //TODO ContentType for the Member entity //Create the PropertyData for this version - cmsPropertyData - var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); + var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id); var keyDictionary = new Dictionary(); //Add Properties // - don't try to save the property if it doesn't exist (or doesn't have an ID) on the content type // - this can occur if the member type doesn't contain the built-in properties that the - // - member object contains. + // - member object contains. var propsToPersist = entity.Properties.Where(x => x.PropertyType.HasIdentity).ToArray(); var propertyDataDtos = propertyFactory.BuildDto(propsToPersist); @@ -389,12 +397,12 @@ namespace Umbraco.Core.Persistence.Repositories if (contentTypeIds == null) { var memberObjectType = Guid.Parse(Constants.ObjectTypes.Member); - var subQuery = new Sql() + var subQuery = Sql() .Select("DISTINCT cmsContentXml.nodeId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, dto => dto.NodeObjectType == memberObjectType); + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeObjectType == memberObjectType); var deleteSql = SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); Database.Execute(deleteSql); @@ -405,15 +413,15 @@ namespace Umbraco.Core.Persistence.Repositories { var id1 = id; var memberObjectType = Guid.Parse(Constants.ObjectTypes.Member); - var subQuery = new Sql() + var subQuery = Sql() .Select("DISTINCT cmsContentXml.nodeId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, dto => dto.NodeObjectType == memberObjectType) - .Where(SqlSyntax, dto => dto.ContentTypeId == id1); + .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); @@ -441,7 +449,7 @@ namespace Umbraco.Core.Persistence.Repositories } } - private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, Transaction tr, int pageSize) + private void RebuildXmlStructuresProcessQuery(Func serializer, IQuery query, ITransaction tr, int pageSize) { var pageIndex = 0; var total = long.MinValue; @@ -467,9 +475,9 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(SqlSyntax, x => x.VersionDate); + sql.OrderByDescending(x => x.VersionDate); - var dto = Database.Fetch(sql).FirstOrDefault(); + var dto = Database.Fetch(sql).FirstOrDefault(); if (dto == null) return null; @@ -479,7 +487,7 @@ namespace Umbraco.Core.Persistence.Repositories var factory = new MemberFactory(memberType, NodeObjectTypeId, dto.NodeId); var media = factory.BuildEntity(dto); - var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.ContentVersionDto.VersionId, media.UpdateDate, media.CreateDate, memberType) }); + var properties = GetPropertyCollection(new[] { new DocumentDefinition(dto.NodeId, dto.ContentVersionDto.VersionId, media.UpdateDate, media.CreateDate, memberType) }); media.Properties = properties[dto.NodeId]; @@ -537,8 +545,8 @@ namespace Umbraco.Core.Persistence.Repositories foreach (var batch in inGroups) { var memberIdBatch = batch.Select(x => x.Id); - var sql = new Sql().Select("*").From(SqlSyntax) - .Where(SqlSyntax, dto => dto.MemberGroup == memberGroup.Id) + var sql = Sql().SelectAll().From() + .Where(dto => dto.MemberGroup == memberGroup.Id) .Where("Member IN (@memberIds)", new { memberIds = memberIdBatch }); var memberIdsInGroup = Database.Fetch(sql) .Select(x => x.Member).ToArray(); @@ -560,27 +568,26 @@ namespace Umbraco.Core.Persistence.Repositories var grpQry = QueryFactory.Create().Where(group => group.Name.Equals(groupName)); var memberGroup = _memberGroupRepository.GetByQuery(grpQry).FirstOrDefault(); if (memberGroup == null) return Enumerable.Empty(); - - var subQuery = new Sql().Select("Member").From(SqlSyntax).Where(SqlSyntax, dto => dto.MemberGroup == memberGroup.Id); + + var subQuery = Sql().Select("Member").From().Where(dto => dto.MemberGroup == memberGroup.Id); var sql = GetBaseQuery(false) //TODO: An inner join would be better, though I've read that the query optimizer will always turn a // subquery with an IN clause into an inner join anyways. - .Append(new Sql("WHERE umbracoNode.id IN (" + subQuery.SQL + ")", subQuery.Arguments)) - .OrderByDescending(SqlSyntax, x => x.VersionDate) - .OrderBy(SqlSyntax, x => x.SortOrder); - - return ProcessQuery(sql); + .Append("WHERE umbracoNode.id IN (" + subQuery.SQL + ")", subQuery.Arguments) + .OrderByDescending(x => x.VersionDate) + .OrderBy(x => x.SortOrder); + + return MapQueryDtos(Database.Fetch(sql)); } public bool Exists(string username) { - var sql = new Sql(); - - sql.Select("COUNT(*)") - .From(SqlSyntax) - .Where(SqlSyntax, x => x.LoginName == username); + var sql = Sql() + .SelectCount() + .From() + .Where(x => x.LoginName == username); return Database.ExecuteScalar(sql) > 0; } @@ -594,7 +601,7 @@ namespace Umbraco.Core.Persistence.Repositories //get the COUNT base query var fullSql = GetBaseQuery(true) .Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments)); - + return Database.ExecuteScalar(fullSql); } @@ -612,28 +619,21 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// - /// The query supplied will ONLY work with data specifically on the cmsMember table because we are using PetaPoco paging (SQL paging) + /// The query supplied will ONLY work with data specifically on the cmsMember table because we are using NPoco paging (SQL paging) /// public IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, string filter = "") { - var args = new List(); - var sbWhere = new StringBuilder(); - Func> filterCallback = null; - if (filter.IsNullOrWhiteSpace() == false) - { - sbWhere.Append("AND ((umbracoNode. " + SqlSyntax.GetQuotedColumnName("text") + " LIKE @" + args.Count + ") " + - "OR (cmsMember.LoginName LIKE @0" + args.Count + "))"); - args.Add("%" + filter + "%"); - filterCallback = () => new Tuple(sbWhere.ToString().Trim(), args.ToArray()); - } + var filterSql = filter.IsNullOrWhiteSpace() + ? null + : Sql().Append("AND ((umbracoNode. " + SqlSyntax.GetQuotedColumnName("text") + " LIKE @0) " + + "OR (cmsMember.LoginName LIKE @0))", "%" + filter + "%"); - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - new Tuple("cmsMember", "nodeId"), - ProcessQuery, orderBy, orderDirection, - filterCallback); + return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, + MapQueryDtos, orderBy, orderDirection, + filterSql); } - + public void AddOrUpdateContentXml(IMember content, Func xml) { _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(content, xml)); @@ -658,23 +658,8 @@ namespace Umbraco.Core.Persistence.Repositories return base.GetDatabaseFieldNameForOrderBy(orderBy); } - protected override string GetEntityPropertyNameForOrderBy(string orderBy) + private IEnumerable MapQueryDtos(List dtos) { - //Some custom ones - switch (orderBy.ToUpperInvariant()) - { - case "LOGINNAME": - return "Username"; - } - - return base.GetEntityPropertyNameForOrderBy(orderBy); - } - - private IEnumerable ProcessQuery(Sql sql) - { - //NOTE: This doesn't allow properties to be part of the query - var dtos = Database.Fetch(sql); - var ids = dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray(); //content types @@ -695,12 +680,12 @@ namespace Umbraco.Core.Persistence.Repositories d.dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, d.contentType)); - var propertyData = GetPropertyCollection(sql, docDefs); + var propertyData = GetPropertyCollection(docDefs.ToArray()); return dtosWithContentTypes.Select(d => CreateMemberFromDto( d.dto, contentTypes.First(ct => ct.Id == d.dto.ContentVersionDto.ContentDto.ContentTypeId), - propertyData[d.dto.NodeId])); + propertyData[d.dto.NodeId])); } /// @@ -741,7 +726,7 @@ namespace Umbraco.Core.Persistence.Repositories var docDef = new DocumentDefinition(dto.ContentVersionDto.NodeId, versionId, member.UpdateDate, member.CreateDate, memberType); - var properties = GetPropertyCollection(docSql, new[] { docDef }); + var properties = GetPropertyCollection(new[] { docDef }); member.Properties = properties[dto.ContentVersionDto.NodeId]; diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 172d2f8296..21f46eb800 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using log4net; +using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; @@ -11,7 +12,6 @@ using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; @@ -40,7 +40,7 @@ namespace Umbraco.Core.Persistence.Repositories expires: true)); } } - + protected override IMemberType PerformGet(int id) { //use the underlying GetAll which will force cache all content types @@ -56,11 +56,12 @@ namespace Umbraco.Core.Persistence.Repositories var statement = string.Join(" OR ", ids.Select(x => string.Format("umbracoNode.id='{0}'", x))); sql.Where(statement); } - sql.OrderByDescending(SqlSyntax, x => x.NodeId); + sql.OrderByDescending(x => x.NodeId); - var dtos = - Database.Fetch( - new PropertyTypePropertyGroupRelator().Map, sql); + var dtos = Database + .Fetch(sql) // cannot use FetchOneToMany because we have 2 collections! + .Transform(MapOneToManies) + .ToList(); return BuildFromDtos(dtos); } @@ -71,57 +72,89 @@ namespace Umbraco.Core.Persistence.Repositories var translator = new SqlTranslator(sqlSubquery, query); var subquery = translator.Translate(); var sql = GetBaseQuery(false) - .Append(new Sql("WHERE umbracoNode.id IN (" + subquery.SQL + ")", subquery.Arguments)) - .OrderBy(SqlSyntax, x => x.SortOrder); + .Append("WHERE umbracoNode.id IN (" + subquery.SQL + ")", subquery.Arguments) + .OrderBy(x => x.SortOrder); - var dtos = - Database.Fetch( - new PropertyTypePropertyGroupRelator().Map, sql); + var dtos = Database + .Fetch(sql) // cannot use FetchOneToMany because we have 2 collections! + .Transform(MapOneToManies) + .ToList(); return BuildFromDtos(dtos); } - - protected override Sql GetBaseQuery(bool isCount) - { - var sql = new Sql(); - if (isCount) + private IEnumerable MapOneToManies(IEnumerable dtos) + { + MemberTypeReadOnlyDto acc = null; + foreach (var dto in dtos) { - sql.Select("COUNT(*)") - .From(SqlSyntax) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); - return sql; + if (acc == null) + { + acc = dto; + } + else if (acc.UniqueId == dto.UniqueId) + { + var prop = dto.PropertyTypes.SingleOrDefault(); + var group = dto.PropertyTypeGroups.SingleOrDefault(); + + if (prop != null && prop.Id.HasValue && acc.PropertyTypes.Any(x => x.Id == prop.Id.Value) == false) + acc.PropertyTypes.Add(prop); + + if (group != null && group.Id.HasValue && acc.PropertyTypeGroups.Any(x => x.Id == group.Id.Value) == false) + acc.PropertyTypeGroups.Add(group); + } + else + { + yield return acc; + acc = dto; + } } - sql.Select("umbracoNode.*", "cmsContentType.*", "cmsPropertyType.id AS PropertyTypeId", "cmsPropertyType.Alias", - "cmsPropertyType.Name", "cmsPropertyType.Description", "cmsPropertyType.mandatory", - "cmsPropertyType.validationRegExp", "cmsPropertyType.dataTypeId", "cmsPropertyType.sortOrder AS PropertyTypeSortOrder", - "cmsPropertyType.propertyTypeGroupId AS PropertyTypesGroupId", "cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile", - "cmsDataType.propertyEditorAlias", "cmsDataType.dbType", "cmsPropertyTypeGroup.id AS PropertyTypeGroupId", - "cmsPropertyTypeGroup.text AS PropertyGroupName", - "cmsPropertyTypeGroup.sortorder AS PropertyGroupSortOrder", "cmsPropertyTypeGroup.contenttypeNodeId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.ContentTypeId, right => right.NodeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.PropertyTypeId, right => right.Id) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.DataTypeId, right => right.DataTypeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.ContentTypeNodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + if (acc != null) + yield return acc; + } + + protected override Sql GetBaseQuery(bool isCount) + { + if (isCount) + { + return Sql() + .SelectCount() + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); + } + + var sql = Sql() + .Select("umbracoNode.*", "cmsContentType.*", "cmsPropertyType.id AS PropertyTypeId", "cmsPropertyType.Alias", + "cmsPropertyType.Name", "cmsPropertyType.Description", "cmsPropertyType.mandatory", + "cmsPropertyType.validationRegExp", "cmsPropertyType.dataTypeId", "cmsPropertyType.sortOrder AS PropertyTypeSortOrder", + "cmsPropertyType.propertyTypeGroupId AS PropertyTypesGroupId", "cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile", + "cmsDataType.propertyEditorAlias", "cmsDataType.dbType", "cmsPropertyTypeGroup.id AS PropertyTypeGroupId", + "cmsPropertyTypeGroup.text AS PropertyGroupName", + "cmsPropertyTypeGroup.sortorder AS PropertyGroupSortOrder", "cmsPropertyTypeGroup.contenttypeNodeId") + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .LeftJoin().On(left => left.ContentTypeId, right => right.NodeId) + .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) + .LeftJoin().On(left => left.DataTypeId, right => right.DataTypeId) + .LeftJoin().On(left => left.ContentTypeNodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; } - protected Sql GetSubquery() + protected Sql GetSubquery() { - var sql = new Sql() + var sql = Sql() .Select("DISTINCT(umbracoNode.id)") - .From(SqlSyntax) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.ContentTypeId, right => right.NodeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.PropertyTypeId, right => right.Id) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.DataTypeId, right => right.DataTypeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.ContentTypeNodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .LeftJoin().On(left => left.ContentTypeId, right => right.NodeId) + .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) + .LeftJoin().On(left => left.DataTypeId, right => right.DataTypeId) + .LeftJoin().On(left => left.ContentTypeNodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; } @@ -154,13 +187,13 @@ namespace Umbraco.Core.Persistence.Repositories { get { return new Guid(Constants.ObjectTypes.MemberType); } } - + protected override void PersistNewItem(IMemberType entity) { ValidateAlias(entity); ((MemberType)entity).AddingEntity(); - + //set a default icon if one is not specified if (entity.Icon.IsNullOrWhiteSpace()) { @@ -225,7 +258,7 @@ namespace Umbraco.Core.Persistence.Repositories entity.ResetDirtyProperties(); } - + /// /// Override so we can specify explicit db type's on any property types that are built-in. /// @@ -311,7 +344,7 @@ namespace Umbraco.Core.Persistence.Repositories } /// - /// If this is one of our internal properties - we will manually assign the data type since they must + /// If this is one of our internal properties - we will manually assign the data type since they must /// always correspond to the correct db type no matter what the backing data type is assigned. /// /// @@ -338,7 +371,7 @@ namespace Umbraco.Core.Persistence.Repositories } /// - /// + /// /// /// /// diff --git a/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs index c10e9169a4..1749ae8be3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MigrationEntryRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Semver; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -13,7 +14,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - internal class MigrationEntryRepository : PetaPocoRepositoryBase, IMigrationEntryRepository + internal class MigrationEntryRepository : NPocoRepositoryBase, IMigrationEntryRepository { public MigrationEntryRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) : base(work, cache, logger, sqlSyntax, mappingResolver) @@ -63,11 +64,17 @@ namespace Umbraco.Core.Persistence.Repositories return Database.Fetch(sql).Select(x => factory.BuildEntity(x)); } - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From(SqlSyntax); + var sql = Sql(); + + sql = isCount + ? sql.SelectCount() + : sql.Select(); + + sql + .From(); + return sql; } @@ -119,9 +126,9 @@ namespace Umbraco.Core.Persistence.Repositories { var versionString = version.ToString(); - var sql = new Sql().Select("*") - .From(SqlSyntax) - .Where(SqlSyntax, x => x.Name.InvariantEquals(migrationName) && x.Version == versionString); + var sql = Sql().SelectAll() + .From() + .Where(x => x.Name.InvariantEquals(migrationName) && x.Version == versionString); var result = Database.FirstOrDefault(sql); diff --git a/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/NPocoRepositoryBase.cs similarity index 65% rename from src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs rename to src/Umbraco.Core/Persistence/Repositories/NPocoRepositoryBase.cs index 569865e688..24eeb93fd9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/NPocoRepositoryBase.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Data.SqlServerCe; +using NPoco; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.Mappers; @@ -11,57 +12,49 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { /// - /// Represent an abstract Repository for PetaPoco based repositories + /// Represent an abstract Repository for NPoco based repositories /// /// /// - internal abstract class PetaPocoRepositoryBase : RepositoryBase + internal abstract class NPocoRepositoryBase : RepositoryBase where TEntity : class, IAggregateRoot { - public ISqlSyntaxProvider SqlSyntax { get; private set; } + public ISqlSyntaxProvider SqlSyntax { get; } - private readonly QueryFactory _queryFactory; /// /// Returns the Query factory /// - public override QueryFactory QueryFactory - { - get { return _queryFactory; } - } + public override QueryFactory QueryFactory { get; } /// /// Used to create a new query instance /// /// - public override Query Query - { - get { return QueryFactory.Create(); } - } + public override Query Query => QueryFactory.Create(); - protected PetaPocoRepositoryBase(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) + protected NPocoRepositoryBase(IUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) : base(work, cache, logger) { - if (sqlSyntax == null) throw new ArgumentNullException("sqlSyntax"); + if (sqlSyntax == null) throw new ArgumentNullException(nameof(sqlSyntax)); SqlSyntax = sqlSyntax; - _queryFactory = new QueryFactory(SqlSyntax, mappingResolver); + QueryFactory = new QueryFactory(SqlSyntax, mappingResolver); } /// /// Returns the database Unit of Work added to the repository /// - protected internal new IDatabaseUnitOfWork UnitOfWork - { - get { return (IDatabaseUnitOfWork)base.UnitOfWork; } - } + protected internal new IDatabaseUnitOfWork UnitOfWork => (IDatabaseUnitOfWork) base.UnitOfWork; - protected UmbracoDatabase Database + protected UmbracoDatabase Database => UnitOfWork.Database; + + protected Sql Sql() { - get { return UnitOfWork.Database; } + return NPoco.Sql.BuilderFor(new SqlContext(SqlSyntax, Database)); } #region Abstract Methods - - protected abstract Sql GetBaseQuery(bool isCount); + + protected abstract Sql GetBaseQuery(bool isCount); protected abstract string GetBaseWhereClause(); protected abstract IEnumerable GetDeleteClauses(); protected abstract Guid NodeObjectTypeId { get; } @@ -96,7 +89,7 @@ namespace Umbraco.Core.Persistence.Repositories } } - protected virtual TId GetEntityId(TEntity entity) + protected virtual new TId GetEntityId(TEntity entity) { return (TId)(object)entity.Id; } diff --git a/src/Umbraco.Core/Persistence/Repositories/NotificationsRepository.cs b/src/Umbraco.Core/Persistence/Repositories/NotificationsRepository.cs index 668fdf4b5b..67b36caa86 100644 --- a/src/Umbraco.Core/Persistence/Repositories/NotificationsRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/NotificationsRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; @@ -23,13 +24,13 @@ namespace Umbraco.Core.Persistence.Repositories public IEnumerable GetUserNotifications(IUser user) { - var sql = new Sql() + var sql = NPoco.Sql.BuilderFor(new SqlContext(_sqlSyntax, _unitOfWork.Database)) .Select("DISTINCT umbracoNode.id, umbracoUser2NodeNotify.userId, umbracoNode.nodeObjectType, umbracoUser2NodeNotify.action") - .From(_sqlSyntax) - .InnerJoin(_sqlSyntax) - .On(_sqlSyntax, dto => dto.NodeId, dto => dto.NodeId) - .Where(_sqlSyntax, dto => dto.UserId == (int)user.Id) - .OrderBy(_sqlSyntax, dto => dto.NodeId); + .From() + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.NodeId) + .Where(dto => dto.UserId == (int)user.Id) + .OrderBy(dto => dto.NodeId); var dtos = _unitOfWork.Database.Fetch(sql); //need to map the results @@ -50,13 +51,13 @@ namespace Umbraco.Core.Persistence.Repositories public IEnumerable GetEntityNotifications(IEntity entity) { - var sql = new Sql() + var sql = NPoco.Sql.BuilderFor(new SqlContext(_sqlSyntax, _unitOfWork.Database)) .Select("DISTINCT umbracoNode.id, umbracoUser2NodeNotify.userId, umbracoNode.nodeObjectType, umbracoUser2NodeNotify.action") - .From(_sqlSyntax) - .InnerJoin(_sqlSyntax) - .On(_sqlSyntax, dto => dto.NodeId, dto => dto.NodeId) - .Where(_sqlSyntax, dto => dto.NodeId == entity.Id) - .OrderBy(_sqlSyntax, dto => dto.NodeId); + .From() + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.NodeId) + .Where(dto => dto.NodeId == entity.Id) + .OrderBy(dto => dto.NodeId); var dtos = _unitOfWork.Database.Fetch(sql); //need to map the results @@ -81,10 +82,10 @@ namespace Umbraco.Core.Persistence.Repositories public Notification CreateNotification(IUser user, IEntity entity, string action) { - var sql = new Sql() + var sql = NPoco.Sql.BuilderFor(new SqlContext(_sqlSyntax, _unitOfWork.Database)) .Select("DISTINCT nodeObjectType") - .From(_sqlSyntax) - .Where(_sqlSyntax, nodeDto => nodeDto.NodeId == entity.Id); + .From() + .Where(nodeDto => nodeDto.NodeId == entity.Id); var nodeType = _unitOfWork.Database.ExecuteScalar(sql); var dto = new User2NodeNotifyDto() diff --git a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs index 8be1dc1b6d..8b37a2b6f1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs @@ -4,8 +4,10 @@ using System.Collections.Generic; using System.Dynamic; using System.Globalization; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Web.Caching; +using NPoco; using Umbraco.Core.Events; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; @@ -78,9 +80,9 @@ namespace Umbraco.Core.Persistence.Repositories whereBuilder.Append(")"); } - var sql = new Sql(); - sql.Select("*") - .From(_sqlSyntax) + var sql = NPoco.Sql.BuilderFor(new SqlContext(_sqlSyntax, _unitOfWork.Database)) + .SelectAll() + .From() .Where(whereBuilder.ToString()); //ToArray() to ensure it's all fetched from the db once. @@ -103,11 +105,11 @@ namespace Umbraco.Core.Persistence.Repositories /// public IEnumerable GetPermissionsForEntity(int entityId) { - var sql = new Sql(); - sql.Select("*") - .From(_sqlSyntax) - .Where(_sqlSyntax, dto => dto.NodeId == entityId) - .OrderBy(_sqlSyntax, dto => dto.NodeId); + var sql = NPoco.Sql.BuilderFor(new SqlContext(_sqlSyntax, _unitOfWork.Database)) + .SelectAll() + .From() + .Where(dto => dto.NodeId == entityId) + .OrderBy(dto => dto.NodeId); //ToArray() to ensure it's all fetched from the db once. var result = _unitOfWork.Database.Fetch(sql).ToArray(); diff --git a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs index 3b3538b220..c389bce2f7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -8,13 +9,12 @@ using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - internal class PublicAccessRepository : PetaPocoRepositoryBase, IPublicAccessRepository + internal class PublicAccessRepository : NPocoRepositoryBase, IPublicAccessRepository { public PublicAccessRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) : base(work, cache, logger, sqlSyntax, mappingResolver) @@ -48,7 +48,7 @@ namespace Umbraco.Core.Persistence.Repositories } var factory = new PublicAccessEntryFactory(); - var dtos = Database.Fetch(new AccessRulesRelator().Map, sql); + var dtos = Database.FetchOneToMany(x => x.Rules, sql); return dtos.Select(factory.BuildEntity); } @@ -59,19 +59,17 @@ namespace Umbraco.Core.Persistence.Repositories var sql = translator.Translate(); var factory = new PublicAccessEntryFactory(); - var dtos = Database.Fetch(new AccessRulesRelator().Map, sql); + var dtos = Database.FetchOneToMany(x => x.Rules, sql); return dtos.Select(factory.BuildEntity); } - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .LeftJoin(SqlSyntax) - .On(SqlSyntax, left => left.Id, right => right.AccessId); - - return sql; + return Sql() + .SelectAll() + .From() + .LeftJoin() + .On(left => left.Id, right => right.AccessId); } protected override string GetBaseWhereClause() @@ -164,6 +162,6 @@ namespace Umbraco.Core.Persistence.Repositories return entity.Key; } - + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs index e7b2d664b9..5f6be8eca7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using NPoco; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; @@ -109,21 +110,19 @@ namespace Umbraco.Core.Persistence.Repositories /// internal List GetFilesInRecycleBinForUploadField() { - var db = this.Database; - //Issue query to get all trashed content or media that has the Upload field as a property //The value for each field is stored in a list: FilesToDelete() //Alias: Constants.Conventions.Media.File and PropertyEditorAlias: Constants.PropertyEditors.UploadField - var sql = new Sql(); - sql.Select("DISTINCT(dataNvarchar)") - .From(SqlSyntax) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.PropertyTypeId, right => right.Id) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.DataTypeId, right => right.DataTypeId) + var sql = Sql() + .Select("DISTINCT(dataNvarchar)") + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.PropertyTypeId, right => right.Id) + .InnerJoin().On(left => left.DataTypeId, right => right.DataTypeId) .Where("umbracoNode.trashed = '1' AND umbracoNode.nodeObjectType = @NodeObjectType AND dataNvarchar IS NOT NULL AND (cmsPropertyType.Alias = @FileAlias OR cmsDataType.propertyEditorAlias = @PropertyEditorAlias)", new { FileAlias = Constants.Conventions.Media.File, NodeObjectType = NodeObjectTypeId, PropertyEditorAlias = Constants.PropertyEditors.UploadFieldAlias }); - var files = db.Fetch(sql); + var files = Database.Fetch(sql); return files; } } diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs index b6c312e4da..a021aca520 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -17,7 +18,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Represents a repository for doing CRUD operations for /// - internal class RelationRepository : PetaPocoRepositoryBase, IRelationRepository + internal class RelationRepository : NPocoRepositoryBase, IRelationRepository { private readonly IRelationTypeRepository _relationTypeRepository; @@ -89,13 +90,19 @@ namespace Umbraco.Core.Persistence.Repositories #endregion - #region Overrides of PetaPocoRepositoryBase + #region Overrides of NPocoRepositoryBase - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From(SqlSyntax); + var sql = Sql(); + + sql = isCount + ? sql.SelectCount() + : sql.Select(); + + sql + .From(); + return sql; } diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs index b3ce9fa14d..db3bf84620 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -17,7 +18,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Represents a repository for doing CRUD operations for /// - internal class RelationTypeRepository : PetaPocoRepositoryBase, IRelationTypeRepository + internal class RelationTypeRepository : NPocoRepositoryBase, IRelationTypeRepository { public RelationTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) @@ -81,13 +82,19 @@ namespace Umbraco.Core.Persistence.Repositories #endregion - #region Overrides of PetaPocoRepositoryBase + #region Overrides of NPocoRepositoryBase - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From(SqlSyntax); + var sql = Sql(); + + sql = isCount + ? sql.SelectCount() + : sql.Select(); + + sql + .From(); + return sql; } diff --git a/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs index 2926525f48..dd576ce899 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ServerRegistrationRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -14,7 +15,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - internal class ServerRegistrationRepository : PetaPocoRepositoryBase, IServerRegistrationRepository + internal class ServerRegistrationRepository : NPocoRepositoryBase, IServerRegistrationRepository { private readonly ICacheProvider _staticCache; @@ -59,11 +60,17 @@ namespace Umbraco.Core.Persistence.Repositories throw new NotSupportedException("This repository does not support this method"); } - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From(SqlSyntax); + var sql = Sql(); + + sql = isCount + ? sql.SelectCount() + : sql.Select(); + + sql + .From(); + return sql; } diff --git a/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs index 2f0a2321d6..e5d941ba4b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.Mappers; @@ -13,7 +14,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Simple abstract ReadOnly repository used to simply have PerformGet and PeformGetAll with an underlying cache /// - internal abstract class SimpleGetRepository : PetaPocoRepositoryBase + internal abstract class SimpleGetRepository : NPocoRepositoryBase where TEntity : class, IAggregateRoot where TDto: class { @@ -56,11 +57,11 @@ namespace Umbraco.Core.Persistence.Repositories protected override IEnumerable PerformGetAll(params TId[] ids) { - var sql = new Sql().From(SqlSyntax); + var sql = Sql().From(); if (ids.Any()) { - sql.Where(GetWhereInClauseForGetAll(), new { ids = ids }); + sql.Where(GetWhereInClauseForGetAll(), new { /*ids =*/ ids }); } return Database.Fetch(sql).Select(ConvertToEntity); diff --git a/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs index add3f21729..df5cc4883f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TagRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using NPoco; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -15,7 +16,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - internal class TagRepository : PetaPocoRepositoryBase, ITagRepository + internal class TagRepository : NPocoRepositoryBase, ITagRepository { public TagRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) : base(work, cache, logger, sqlSyntax, mappingResolver) @@ -92,25 +93,14 @@ namespace Umbraco.Core.Persistence.Repositories } - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); - if (isCount) - { - sql.Select("COUNT(*)").From(SqlSyntax); - } - else - { - return GetBaseQuery(); - } - return sql; + return isCount ? Sql().SelectCount().From() : GetBaseQuery(); } - private Sql GetBaseQuery() + private Sql GetBaseQuery() { - var sql = new Sql(); - sql.Select("*").From(SqlSyntax); - return sql; + return Sql().SelectAll().From(); } protected override string GetBaseWhereClause() @@ -165,60 +155,60 @@ namespace Umbraco.Core.Persistence.Repositories public TaggedEntity GetTaggedEntityByKey(Guid key) { - var sql = new Sql() + var sql = Sql() .Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntax.GetQuotedColumnName("group")) - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.TagId, right => right.Id) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.Id, right => right.PropertyTypeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, dto => dto.UniqueId == key); + .From() + .InnerJoin() + .On(left => left.TagId, right => right.Id) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.Id, right => right.PropertyTypeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.UniqueId == key); return CreateTaggedEntityCollection(Database.Fetch(sql)).FirstOrDefault(); } public TaggedEntity GetTaggedEntityById(int id) { - var sql = new Sql() + var sql = Sql() .Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntax.GetQuotedColumnName("group")) - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.TagId, right => right.Id) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.Id, right => right.PropertyTypeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, dto => dto.NodeId == id); + .From() + .InnerJoin() + .On(left => left.TagId, right => right.Id) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.Id, right => right.PropertyTypeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeId == id); return CreateTaggedEntityCollection(Database.Fetch(sql)).FirstOrDefault(); } public IEnumerable GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string tagGroup) { - var sql = new Sql() + var sql = Sql() .Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntax.GetQuotedColumnName("group")) - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.TagId, right => right.Id) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.Id, right => right.PropertyTypeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, dto => dto.Group == tagGroup); + .From() + .InnerJoin() + .On(left => left.TagId, right => right.Id) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.Id, right => right.PropertyTypeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.Group == tagGroup); if (objectType != TaggableObjectTypes.All) { var nodeObjectType = GetNodeObjectType(objectType); sql = sql - .Where(SqlSyntax, dto => dto.NodeObjectType == nodeObjectType); + .Where(dto => dto.NodeObjectType == nodeObjectType); } return CreateTaggedEntityCollection( @@ -227,29 +217,29 @@ namespace Umbraco.Core.Persistence.Repositories public IEnumerable GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string tagGroup = null) { - var sql = new Sql() + var sql = Sql() .Select("cmsTagRelationship.nodeId, cmsPropertyType.Alias, cmsPropertyType.id as propertyTypeId, cmsTags.tag, cmsTags.id as tagId, cmsTags." + SqlSyntax.GetQuotedColumnName("group")) - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.TagId, right => right.Id) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.Id, right => right.PropertyTypeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, dto => dto.Tag == tag); + .From() + .InnerJoin() + .On(left => left.TagId, right => right.Id) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.Id, right => right.PropertyTypeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.Tag == tag); if (objectType != TaggableObjectTypes.All) { var nodeObjectType = GetNodeObjectType(objectType); sql = sql - .Where(SqlSyntax, dto => dto.NodeObjectType == nodeObjectType); + .Where(dto => dto.NodeObjectType == nodeObjectType); } if (tagGroup.IsNullOrWhiteSpace() == false) { - sql = sql.Where(SqlSyntax, dto => dto.Group == tagGroup); + sql = sql.Where(dto => dto.Group == tagGroup); } return CreateTaggedEntityCollection( @@ -275,12 +265,12 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetTagsQuerySelect(true); sql = ApplyRelationshipJoinToTagsQuery(sql); - + if (objectType != TaggableObjectTypes.All) { var nodeObjectType = GetNodeObjectType(objectType); sql = sql - .Where(SqlSyntax, dto => dto.NodeObjectType == nodeObjectType); + .Where(dto => dto.NodeObjectType == nodeObjectType); } sql = ApplyGroupFilterToTagsQuery(sql, group); @@ -297,7 +287,7 @@ namespace Umbraco.Core.Persistence.Repositories sql = ApplyRelationshipJoinToTagsQuery(sql); sql = sql - .Where(SqlSyntax, dto => dto.NodeId == contentId); + .Where(dto => dto.NodeId == contentId); sql = ApplyGroupFilterToTagsQuery(sql, group); @@ -311,7 +301,7 @@ namespace Umbraco.Core.Persistence.Repositories sql = ApplyRelationshipJoinToTagsQuery(sql); sql = sql - .Where(SqlSyntax, dto => dto.UniqueId == contentId); + .Where(dto => dto.UniqueId == contentId); sql = ApplyGroupFilterToTagsQuery(sql, group); @@ -325,10 +315,10 @@ namespace Umbraco.Core.Persistence.Repositories sql = ApplyRelationshipJoinToTagsQuery(sql); sql = sql - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.Id, right => right.PropertyTypeId) - .Where(SqlSyntax, dto => dto.NodeId == contentId) - .Where(SqlSyntax, dto => dto.Alias == propertyTypeAlias); + .InnerJoin() + .On(left => left.Id, right => right.PropertyTypeId) + .Where(dto => dto.NodeId == contentId) + .Where(dto => dto.Alias == propertyTypeAlias); sql = ApplyGroupFilterToTagsQuery(sql, group); @@ -342,19 +332,19 @@ namespace Umbraco.Core.Persistence.Repositories sql = ApplyRelationshipJoinToTagsQuery(sql); sql = sql - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.Id, right => right.PropertyTypeId) - .Where(SqlSyntax, dto => dto.UniqueId == contentId) - .Where(SqlSyntax, dto => dto.Alias == propertyTypeAlias); + .InnerJoin() + .On(left => left.Id, right => right.PropertyTypeId) + .Where(dto => dto.UniqueId == contentId) + .Where(dto => dto.Alias == propertyTypeAlias); sql = ApplyGroupFilterToTagsQuery(sql, group); return ExecuteTagsQuery(sql); } - private Sql GetTagsQuerySelect(bool withGrouping = false) + private Sql GetTagsQuerySelect(bool withGrouping = false) { - var sql = new Sql(); + var sql = Sql(); if (withGrouping) { @@ -368,31 +358,31 @@ namespace Umbraco.Core.Persistence.Repositories return sql; } - private Sql ApplyRelationshipJoinToTagsQuery(Sql sql) + private Sql ApplyRelationshipJoinToTagsQuery(Sql sql) { return sql - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.TagId, right => right.Id) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId); + .From() + .InnerJoin() + .On(left => left.TagId, right => right.Id) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId); } - private Sql ApplyGroupFilterToTagsQuery(Sql sql, string group) + private Sql ApplyGroupFilterToTagsQuery(Sql sql, string group) { - if (@group.IsNullOrWhiteSpace() == false) + if (group.IsNullOrWhiteSpace() == false) { - sql = sql.Where(SqlSyntax, dto => dto.Group == group); + sql = sql.Where(dto => dto.Group == group); } return sql; } - private Sql ApplyGroupByToTagsQuery(Sql sql) + private Sql ApplyGroupByToTagsQuery(Sql sql) { - return sql.GroupBy(new string[] { "cmsTags.id", "cmsTags.tag", "cmsTags." + SqlSyntax.GetQuotedColumnName("group") + @"" }); + return sql.GroupBy("cmsTags.id", "cmsTags.tag", "cmsTags." + SqlSyntax.GetQuotedColumnName("group") + @""); } private IEnumerable ExecuteTagsQuery(Sql sql) @@ -409,7 +399,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// The tags to assign /// - /// If set to true, this will replace all tags with the given tags, + /// If set to true, this will replace all tags with the given tags, /// if false this will append the tags that already exist for the content item /// /// @@ -436,8 +426,8 @@ namespace Umbraco.Core.Persistence.Repositories } //ok so now we're actually assigning tags... - //NOTE: There's some very clever logic in the umbraco.cms.businesslogic.Tags.Tag to insert tags where they don't exist, - // and assign where they don't exist which we've borrowed here. The queries are pretty zany but work, otherwise we'll end up + //NOTE: There's some very clever logic in the umbraco.cms.businesslogic.Tags.Tag to insert tags where they don't exist, + // and assign where they don't exist which we've borrowed here. The queries are pretty zany but work, otherwise we'll end up // with quite a few additional queries. //do all this in one transaction @@ -522,7 +512,7 @@ namespace Umbraco.Core.Persistence.Repositories public void ClearTagsFromProperty(int contentId, int propertyTypeId) { - Database.Execute("DELETE FROM cmsTagRelationship WHERE nodeId = @nodeId AND propertyTypeId = @propertyTypeId", + Database.Execute("DELETE FROM cmsTagRelationship WHERE nodeId = @nodeId AND propertyTypeId = @propertyTypeId", new { nodeId = contentId, propertyTypeId = propertyTypeId }); } @@ -549,18 +539,19 @@ namespace Umbraco.Core.Persistence.Repositories /// /// This is a clever way to produce an SQL statement like this: - /// - /// (select 'Spacesdd' as Tag, 'default' as [Group] - /// union + /// + /// (select 'Spacesdd' as Tag, 'default' as [Group] + /// union /// select 'Cool' as Tag, 'default' as [Group] /// ) as TagSet - /// - /// This allows us to use the tags to be inserted as a temporary in memory table. + /// + /// This allows us to use the tags to be inserted as a temporary in memory table. /// /// /// private string GetTagSet(IEnumerable tagsToInsert) { + // fixme.npoco //TODO: Fix this query, since this is going to be basically a unique query each time, this will cause some mem usage in peta poco, // and surely there's a nicer way! //TODO: When we fix, be sure to remove the @ symbol escape @@ -568,7 +559,7 @@ namespace Umbraco.Core.Persistence.Repositories var array = tagsToInsert .Select(tag => string.Format("select '{0}' as Tag, '{1}' as " + SqlSyntax.GetQuotedColumnName("group") + @"", - PetaPocoExtensions.EscapeAtSymbols(tag.Text.Replace("'", "''")), tag.Group)) + NPocoDatabaseExtensions.EscapeAtSymbols(tag.Text.Replace("'", "''")), tag.Group)) .ToArray(); return "(" + string.Join(" union ", array).Replace(" ", " ") + ") as TagSet"; } diff --git a/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs index c2793dcc1b..f87dacc911 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TaskRepository.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using AutoMapper; +using NPoco; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -14,7 +15,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - internal class TaskRepository : PetaPocoRepositoryBase, ITaskRepository + internal class TaskRepository : NPocoRepositoryBase, ITaskRepository { public TaskRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) : base(work, cache, logger, sqlSyntax, mappingResolver) @@ -26,7 +27,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var taskDto = Database.Fetch(sql).FirstOrDefault(); + var taskDto = Database.Fetch(sql).FirstOrDefault(); if (taskDto == null) return null; @@ -45,7 +46,7 @@ namespace Umbraco.Core.Persistence.Repositories } var factory = new TaskFactory(); - var dtos = Database.Fetch(sql); + var dtos = Database.Fetch(sql); return dtos.Select(factory.BuildEntity); } @@ -56,34 +57,24 @@ namespace Umbraco.Core.Persistence.Repositories var sql = translator.Translate(); var factory = new TaskFactory(); - var dtos = Database.Fetch(sql); + var dtos = Database.Fetch(sql); return dtos.Select(factory.BuildEntity); } - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); - if (isCount) - { - sql.Select("COUNT(*)").From(SqlSyntax); - } - else - { - return GetBaseQuery(); - } - return sql; + return isCount ? Sql().SelectCount().From() : GetBaseQuery(); } - private Sql GetBaseQuery() + private Sql GetBaseQuery() { - var sql = new Sql(); - sql.Select("cmsTask.closed,cmsTask.id,cmsTask.taskTypeId,cmsTask.nodeId,cmsTask.parentUserId,cmsTask.userId,cmsTask." + SqlSyntax.GetQuotedColumnName("DateTime") + ",cmsTask.Comment,cmsTaskType.id, cmsTaskType.alias") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.TaskTypeId, right => right.Id) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId); - return sql; + return Sql() + .Select("cmsTask.closed,cmsTask.id,cmsTask.taskTypeId,cmsTask.nodeId,cmsTask.parentUserId,cmsTask.userId,cmsTask." + SqlSyntax.GetQuotedColumnName("DateTime") + ",cmsTask.Comment,cmsTaskType.id, cmsTaskType.alias") + .From() + .InnerJoin() + .On(left => left.TaskTypeId, right => right.Id) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId); } protected override string GetBaseWhereClause() @@ -95,7 +86,7 @@ namespace Umbraco.Core.Persistence.Repositories { var list = new List { - "DELETE FROM cmsTask WHERE id = @Id" + "DELETE FROM cmsTask WHERE id = @Id" }; return list; } @@ -147,21 +138,21 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetGetTasksQuery(assignedUser, ownerUser, taskTypeAlias, includeClosed); if (itemId.HasValue) { - sql.Where(SqlSyntax, dto => dto.NodeId == itemId.Value); + sql.Where(dto => dto.NodeId == itemId.Value); } - var dtos = Database.Fetch(sql); + var dtos = Database.Fetch(sql); var factory = new TaskFactory(); return dtos.Select(factory.BuildEntity); } - private Sql GetGetTasksQuery(int? assignedUser = null, int? ownerUser = null, string taskTypeAlias = null, bool includeClosed = false) + private Sql GetGetTasksQuery(int? assignedUser = null, int? ownerUser = null, string taskTypeAlias = null, bool includeClosed = false) { var sql = GetBaseQuery(false); if (includeClosed == false) { - sql.Where(SqlSyntax, dto => dto.Closed == false); + sql.Where(dto => dto.Closed == false); } if (taskTypeAlias.IsNullOrWhiteSpace() == false) { @@ -169,11 +160,11 @@ namespace Umbraco.Core.Persistence.Repositories } if (ownerUser.HasValue) { - sql.Where(SqlSyntax, dto => dto.ParentUserId == ownerUser.Value); + sql.Where(dto => dto.ParentUserId == ownerUser.Value); } if (assignedUser.HasValue) { - sql.Where(SqlSyntax, dto => dto.UserId == assignedUser.Value); + sql.Where(dto => dto.UserId == assignedUser.Value); } return sql; } diff --git a/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs index aef8debd1f..cc26450962 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TaskTypeRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; @@ -12,7 +13,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - internal class TaskTypeRepository : PetaPocoRepositoryBase, ITaskTypeRepository + internal class TaskTypeRepository : NPocoRepositoryBase, ITaskTypeRepository { public TaskTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) : base(work, cache, logger, sqlSyntax, mappingResolver) @@ -58,11 +59,9 @@ namespace Umbraco.Core.Persistence.Repositories return dtos.Select(factory.BuildEntity); } - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); - sql.Select("*").From(SqlSyntax); - return sql; + return Sql().SelectAll().From(); } protected override string GetBaseWhereClause() diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index 32f5f63d98..e4379119c2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Text; +using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; @@ -27,7 +28,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Represents the Template Repository /// - internal class TemplateRepository : PetaPocoRepositoryBase, ITemplateRepository + internal class TemplateRepository : NPocoRepositoryBase, ITemplateRepository { private readonly IFileSystem _masterpagesFileSystem; private readonly IFileSystem _viewsFileSystem; @@ -75,14 +76,14 @@ namespace Umbraco.Core.Persistence.Repositories } else { - sql.Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + sql.Where(x => x.NodeObjectType == NodeObjectTypeId); } - var dtos = Database.Fetch(sql); + var dtos = Database.Fetch(sql); if (dtos.Count == 0) return Enumerable.Empty(); - //look up the simple template definitions that have a master template assigned, this is used + //look up the simple template definitions that have a master template assigned, this is used // later to populate the template item's properties var childIds = (ids.Any() ? GetAxisDefinitions(dtos.ToArray()) @@ -92,7 +93,7 @@ namespace Umbraco.Core.Persistence.Repositories ParentId = x.NodeDto.ParentId, Name = x.Alias })).ToArray(); - + return dtos.Select(d => MapFromDto(d, childIds)); } @@ -102,11 +103,11 @@ namespace Umbraco.Core.Persistence.Repositories var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); - var dtos = Database.Fetch(sql); + var dtos = Database.Fetch(sql); if (dtos.Count == 0) return Enumerable.Empty(); - //look up the simple template definitions that have a master template assigned, this is used + //look up the simple template definitions that have a master template assigned, this is used // later to populate the template item's properties var childIds = GetAxisDefinitions(dtos.ToArray()).ToArray(); @@ -115,16 +116,23 @@ namespace Umbraco.Core.Persistence.Repositories #endregion - #region Overrides of PetaPocoRepositoryBase + #region Overrides of NPocoRepositoryBase - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + var sql = Sql(); + + sql = isCount + ? sql.SelectCount() + : sql.Select(r => + r.Select()); + + sql + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); + return sql; } @@ -166,7 +174,7 @@ namespace Umbraco.Core.Persistence.Repositories //Create the (base) node data - umbracoNode var nodeDto = dto.NodeDto; nodeDto.Path = "-1," + dto.NodeDto.NodeId; - 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 var parent = Get(template.MasterTemplateId.Value); @@ -325,23 +333,23 @@ namespace Umbraco.Core.Persistence.Repositories { var masterpageName = string.Concat(entity.Alias, ".master"); _masterpagesFileSystem.DeleteFile(masterpageName); - } + } } #endregion private IEnumerable GetAxisDefinitions(params TemplateDto[] templates) { - //look up the simple template definitions that have a master template assigned, this is used + //look up the simple template definitions that have a master template assigned, this is used // later to populate the template item's properties - var childIdsSql = new Sql() + var childIdsSql = Sql() .Select("nodeId,alias,parentID") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.NodeId, dto => dto.NodeId) + .From() + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.NodeId) //lookup axis's .Where("umbracoNode." + SqlSyntax.GetQuotedColumnName("id") + " IN (@parentIds) OR umbracoNode.parentID IN (@childIds)", - new {parentIds = templates.Select(x => x.NodeDto.ParentId), childIds = templates.Select(x => x.NodeId)}); + new {parentIds = templates.Select(x => x.NodeDto.ParentId), childIds = templates.Select(x => x.NodeId)}); var childIds = Database.Fetch(childIdsSql) .Select(x => new UmbracoEntity @@ -571,7 +579,7 @@ namespace Umbraco.Core.Persistence.Repositories /// [Obsolete("Use GetDescendants instead")] public TemplateNode GetTemplateNode(string alias) - { + { //first get all template objects var allTemplates = base.GetAll().ToArray(); @@ -580,7 +588,7 @@ namespace Umbraco.Core.Persistence.Repositories { return null; } - + var top = selfTemplate; while (top.MasterTemplateAlias.IsNullOrWhiteSpace() == false) { @@ -631,16 +639,16 @@ namespace Umbraco.Core.Persistence.Repositories } /// - /// This checks what the default rendering engine is set in config but then also ensures that there isn't already - /// a template that exists in the opposite rendering engine's template folder, then returns the appropriate + /// This checks what the default rendering engine is set in config but then also ensures that there isn't already + /// a template that exists in the opposite rendering engine's template folder, then returns the appropriate /// rendering engine to use. - /// + /// /// /// /// The reason this is required is because for example, if you have a master page file already existing under ~/masterpages/Blah.aspx - /// and then you go to create a template in the tree called Blah and the default rendering engine is MVC, it will create a Blah.cshtml - /// empty template in ~/Views. This means every page that is using Blah will go to MVC and render an empty page. - /// This is mostly related to installing packages since packages install file templates to the file system and then create the + /// and then you go to create a template in the tree called Blah and the default rendering engine is MVC, it will create a Blah.cshtml + /// empty template in ~/Views. This means every page that is using Blah will go to MVC and render an empty page. + /// This is mostly related to installing packages since packages install file templates to the file system and then create the /// templates in business logic. Without this, it could cause the wrong rendering engine to be used for a package. /// public RenderingEngine DetermineTemplateRenderingEngine(ITemplate template) @@ -748,7 +756,7 @@ namespace Umbraco.Core.Persistence.Repositories /// private void EnsureValidAlias(ITemplate template) { - //ensure unique alias + //ensure unique alias template.Alias = template.Alias.ToCleanString(CleanStringType.UnderscoreAlias); if (template.Alias.Length > 100) @@ -762,7 +770,7 @@ namespace Umbraco.Core.Persistence.Repositories private bool AliasAlreadExists(ITemplate template) { - var sql = GetBaseQuery(true).Where(SqlSyntax, x => x.Alias.InvariantEquals(template.Alias) && x.NodeId != template.Id); + var sql = GetBaseQuery(true).Where(x => x.Alias.InvariantEquals(template.Alias) && x.NodeId != template.Id); var count = Database.ExecuteScalar(sql); return count > 0; } diff --git a/src/Umbraco.Core/Persistence/Repositories/TupleExtensions.cs b/src/Umbraco.Core/Persistence/Repositories/TupleExtensions.cs new file mode 100644 index 0000000000..a944e9e497 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/TupleExtensions.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Persistence.Repositories +{ + static class TupleExtensions + { + public static IEnumerable Map(this Tuple, List> t, Func relator) + { + return t.Item1.Zip(t.Item2, relator); + } + + public static IEnumerable Map(this Tuple, List, List> t, Func relator) + { + return t.Item1.Zip(t.Item2, t.Item3, relator); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index e7d310e550..72333289c7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using NPoco; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -12,7 +13,6 @@ using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; @@ -21,7 +21,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Represents the UserRepository for doing CRUD operations for /// - internal class UserRepository : PetaPocoRepositoryBase, IUserRepository + internal class UserRepository : NPocoRepositoryBase, IUserRepository { private readonly IUserTypeRepository _userTypeRepository; private readonly CacheHelper _cacheHelper; @@ -40,8 +40,10 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { Id = id }); - var dto = Database.Fetch(new UserSectionRelator().Map, sql).FirstOrDefault(); - + var dto = Database + .FetchOneToMany(x => x.User2AppDtos, sql) + .FirstOrDefault(); + if (dto == null) return null; @@ -60,50 +62,49 @@ namespace Umbraco.Core.Persistence.Repositories { sql.Where("umbracoUser.id in (@ids)", new {ids = ids}); } - - return ConvertFromDtos(Database.Fetch(new UserSectionRelator().Map, sql)) - .ToArray(); // important so we don't iterate twice, if we don't do this we can end up with null values in cache if we were caching. + + var dtos = Database + .FetchOneToMany(x => x.User2AppDtos, sql); + + return ConvertFromDtos(dtos).ToArray(); // do it now and do it once, else can end up with nulls in cache } - + protected override IEnumerable PerformGetByQuery(IQuery query) { var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); - var dtos = Database.Fetch(new UserSectionRelator().Map, sql) + var dtos = Database + .FetchOneToMany(x => x.User2AppDtos, sql) .DistinctBy(x => x.Id); - return ConvertFromDtos(dtos) - .ToArray(); // important so we don't iterate twice, if we don't do this we can end up with null values in cache if we were caching. + return ConvertFromDtos(dtos).ToArray(); // do it now and do it once, else can end up with nulls in cache } - + #endregion - #region Overrides of PetaPocoRepositoryBase - - protected override Sql GetBaseQuery(bool isCount) + #region Overrides of NPocoRepositoryBase + + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); if (isCount) - { - sql.Select("COUNT(*)").From(SqlSyntax); - } - else - { - return GetBaseQuery("*"); - } - return sql; + return Sql().SelectCount().From(); + + return Sql() + .Select(r => r.Select(referenceName: "User2AppDtos")) + .From() + .LeftJoin() + .On(left => left.Id, right => right.UserId); } - private Sql GetBaseQuery(string columns) + private Sql GetBaseQuery(string columns) { - var sql = new Sql(); - sql.Select(columns) - .From(SqlSyntax) - .LeftJoin(SqlSyntax) - .On(SqlSyntax, left => left.Id, right => right.UserId); - return sql; + return Sql() + .Select(columns) + .From() + .LeftJoin() + .On(left => left.Id, right => right.UserId); } @@ -113,7 +114,7 @@ namespace Umbraco.Core.Persistence.Repositories } protected override IEnumerable GetDeleteClauses() - { + { var list = new List { "DELETE FROM cmsTask WHERE userId = @Id", @@ -131,7 +132,7 @@ namespace Umbraco.Core.Persistence.Repositories { get { throw new NotImplementedException(); } } - + protected override void PersistNewItem(IUser entity) { var userFactory = new UserFactory(entity.UserType); @@ -141,7 +142,7 @@ namespace Umbraco.Core.Persistence.Repositories { entity.SecurityStamp = Guid.NewGuid().ToString(); } - + var userDto = userFactory.BuildDto(entity); var id = Convert.ToInt32(Database.Insert(userDto)); @@ -181,8 +182,8 @@ namespace Umbraco.Core.Persistence.Repositories {"startStructureID", "StartContentId"}, {"startMediaID", "StartMediaId"}, {"userName", "Name"}, - {"userLogin", "Username"}, - {"userEmail", "Email"}, + {"userLogin", "Username"}, + {"userEmail", "Email"}, {"userLanguage", "Language"}, {"securityStampToken", "SecurityStamp"}, {"lastLockoutDate", "LastLockoutDate"}, @@ -217,7 +218,7 @@ namespace Umbraco.Core.Persistence.Repositories { Database.Update(userDto, changedCols); } - + //update the sections if they've changed var user = (User)entity; if (user.IsPropertyDirty("AllowedSections")) @@ -237,7 +238,7 @@ namespace Umbraco.Core.Persistence.Repositories //if something has been added then insert it if (user.AddedSections.Contains(sectionDto.AppAlias)) { - //we need to insert since this was added + //we need to insert since this was added Database.Insert(sectionDto); } else @@ -248,7 +249,7 @@ namespace Umbraco.Core.Persistence.Repositories } } - + } entity.ResetDirtyProperties(); @@ -272,11 +273,10 @@ namespace Umbraco.Core.Persistence.Repositories public bool Exists(string username) { - var sql = new Sql(); - - sql.Select("COUNT(*)") - .From(SqlSyntax) - .Where(SqlSyntax, x => x.UserName == username); + var sql = Sql() + .SelectCount() + .From() + .Where(x => x.UserName == username); return Database.ExecuteScalar(sql) > 0; } @@ -301,7 +301,10 @@ namespace Umbraco.Core.Persistence.Repositories innerSql.Where("umbracoUser2app.app = " + SqlSyntax.GetQuotedValue(sectionAlias)); sql.Where(string.Format("umbracoUser.id IN ({0})", innerSql.SQL)); - return ConvertFromDtos(Database.Fetch(new UserSectionRelator().Map, sql)); + var dtos = Database + .FetchOneToMany(x => x.User2AppDtos, sql); + + return ConvertFromDtos(dtos); } /// @@ -316,14 +319,15 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// - /// The query supplied will ONLY work with data specifically on the umbracoUser table because we are using PetaPoco paging (SQL paging) + /// The query supplied will ONLY work with data specifically on the umbracoUser table because we are using NPoco paging (SQL paging) /// public IEnumerable GetPagedResultsByQuery(IQuery query, int pageIndex, int pageSize, out int totalRecords, Expression> orderBy) { - if (orderBy == null) throw new ArgumentNullException("orderBy"); + if (orderBy == null) throw new ArgumentNullException(nameof(orderBy)); - var sql = new Sql(); - sql.Select("*").From(SqlSyntax); + var sql = Sql() + .SelectAll() + .From(); Sql resultQuery; if (query != null) @@ -364,13 +368,13 @@ namespace Umbraco.Core.Persistence.Repositories //now we need to ensure this result is also ordered by the same order by clause return result.OrderBy(orderBy.Compile()); } - + /// /// Returns permissions for a given user for any number of nodes /// /// /// - /// + /// public IEnumerable GetUserPermissionsForEntities(int userId, params int[] entityIds) { var repo = new PermissionRepository(UnitOfWork, _cacheHelper, SqlSyntax); @@ -410,7 +414,7 @@ namespace Umbraco.Core.Persistence.Repositories var allUserTypes = userTypeIds.Length == 0 ? Enumerable.Empty() : _userTypeRepository.GetAll(userTypeIds); return dtos.Select(dto => - { + { var userType = allUserTypes.Single(x => x.Id == dto.Type); var userFactory = new UserFactory(userType); diff --git a/src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs index df3ab9f669..497eef779b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Logging; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; @@ -16,7 +17,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Represents the UserTypeRepository for doing CRUD operations for /// - internal class UserTypeRepository : PetaPocoRepositoryBase, IUserTypeRepository + internal class UserTypeRepository : NPocoRepositoryBase, IUserTypeRepository { public UserTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMappingResolver mappingResolver) : base(work, cache, logger, sqlSyntax, mappingResolver) @@ -35,14 +36,14 @@ namespace Umbraco.Core.Persistence.Repositories var userTypeFactory = new UserTypeFactory(); var sql = GetBaseQuery(false); - + if (ids.Any()) { sql.Where("umbracoUserType.id in (@ids)", new { ids = ids }); } else { - sql.Where(SqlSyntax, x => x.Id >= 0); + sql.Where(x => x.Id >= 0); } var dtos = Database.Fetch(sql); @@ -63,13 +64,19 @@ namespace Umbraco.Core.Persistence.Repositories #endregion - #region Overrides of PetaPocoRepositoryBase + #region Overrides of NPocoRepositoryBase - protected override Sql GetBaseQuery(bool isCount) + protected override Sql GetBaseQuery(bool isCount) { - var sql = new Sql(); - sql.Select(isCount ? "COUNT(*)" : "*") - .From(SqlSyntax); + var sql = Sql(); + + sql = isCount + ? sql.SelectCount() + : sql.Select(); + + sql + .From(); + return sql; } diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 86de4d7052..53a3884431 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using NPoco; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; @@ -26,7 +27,27 @@ using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Persistence.Repositories { - internal abstract class VersionableRepositoryBase : PetaPocoRepositoryBase + // this cannot be inside VersionableRepositoryBase because that class is static + internal static class VersionableRepositoryBaseAliasRegex + { + private readonly static Dictionary Regexes = new Dictionary(); + + public static Regex For(ISqlSyntaxProvider sqlSyntax) + { + var type = sqlSyntax.GetType(); + Regex aliasRegex; + if (Regexes.TryGetValue(type, out aliasRegex)) + return aliasRegex; + + var col = Regex.Escape(sqlSyntax.GetQuotedColumnName("column")).Replace("column", @"\w+"); + var fld = Regex.Escape(sqlSyntax.GetQuotedTableName("table") + ".").Replace("table", @"\w+") + col; + aliasRegex = new Regex("(" + fld + @")\s+AS\s+(" + col + ")", RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.IgnoreCase); + Regexes[type] = aliasRegex; + return aliasRegex; + } + } + + internal abstract class VersionableRepositoryBase : NPocoRepositoryBase where TEntity : class, IAggregateRoot { private readonly IContentSection _contentSection; @@ -41,22 +62,19 @@ namespace Umbraco.Core.Persistence.Repositories public virtual IEnumerable GetAllVersions(int id) { - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId) - .Where(SqlSyntax, x => x.NodeId == id) - .OrderByDescending(SqlSyntax, x => x.VersionDate); + var sql = Sql() + .SelectAll() + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.NodeId == id) + .OrderByDescending(x => x.VersionDate); - var dtos = Database.Fetch(sql); - foreach (var dto in dtos) - { - yield return GetByVersion(dto.VersionId); - } + var dtos = Database.Fetch(sql); + return dtos.Select(x => GetByVersion(x.VersionId)); } public virtual void DeleteVersion(Guid versionId) @@ -114,25 +132,27 @@ namespace Umbraco.Core.Persistence.Repositories var pathMatch = parentId == -1 ? "-1," : "," + parentId + ","; - var sql = new Sql(); + + var sql = Sql() + .SelectCount() + .From(); + if (contentTypeAlias.IsNullOrWhiteSpace()) { - sql.Select("COUNT(*)") - .From(SqlSyntax) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId) - .Where(SqlSyntax, x => x.Path.Contains(pathMatch)); + sql + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.Path.Contains(pathMatch)); } else { - sql.Select("COUNT(*)") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.ContentTypeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId) - .Where(SqlSyntax, x => x.Path.Contains(pathMatch)) - .Where(SqlSyntax, x => x.Alias == contentTypeAlias); + sql + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.ContentTypeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.Path.Contains(pathMatch)) + .Where(x => x.Alias == contentTypeAlias); } return Database.ExecuteScalar(sql); @@ -140,25 +160,26 @@ namespace Umbraco.Core.Persistence.Repositories public int CountChildren(int parentId, string contentTypeAlias = null) { - var sql = new Sql(); + var sql = Sql() + .SelectCount() + .From(); + if (contentTypeAlias.IsNullOrWhiteSpace()) { - sql.Select("COUNT(*)") - .From(SqlSyntax) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId) - .Where(SqlSyntax, x => x.ParentId == parentId); + sql + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.ParentId == parentId); } else { - sql.Select("COUNT(*)") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.ContentTypeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId) - .Where(SqlSyntax, x => x.ParentId == parentId) - .Where(SqlSyntax, x => x.Alias == contentTypeAlias); + sql + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.ContentTypeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.ParentId == parentId) + .Where(x => x.Alias == contentTypeAlias); } return Database.ExecuteScalar(sql); @@ -171,23 +192,24 @@ namespace Umbraco.Core.Persistence.Repositories /// public int Count(string contentTypeAlias = null) { - var sql = new Sql(); + var sql = Sql() + .SelectCount() + .From(); + if (contentTypeAlias.IsNullOrWhiteSpace()) { - sql.Select("COUNT(*)") - .From(SqlSyntax) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + sql + .Where(x => x.NodeObjectType == NodeObjectTypeId); } else { - sql.Select("COUNT(*)") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.ContentTypeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId) - .Where(SqlSyntax, x => x.Alias == contentTypeAlias); + sql + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.ContentTypeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId) + .Where(x => x.Alias == contentTypeAlias); } return Database.ExecuteScalar(sql); @@ -232,197 +254,120 @@ namespace Umbraco.Core.Persistence.Repositories } } - private Sql GetFilteredSqlForPagedResults(Sql sql, Func> defaultFilter = null) - { - //copy to var so that the original isn't changed - var filteredSql = new Sql(sql.SQL, sql.Arguments); - // Apply filter - if (defaultFilter != null) - { - var filterResult = defaultFilter(); - filteredSql.Append(filterResult.Item1, filterResult.Item2); - } - return filteredSql; - } - private Sql GetSortedSqlForPagedResults(Sql sql, Direction orderDirection, string orderBy) + private Sql PrepareSqlForPagedResults(Sql sql, Sql filterSql, string orderBy, Direction orderDirection) { - //copy to var so that the original isn't changed - var sortedSql = new Sql(sql.SQL, sql.Arguments); - // Apply order according to parameters + if (filterSql == null && string.IsNullOrEmpty(orderBy)) return sql; + + // preserve original + var psql = new Sql(sql.SqlContext, sql.SQL, sql.Arguments); + + // apply filter + if (filterSql != null) + { + psql.Append(filterSql); + } + + // apply sort if (string.IsNullOrEmpty(orderBy) == false) { - var orderByParams = new[] { GetDatabaseFieldNameForOrderBy(orderBy) }; + // get the database field eg "[table].[column]" + var dbfield = GetDatabaseFieldNameForOrderBy(orderBy); + + // for SqlServer pagination to work, the "order by" field needs to be the alias eg if + // the select statement has "umbracoNode.text AS NodeDto__Text" then the order field needs + // to be "NodeDto__Text" and NOT "umbracoNode.text". + // not sure about SqlCE nor MySql, so better do it too. initially thought about patching + // NPoco but that would be expensive and not 100% possible, so better give NPoco proper + // queries to begin with. + // thought about maintaining a map of columns-to-aliases in the sql context but that would + // be expensive and most of the time, useless. so instead we parse the SQL looking for the + // alias. somewhat expensive too but nothing's free. + + var matches = VersionableRepositoryBaseAliasRegex.For(SqlSyntax).Matches(sql.SQL); + var match = matches.Cast().FirstOrDefault(m => m.Groups[1].Value == dbfield); + if (match != null) + dbfield = match.Groups[2].Value; + + var orderByParams = new object[] { dbfield }; if (orderDirection == Direction.Ascending) - { - sortedSql.OrderBy(orderByParams); - } + psql.OrderBy(orderByParams); else - { - sortedSql.OrderByDescending(orderByParams); - } - return sortedSql; + psql.OrderByDescending(orderByParams); } - return sortedSql; + + return psql; } - /// - /// A helper method for inheritors to get the paged results by query in a way that minimizes queries - /// - /// The type of the d. - /// The 'true' entity type (i.e. Content, Member, etc...) - /// The query. - /// Index of the page. - /// Size of the page. - /// The total records. - /// The tablename + column name for the SELECT statement fragment to return the node id from the query - /// A callback to create the default filter to be applied if there is one - /// A callback to process the query result - /// The order by column - /// The order direction. - /// - /// orderBy - protected IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, - Tuple nodeIdSelect, - Func> processQuery, - string orderBy, - Direction orderDirection, - Func> defaultFilter = null) - where TContentBase : class, IAggregateRoot, TEntity + protected IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, + Func, IEnumerable> mapper, + string orderBy, Direction orderDirection, + Sql filterSql = null) { - if (orderBy == null) throw new ArgumentNullException("orderBy"); + if (orderBy == null) throw new ArgumentNullException(nameof(orderBy)); - // Get base query + // start with base query, and apply the supplied IQuery var sqlBase = GetBaseQuery(false); - if (query == null) query = Query; var translator = new SqlTranslator(sqlBase, query); - var sqlQuery = translator.Translate(); - - // Note we can't do multi-page for several DTOs like we can multi-fetch and are doing in PerformGetByQuery, - // but actually given we are doing a Get on each one (again as in PerformGetByQuery), we only need the node Id. - // So we'll modify the SQL. - var sqlNodeIds = new Sql( - sqlQuery.SQL.Replace("SELECT *", string.Format("SELECT {0}.{1}",nodeIdSelect.Item1, nodeIdSelect.Item2)), - sqlQuery.Arguments); - - //get sorted and filtered sql - var sqlNodeIdsWithSort = GetSortedSqlForPagedResults( - GetFilteredSqlForPagedResults(sqlNodeIds, defaultFilter), - orderDirection, orderBy); + var sqlNodeIds = translator.Translate(); - // Get page of results and total count - IEnumerable result; - var pagedResult = Database.Page(pageIndex + 1, pageSize, sqlNodeIdsWithSort); + // sort and filter + sqlNodeIds = PrepareSqlForPagedResults(sqlNodeIds, filterSql, orderBy, orderDirection); + + // get a page of DTOs and the total count + var pagedResult = Database.Page(pageIndex + 1, pageSize, sqlNodeIds); totalRecords = Convert.ToInt32(pagedResult.TotalItems); - //NOTE: We need to check the actual items returned, not the 'totalRecords', that is because if you request a page number - // that doesn't actually have any data on it, the totalRecords will still indicate there are records but there are none in - // the pageResult, then the GetAll will actually return ALL records in the db. - if (pagedResult.Items.Any()) - { - //Crete the inner paged query that was used above to get the paged result, we'll use that as the inner sub query - var args = sqlNodeIdsWithSort.Arguments; - string sqlStringCount, sqlStringPage; - Database.BuildPageQueries(pageIndex * pageSize, pageSize, sqlNodeIdsWithSort.SQL, ref args, out sqlStringCount, out sqlStringPage); - - //if this is for sql server, the sqlPage will start with a SELECT * but we don't want that, we only want to return the nodeId - sqlStringPage = sqlStringPage - .Replace("SELECT *", - //This ensures we only take the field name of the node id select and not the table name - since the resulting select - // will ony work with the field name. - "SELECT " + nodeIdSelect.Item2); - - //We need to make this an inner join on the paged query - var splitQuery = sqlQuery.SQL.Split(new[] {"WHERE "}, StringSplitOptions.None); - var withInnerJoinSql = new Sql(splitQuery[0]) - .Append("INNER JOIN (") - //join the paged query with the paged query arguments - .Append(sqlStringPage, args) - .Append(") temp ") - .Append(string.Format("ON {0}.{1} = temp.{1}", nodeIdSelect.Item1, nodeIdSelect.Item2)) - //add the original where clause back with the original arguments - .Where(splitQuery[1], sqlQuery.Arguments); - - //get sorted and filtered sql - var fullQuery = GetSortedSqlForPagedResults( - GetFilteredSqlForPagedResults(withInnerJoinSql, defaultFilter), - orderDirection, orderBy); - - var content = processQuery(fullQuery) - .Cast() - .AsQueryable(); - - // Now we need to ensure this result is also ordered by the same order by clause - var orderByProperty = GetEntityPropertyNameForOrderBy(orderBy); - if (orderDirection == Direction.Ascending) - { - result = content.OrderBy(orderByProperty); - } - else - { - result = content.OrderByDescending(orderByProperty); - } - } - else - { - result = Enumerable.Empty(); - } - - return result; + // map the DTOs and return + return mapper(pagedResult.Items); } - protected IDictionary GetPropertyCollection( - Sql docSql, - IEnumerable documentDefs) + protected IDictionary GetPropertyCollection(DocumentDefinition[] ddefs) { - if (documentDefs.Any() == false) return new Dictionary(); + var versions = ddefs.Select(x => x.Version).ToArray(); + if (versions.Length == 0) return new Dictionary(); - //we need to parse the original SQL statement and reduce the columns to just cmsContent.nodeId, cmsContentVersion.VersionId so that we can use - // the statement to go get the property data for all of the items by using an inner join - var parsedOriginalSql = "SELECT {0} " + docSql.SQL.Substring(docSql.SQL.IndexOf("FROM", StringComparison.Ordinal)); - //now remove everything from an Orderby clause and beyond - if (parsedOriginalSql.InvariantContains("ORDER BY ")) + // fetch by version only, that should be enough, versions are guids and the same guid + // should not be reused for two different nodes -- then validate with a Where() just + // to be sure -- but we probably can get rid of the validation + var allPropertyData = Database.FetchByGroups(versions, 2000, batch => + Sql() + .Select(r => r.Select()) + .From() + .LeftJoin() + .On(left => left.PropertyTypeId, right => right.Id) + .WhereIn(x => x.VersionId, batch)) + .Where(x => ddefs.Any(y => y.Version == x.VersionId && y.Id == x.NodeId)) // so... probably redundant, but safe + .ToList(); + + // lazy access to prevalue for data types if any property requires tag support + var pre = new Lazy>(() => { - parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal)); - } + var allPropertyTypes = allPropertyData + .Select(x => x.PropertyTypeDto.Id) + .Distinct(); - var propSql = new Sql(@"SELECT cmsPropertyData.* -FROM cmsPropertyData -INNER JOIN cmsPropertyType -ON cmsPropertyData.propertytypeid = cmsPropertyType.id -INNER JOIN - (" + string.Format(parsedOriginalSql, "cmsContent.nodeId, cmsContentVersion.VersionId") + @") as docData -ON cmsPropertyData.versionId = docData.VersionId AND cmsPropertyData.contentNodeId = docData.nodeId -LEFT OUTER JOIN cmsDataTypePreValues -ON cmsPropertyType.dataTypeId = cmsDataTypePreValues.datatypeNodeId", docSql.Arguments); + var allDataTypePreValue = Database.FetchByGroups(allPropertyTypes, 2000, batch => + Sql() + .Select() + .From() + .LeftJoin().On(left => left.DataTypeNodeId, right => right.DataTypeId) + .WhereIn(x => x.Id, batch)); - var allPropertyData = Database.Fetch(propSql); - - //This is a lazy access call to get all prevalue data for the data types that make up all of these properties which we use - // below if any property requires tag support - var allPreValues = new Lazy>(() => - { - var preValsSql = new Sql(@"SELECT a.id, a.value, a.sortorder, a.alias, a.datatypeNodeId -FROM cmsDataTypePreValues a -WHERE EXISTS( - SELECT DISTINCT b.id as preValIdInner - FROM cmsDataTypePreValues b - INNER JOIN cmsPropertyType - ON b.datatypeNodeId = cmsPropertyType.dataTypeId - INNER JOIN - (" + string.Format(parsedOriginalSql, "DISTINCT cmsContent.contentType") + @") as docData - ON cmsPropertyType.contentTypeId = docData.contentType - WHERE a.id = b.id)", docSql.Arguments); - - return Database.Fetch(preValsSql); + return allDataTypePreValue; }); + return GetPropertyCollection(ddefs, allPropertyData, pre); + } + + protected IDictionary GetPropertyCollection(DocumentDefinition[] documentDefs, List allPropertyData, Lazy> allPreValues) + { var result = new Dictionary(); var propertiesWithTagSupport = new Dictionary(); - //iterate each definition grouped by it's content type - this will mean less property type iterations while building + //iterate each definition grouped by it's content type - this will mean less property type iterations while building // up the property collections foreach (var compositionGroup in documentDefs.GroupBy(x => x.Composition)) { @@ -433,8 +378,8 @@ WHERE EXISTS( var propertyDataDtos = allPropertyData.Where(x => x.NodeId == def.Id).Distinct(); var propertyFactory = new PropertyFactory(compositionProperties, def.Version, def.Id, def.CreateDate, def.VersionDate); - var properties = propertyFactory.BuildEntity(propertyDataDtos.ToArray()).ToArray(); - + var properties = propertyFactory.BuildEntity(propertyDataDtos.ToArray()).ToArray(); + var newProperties = properties.Where(x => x.HasIdentity == false && x.PropertyType.HasIdentity); foreach (var property in newProperties) @@ -482,7 +427,7 @@ WHERE EXISTS( Logger.Warn>("The query returned multiple property sets for document definition " + def.Id + ", " + def.Composition.Name); } result[def.Id] = new PropertyCollection(properties); - } + } } return result; @@ -511,37 +456,20 @@ WHERE EXISTS( protected virtual string GetDatabaseFieldNameForOrderBy(string orderBy) { - // Translate the passed order by field (which were originally defined for in-memory object sorting - // of ContentItemBasic instances) to the database field names. + // translate the supplied "order by" field, which were originally defined for in-memory + // object sorting of ContentItemBasic instance, to the actual database field names. + switch (orderBy.ToUpperInvariant()) { case "UPDATEDATE": - return "cmsContentVersion.VersionDate"; + return SqlSyntax.GetQuotedTableName("cmsContentVersion") + "." + SqlSyntax.GetQuotedColumnName("VersionDate"); case "NAME": - return "umbracoNode.text"; + return SqlSyntax.GetQuotedTableName("umbracoNode") + "." + SqlSyntax.GetQuotedColumnName("text"); case "OWNER": //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter - return "umbracoNode.nodeUser"; - default: - //ensure invalid SQL cannot be submitted - return Regex.Replace(orderBy, @"[^\w\.,`\[\]@-]", ""); - } - } - - protected virtual string GetEntityPropertyNameForOrderBy(string orderBy) - { - // Translate the passed order by field (which were originally defined for in-memory object sorting - // of ContentItemBasic instances) to the IMedia property names. - switch (orderBy.ToUpperInvariant()) - { - case "OWNER": - //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter - return "CreatorId"; - case "UPDATER": - //TODO: This isn't going to work very nicely because it's going to order by ID, not by letter - return "WriterId"; - case "VERSIONDATE": - return "UpdateDate"; + return SqlSyntax.GetQuotedTableName("umbracoNode") + "." + SqlSyntax.GetQuotedColumnName("nodeUser"); + case "PATH": + return SqlSyntax.GetQuotedTableName("umbracoNode") + "." + SqlSyntax.GetQuotedColumnName("path"); default: //ensure invalid SQL cannot be submitted return Regex.Replace(orderBy, @"[^\w\.,`\[\]@-]", ""); diff --git a/src/Umbraco.Core/Persistence/SqlContext.cs b/src/Umbraco.Core/Persistence/SqlContext.cs new file mode 100644 index 0000000000..927d6b469e --- /dev/null +++ b/src/Umbraco.Core/Persistence/SqlContext.cs @@ -0,0 +1,36 @@ +using System; +using NPoco; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence +{ + public class SqlContext + { + public SqlContext(ISqlSyntaxProvider sqlSyntax, IPocoDataFactory pocoDataFactory, DatabaseType databaseType) + { + if (sqlSyntax == null) throw new ArgumentNullException(nameof(sqlSyntax)); + if (pocoDataFactory == null) throw new ArgumentNullException(nameof(pocoDataFactory)); + if (databaseType == null) throw new ArgumentNullException(nameof(databaseType)); + + SqlSyntax = sqlSyntax; + PocoDataFactory = pocoDataFactory; + DatabaseType = databaseType; + } + + public SqlContext(ISqlSyntaxProvider sqlSyntax, IDatabaseConfig database) + { + if (sqlSyntax == null) throw new ArgumentNullException(nameof(sqlSyntax)); + if (database == null) throw new ArgumentNullException(nameof(database)); + + SqlSyntax = sqlSyntax; + PocoDataFactory = database.PocoDataFactory; + DatabaseType = database.DatabaseType; + } + + public ISqlSyntaxProvider SqlSyntax { get; } + + public IPocoDataFactory PocoDataFactory { get; } + + public DatabaseType DatabaseType { get; } + } +} diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index 056d9c1d9a..5778b4e618 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index c18a5f8477..6e21881f0c 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Logging; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -390,7 +391,7 @@ ORDER BY TABLE_NAME, INDEX_NAME", public override string EscapeString(string val) { - return PetaPocoExtensions.EscapeAtSymbols(MySql.Data.MySqlClient.MySqlHelper.EscapeString(val)); + return NPocoDatabaseExtensions.EscapeAtSymbols(MySql.Data.MySqlClient.MySqlHelper.EscapeString(val)); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index 1c6e1bba52..41b88abec9 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 9cb0ce327d..8ea169e997 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Persistence.SqlSyntax diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index eb8a56faca..bc1b12a22d 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -4,6 +4,7 @@ using System.Data; using System.Globalization; using System.Linq; using System.Text; +using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; @@ -121,7 +122,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax public virtual string EscapeString(string val) { - return PetaPocoExtensions.EscapeAtSymbols(val.Replace("'", "''")); + return NPocoDatabaseExtensions.EscapeAtSymbols(val.Replace("'", "''")); } public virtual string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType) diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs index af18d9c3d9..90f0a56696 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Core.Persistence.SqlSyntax +using NPoco; + +namespace Umbraco.Core.Persistence.SqlSyntax { internal static class SqlSyntaxProviderExtensions { diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs index 8125c183ff..eaf637df4b 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs @@ -3,23 +3,31 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Linq; +using NPoco; using StackExchange.Profiling; using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.FaultHandling; +using Umbraco.Core.Persistence.Mappers; namespace Umbraco.Core.Persistence { /// - /// Represents the Umbraco implementation of the PetaPoco Database object + /// Extends NPoco Database for Umbraco. /// /// - /// Currently this object exists for 'future proofing' our implementation. By having our own inheritied implementation we - /// can then override any additional execution (such as additional loggging, functionality, etc...) that we need to without breaking compatibility since we'll always be exposing - /// this object instead of the base PetaPoco database object. + /// Is used everywhere in place of the original NPoco Database object, and provides additional features + /// such as profiling, retry policies, logging, etc. + /// Is never created directly but obtained from the . /// public class UmbracoDatabase : Database, IDisposeOnRequestEnd { + // Umbraco's default isolation level is RepeatableRead + private const IsolationLevel DefaultIsolationLevel = IsolationLevel.RepeatableRead; + private readonly ILogger _logger; private readonly Guid _instanceId = Guid.NewGuid(); + private readonly RetryPolicy _connectionRetryPolicy; + private readonly RetryPolicy _commandRetryPolicy; private bool _enableCount; /// @@ -57,49 +65,52 @@ namespace Umbraco.Core.Persistence /// internal int SqlCount { get; private set; } - public UmbracoDatabase(IDbConnection connection, ILogger logger) - : base(connection) + // used by DefaultDatabaseFactory + // creates one instance per request + // also used by DatabaseContext for creating DBs and upgrading + public UmbracoDatabase(string connectionString, string providerName, ILogger logger, RetryPolicy connectionRetryPolicy = null, RetryPolicy commandRetryPolicy = null) + : base(connectionString, providerName, DefaultIsolationLevel) { _logger = logger; + _connectionRetryPolicy = connectionRetryPolicy; + _commandRetryPolicy = commandRetryPolicy; EnableSqlTrace = false; } - public UmbracoDatabase(string connectionString, string providerName, ILogger logger) - : base(connectionString, providerName) + // used by DefaultDatabaseFactory + // creates one instance per request + public UmbracoDatabase(string connectionStringName, ILogger logger, RetryPolicy connectionRetryPolicy = null, RetryPolicy commandRetryPolicy = null) + : base(connectionStringName, DefaultIsolationLevel) { _logger = logger; + _connectionRetryPolicy = connectionRetryPolicy; + _commandRetryPolicy = commandRetryPolicy; EnableSqlTrace = false; } - public UmbracoDatabase(string connectionString, DbProviderFactory provider, ILogger logger) - : base(connectionString, provider) + protected override DbConnection OnConnectionOpened(DbConnection connection) { - _logger = logger; - EnableSqlTrace = false; - } - - public UmbracoDatabase(string connectionStringName, ILogger logger) - : base(connectionStringName) - { - _logger = logger; - EnableSqlTrace = false; - } - - public override IDbConnection OnConnectionOpened(IDbConnection connection) - { - // propagate timeout if none yet + if (connection == null) throw new ArgumentNullException("connection"); // wrap the connection with a profiling connection that tracks timings - return new StackExchange.Profiling.Data.ProfiledDbConnection(connection as DbConnection, MiniProfiler.Current); + connection = new StackExchange.Profiling.Data.ProfiledDbConnection(connection, MiniProfiler.Current); + + // wrap the connection with a retrying connection + if (_connectionRetryPolicy != null || _commandRetryPolicy != null) + connection = new RetryDbConnection(connection, _connectionRetryPolicy, _commandRetryPolicy); + + return connection; } - public override void OnException(Exception x) + protected override void OnException(Exception x) { _logger.Error("Database exception occurred", x); base.OnException(x); } - public override void OnExecutingCommand(IDbCommand cmd) + // fixme.poco - has new interceptors? + + protected override void OnExecutingCommand(DbCommand cmd) { // if no timeout is specified, and the connection has a longer timeout, use it if (OneTimeCommandTimeout == 0 && CommandTimeout == 0 && cmd.Connection.ConnectionTimeout > 30) @@ -107,7 +118,7 @@ namespace Umbraco.Core.Persistence base.OnExecutingCommand(cmd); } - public override void OnExecutedCommand(IDbCommand cmd) + protected override void OnExecutedCommand(DbCommand cmd) { if (EnableSqlTrace) { @@ -119,5 +130,10 @@ namespace Umbraco.Core.Persistence } base.OnExecutedCommand(cmd); } + + public IEnumerable FetchByGroups(IEnumerable source, int groupSize, Func, Sql> sqlFactory) + { + return source.SelectByGroups(x => Fetch(sqlFactory(x)), groupSize); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UmbracoSqlExtensions.cs b/src/Umbraco.Core/Persistence/UmbracoSqlExtensions.cs new file mode 100644 index 0000000000..6bb571fe6e --- /dev/null +++ b/src/Umbraco.Core/Persistence/UmbracoSqlExtensions.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using NPoco; +using Umbraco.Core.Persistence.Querying; + +namespace Umbraco.Core.Persistence +{ + public static class UmbracoSqlExtensions + { + // note: here we take benefit from the fact that NPoco methods that return a Sql, such as + // when doing "sql = sql.Where(...)" actually append to, and return, the original Sql, not + // a new one. + + #region Where + + public static Sql Where(this Sql sql, Expression> predicate) + { + var expresionist = new PocoToSqlExpressionHelper(sql.SqlContext); + var whereExpression = expresionist.Visit(predicate); + sql.Where(whereExpression, expresionist.GetSqlParameters()); + return sql; + } + + public static Sql WhereIn(this Sql sql, Expression> fieldSelector, IEnumerable values) + { + var expresionist = new PocoToSqlExpressionHelper(sql.SqlContext); + var fieldExpression = expresionist.Visit(fieldSelector); + sql.Where(fieldExpression + " IN (@values)", new { /*@values =*/ values }); + return sql; + } + + #endregion + + #region From + + public static Sql From(this Sql sql) + { + var type = typeof (T); + var tableNameAttribute = type.FirstAttribute(); + var tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + + sql.From(sql.SqlContext.SqlSyntax.GetQuotedTableName(tableName)); + return sql; + } + + #endregion + + #region OrderBy, GroupBy + + public static Sql OrderBy(this Sql sql, Expression> columnMember) + { + var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; + var columnName = column.FirstAttribute().Name; + + var type = typeof (T); + var tableNameAttribute = type.FirstAttribute(); + var tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + + // need to ensure the order by is in brackets, see: https://github.com/toptensoftware/PetaPoco/issues/177 + var sqlSyntax = sql.SqlContext.SqlSyntax; + var syntax = $"({sqlSyntax.GetQuotedTableName(tableName)}.{sqlSyntax.GetQuotedColumnName(columnName)})"; + + sql.OrderBy(syntax); + return sql; + } + + public static Sql OrderByDescending(this Sql sql, Expression> columnMember) + { + var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; + var columnName = column.FirstAttribute().Name; + + var type = typeof(T); + var tableNameAttribute = type.FirstAttribute(); + var tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + + var sqlSyntax = sql.SqlContext.SqlSyntax; + var syntax = $"{sqlSyntax.GetQuotedTableName(tableName)}.{sqlSyntax.GetQuotedColumnName(columnName)} DESC"; + + sql.OrderBy(syntax); + return sql; + } + + public static Sql OrderByDescending(this Sql sql, params object[] columns) + { + sql.Append("ORDER BY " + string.Join(", ", columns.Select(x => x + " DESC"))); + return sql; + } + + public static Sql GroupBy(this Sql sql, Expression> columnMember) + { + var column = ExpressionHelper.FindProperty(columnMember) as PropertyInfo; + var columnName = column.FirstAttribute().Name; + + sql.GroupBy(sql.SqlContext.SqlSyntax.GetQuotedColumnName(columnName)); + return sql; + } + + #endregion + + #region Joins + + public static Sql.SqlJoinClause InnerJoin(this Sql sql) + { + var type = typeof(T); + var tableNameAttribute = type.FirstAttribute(); + var tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + + return sql.InnerJoin(sql.SqlContext.SqlSyntax.GetQuotedTableName(tableName)); + } + + public static Sql.SqlJoinClause LeftJoin(this Sql sql) + { + var type = typeof(T); + var tableNameAttribute = type.FirstAttribute(); + var tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + + return sql.LeftJoin(sql.SqlContext.SqlSyntax.GetQuotedTableName(tableName)); + } + + public static Sql.SqlJoinClause LeftOuterJoin(this Sql sql) + { + var type = typeof(T); + var tableNameAttribute = type.FirstAttribute(); + var tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + + return sql.LeftOuterJoin(sql.SqlContext.SqlSyntax.GetQuotedTableName(tableName)); + } + + public static Sql.SqlJoinClause RightJoin(this Sql sql) + { + var type = typeof(T); + var tableNameAttribute = type.FirstAttribute(); + var tableName = tableNameAttribute == null ? string.Empty : tableNameAttribute.Value; + + return sql.RightJoin(sql.SqlContext.SqlSyntax.GetQuotedTableName(tableName)); + } + + public static Sql On(this Sql.SqlJoinClause clause, + Expression> leftMember, Expression> rightMember, + params object[] args) + { + var sqlSyntax = clause.SqlContext.SqlSyntax; + + var leftType = typeof (TLeft); + var rightType = typeof (TRight); + var leftTableName = sqlSyntax.GetQuotedTableName(leftType.FirstAttribute().Value); + var rightTableName = sqlSyntax.GetQuotedTableName(rightType.FirstAttribute().Value); + + var left = ExpressionHelper.FindProperty(leftMember) as PropertyInfo; + var right = ExpressionHelper.FindProperty(rightMember) as PropertyInfo; + var leftColumnName = sqlSyntax.GetQuotedColumnName(left.FirstAttribute().Name); + var rightColumnName = sqlSyntax.GetQuotedColumnName(right.FirstAttribute().Name); + + var onClause = $"{leftTableName}.{leftColumnName} = {rightTableName}.{rightColumnName}"; + return clause.On(onClause); + } + + #endregion + + #region Select + + public static Sql SelectCount(this Sql sql) + { + sql.Select("COUNT(*)"); + return sql; + } + + public static Sql SelectAll(this Sql sql) + { + sql.Select("*"); + return sql; + } + + public static Sql Select(this Sql sql, Func refexpr = null) + { + var pd = sql.SqlContext.PocoDataFactory.ForType(typeof (T)); + + var tableName = pd.TableInfo.TableName; + var columns = pd.QueryColumns.Select(x => GetColumn(sql.SqlContext.DatabaseType, + tableName, + x.Value.ColumnName, + string.IsNullOrEmpty(x.Value.ColumnAlias) ? x.Value.MemberInfoKey : x.Value.ColumnAlias)); + + sql.Select(string.Join(", ", columns)); + + if (refexpr == null) return sql; + refexpr(new RefSql(sql, null)); + return sql; + } + + /// + /// + /// + /// + /// + /// + /// The name of the DTO reference. + /// The name of the table alias. + /// + /// + /// Select<Foo>() produces: [foo].[value] AS [Foo_Value] + /// With tableAlias: [tableAlias].[value] AS [Foo_Value] + /// With referenceName: [foo].[value] AS [referenceName_Value] + /// + public static RefSql Select(this RefSql refSql, Func refexpr = null, string referenceName = null, string tableAlias = null) + { + if (referenceName == null) referenceName = typeof (T).Name; + if (refSql.Prefix != null) referenceName = refSql.Prefix + PocoData.Separator + referenceName; + var pd = refSql.Sql.SqlContext.PocoDataFactory.ForType(typeof (T)); + + var tableName = tableAlias ?? pd.TableInfo.TableName; + var columns = pd.QueryColumns.Select(x => GetColumn(refSql.Sql.SqlContext.DatabaseType, + tableName, + x.Value.ColumnName, + string.IsNullOrEmpty(x.Value.ColumnAlias) ? x.Value.MemberInfoKey : x.Value.ColumnAlias, + referenceName)); + + refSql.Sql.Append(", " + string.Join(", ", columns)); + + if (refexpr == null) return refSql; + refexpr(new RefSql(refSql.Sql, referenceName)); + return refSql; + } + + public class RefSql + { + public RefSql(Sql sql, string prefix) + { + Sql = sql; + Prefix = prefix; + } + + public Sql Sql { get; } + public string Prefix { get; } + } + + private static string GetColumn(DatabaseType dbType, string tableName, string columnName, string columnAlias, string referenceName = null) + { + tableName = dbType.EscapeTableName(tableName); + columnName = dbType.EscapeSqlIdentifier(columnName); + columnAlias = dbType.EscapeSqlIdentifier((referenceName == null ? "" : (referenceName + "__")) + columnAlias); + return tableName + "." + columnName + " AS " + columnAlias; + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/NPocoUnitOfWork.cs similarity index 88% rename from src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs rename to src/Umbraco.Core/Persistence/UnitOfWork/NPocoUnitOfWork.cs index a5337e854c..5b6af0b4c0 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/NPocoUnitOfWork.cs @@ -1,183 +1,183 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Models.EntityBase; - -namespace Umbraco.Core.Persistence.UnitOfWork -{ - /// - /// Represents the Unit of Work implementation for PetaPoco - /// - internal class PetaPocoUnitOfWork : DisposableObject, IDatabaseUnitOfWork - { - - /// - /// Used for testing - /// - internal Guid InstanceId { get; private set; } - - private Guid _key; - private readonly Queue _operations = new Queue(); - - /// - /// Creates a new unit of work instance - /// - /// - /// - /// This should normally not be used directly and should be created with the UnitOfWorkProvider - /// - internal PetaPocoUnitOfWork(UmbracoDatabase database) - { - Database = database; - _key = Guid.NewGuid(); - InstanceId = Guid.NewGuid(); - } - - /// - /// Registers an instance to be added through this - /// - /// The - /// The participating in the transaction - public void RegisterAdded(IEntity entity, IUnitOfWorkRepository repository) - { - _operations.Enqueue(new Operation - { - Entity = entity, - Repository = repository, - Type = TransactionType.Insert - }); - } - - /// - /// Registers an instance to be changed through this - /// - /// The - /// The participating in the transaction - public void RegisterChanged(IEntity entity, IUnitOfWorkRepository repository) - { - _operations.Enqueue( - new Operation - { - Entity = entity, - Repository = repository, - Type = TransactionType.Update - }); - } - - /// - /// Registers an instance to be removed through this - /// - /// The - /// The participating in the transaction - public void RegisterRemoved(IEntity entity, IUnitOfWorkRepository repository) - { - _operations.Enqueue( - new Operation - { - Entity = entity, - Repository = repository, - Type = TransactionType.Delete - }); - } - - /// - /// Commits all batched changes within the scope of a PetaPoco transaction - /// - /// - /// Unlike a typical unit of work, this UOW will let you commit more than once since a new transaction is creaed per - /// Commit() call instead of having one Transaction per UOW. - /// - public void Commit() - { - Commit(null); - } - - /// - /// Commits all batched changes within the scope of a PetaPoco transaction - /// - /// - /// Allows you to set a callback which is executed before the transaction is committed, allow you to add additional SQL - /// operations to the overall commit process after the queue has been processed. - /// - internal void Commit(Action transactionCompleting) - { - using (var transaction = Database.GetTransaction()) - { - while (_operations.Count > 0) - { - var operation = _operations.Dequeue(); - switch (operation.Type) - { - case TransactionType.Insert: - operation.Repository.PersistNewItem(operation.Entity); - break; - case TransactionType.Delete: - operation.Repository.PersistDeletedItem(operation.Entity); - break; - case TransactionType.Update: - operation.Repository.PersistUpdatedItem(operation.Entity); - break; - } - } - - //Execute the callback if there is one - if (transactionCompleting != null) - { - transactionCompleting(Database); - } - - transaction.Complete(); - } - - // Clear everything - _operations.Clear(); - _key = Guid.NewGuid(); - } - - public object Key - { - get { return _key; } - } - - public UmbracoDatabase Database { get; private set; } - - #region Operation - - /// - /// Provides a snapshot of an entity and the repository reference it belongs to. - /// - private sealed class Operation - { - /// - /// Gets or sets the entity. - /// - /// The entity. - public IEntity Entity { get; set; } - - /// - /// Gets or sets the repository. - /// - /// The repository. - public IUnitOfWorkRepository Repository { get; set; } - - /// - /// Gets or sets the type of operation. - /// - /// The type of operation. - public TransactionType Type { get; set; } - } - - #endregion - - /// - /// Ensures disposable objects are disposed - /// - /// - /// Ensures that the Transaction instance is disposed of - /// - protected override void DisposeResources() - { - _operations.Clear(); - } - } +using System; +using System.Collections.Generic; +using NPoco; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Persistence.UnitOfWork +{ + /// + /// Represents the Unit of Work implementation for NPoco + /// + internal class NPocoUnitOfWork : DisposableObject, IDatabaseUnitOfWork + { + + /// + /// Used for testing + /// + internal Guid InstanceId { get; private set; } + + private Guid _key; + private readonly Queue _operations = new Queue(); + + /// + /// Creates a new unit of work instance + /// + /// + /// + /// This should normally not be used directly and should be created with the UnitOfWorkProvider + /// + internal NPocoUnitOfWork(UmbracoDatabase database) + { + Database = database; + _key = Guid.NewGuid(); + InstanceId = Guid.NewGuid(); + } + + /// + /// Registers an instance to be added through this + /// + /// The + /// The participating in the transaction + public void RegisterAdded(IEntity entity, IUnitOfWorkRepository repository) + { + _operations.Enqueue(new Operation + { + Entity = entity, + Repository = repository, + Type = TransactionType.Insert + }); + } + + /// + /// Registers an instance to be changed through this + /// + /// The + /// The participating in the transaction + public void RegisterChanged(IEntity entity, IUnitOfWorkRepository repository) + { + _operations.Enqueue( + new Operation + { + Entity = entity, + Repository = repository, + Type = TransactionType.Update + }); + } + + /// + /// Registers an instance to be removed through this + /// + /// The + /// The participating in the transaction + public void RegisterRemoved(IEntity entity, IUnitOfWorkRepository repository) + { + _operations.Enqueue( + new Operation + { + Entity = entity, + Repository = repository, + Type = TransactionType.Delete + }); + } + + /// + /// Commits all batched changes within the scope of a NPoco . + /// + /// + /// Unlike a typical unit of work, this UOW will let you commit more than once since a new transaction is creaed per + /// Commit() call instead of having one transaction per UOW. + /// + public void Commit() + { + Commit(null); + } + + /// + /// Commits all batched changes within the scope of a NPoco . + /// + /// + /// Allows you to set a callback which is executed before the transaction is committed, allow you to add additional SQL + /// operations to the overall commit process after the queue has been processed. + /// + internal void Commit(Action transactionCompleting) + { + using (var transaction = Database.GetTransaction()) + { + while (_operations.Count > 0) + { + var operation = _operations.Dequeue(); + switch (operation.Type) + { + case TransactionType.Insert: + operation.Repository.PersistNewItem(operation.Entity); + break; + case TransactionType.Delete: + operation.Repository.PersistDeletedItem(operation.Entity); + break; + case TransactionType.Update: + operation.Repository.PersistUpdatedItem(operation.Entity); + break; + } + } + + //Execute the callback if there is one + if (transactionCompleting != null) + { + transactionCompleting(Database); + } + + transaction.Complete(); + } + + // Clear everything + _operations.Clear(); + _key = Guid.NewGuid(); + } + + public object Key + { + get { return _key; } + } + + public UmbracoDatabase Database { get; private set; } + + #region Operation + + /// + /// Provides a snapshot of an entity and the repository reference it belongs to. + /// + private sealed class Operation + { + /// + /// Gets or sets the entity. + /// + /// The entity. + public IEntity Entity { get; set; } + + /// + /// Gets or sets the repository. + /// + /// The repository. + public IUnitOfWorkRepository Repository { get; set; } + + /// + /// Gets or sets the type of operation. + /// + /// The type of operation. + public TransactionType Type { get; set; } + } + + #endregion + + /// + /// Ensures disposable objects are disposed + /// + /// + /// Ensures that the Transaction instance is disposed of + /// + protected override void DisposeResources() + { + _operations.Clear(); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/NPocoUnitOfWorkProvider.cs b/src/Umbraco.Core/Persistence/UnitOfWork/NPocoUnitOfWorkProvider.cs new file mode 100644 index 0000000000..c8407eb08d --- /dev/null +++ b/src/Umbraco.Core/Persistence/UnitOfWork/NPocoUnitOfWorkProvider.cs @@ -0,0 +1,47 @@ +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Persistence.UnitOfWork +{ + /// + /// Represents a Unit of Work Provider for creating a . + /// + public class NPocoUnitOfWorkProvider : IDatabaseUnitOfWorkProvider + { + private readonly IDatabaseFactory _dbFactory; + + /// + /// Initializes a new instance of the class with an . + /// + /// + public NPocoUnitOfWorkProvider(IDatabaseFactory dbFactory) + { + Mandate.ParameterNotNull(dbFactory, "dbFactory"); + _dbFactory = dbFactory; + } + + // for unit tests only + // will re-create a new DefaultDatabaseFactory each time it is called + internal NPocoUnitOfWorkProvider(ILogger logger) + : this(new DefaultDatabaseFactory(GlobalSettings.UmbracoConnectionName, logger)) + { } + + #region Implementation of IUnitOfWorkProvider + + /// + /// Creates a unit of work around a database obtained from the database factory. + /// + /// A unit of work. + /// The unit of work will execute on the current database returned by the database factory. + public IDatabaseUnitOfWork GetUnitOfWork() + { + // get a database from the factory - might be the "ambient" database eg + // the one that's enlisted with the HttpContext - so it's not always a + // "new" database. + var database = _dbFactory.CreateDatabase(); + return new NPocoUnitOfWork(database); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs deleted file mode 100644 index 65dc09b418..0000000000 --- a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; - -namespace Umbraco.Core.Persistence.UnitOfWork -{ - /// - /// Represents a Unit of Work Provider for creating a - /// - public class PetaPocoUnitOfWorkProvider : IDatabaseUnitOfWorkProvider - { - private readonly IDatabaseFactory _dbFactory; - - /// - /// Parameterless constructor uses defaults - /// - public PetaPocoUnitOfWorkProvider(ILogger logger) - : this(new DefaultDatabaseFactory(GlobalSettings.UmbracoConnectionName, logger)) - { - - } - - /// - /// Constructor accepting custom connectino string and provider name - /// - /// - /// Connection String to use with Database - /// Database Provider for the Connection String - public PetaPocoUnitOfWorkProvider(ILogger logger, string connectionString, string providerName) - : this(new DefaultDatabaseFactory(connectionString, providerName, logger)) - { } - - /// - /// Constructor accepting an IDatabaseFactory instance - /// - /// - public PetaPocoUnitOfWorkProvider(IDatabaseFactory dbFactory) - { - Mandate.ParameterNotNull(dbFactory, "dbFactory"); - _dbFactory = dbFactory; - } - - #region Implementation of IUnitOfWorkProvider - - /// - /// Creates a Unit of work with a new UmbracoDatabase instance for the work item/transaction. - /// - /// - /// - /// Each PetaPoco UOW uses it's own Database object, not the shared Database object that comes from - /// the ApplicationContext.Current.DatabaseContext.Database. This is because each transaction should use it's own Database - /// and we Dispose of this Database object when the UOW is disposed. - /// - public IDatabaseUnitOfWork GetUnitOfWork() - { - return new PetaPocoUnitOfWork(_dbFactory.CreateDatabase()); - } - - #endregion - - /// - /// Static helper method to return a new unit of work - /// - /// - internal static IDatabaseUnitOfWork CreateUnitOfWork(ILogger logger) - { - var provider = new PetaPocoUnitOfWorkProvider(logger); - return provider.GetUnitOfWork(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index 4314308f41..5f24eac64b 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.CodeAnnotations; using Umbraco.Core.Events; @@ -86,9 +87,10 @@ namespace Umbraco.Core.Services case UmbracoObjectTypes.DataType: case UmbracoObjectTypes.DocumentTypeContainer: return uow.Database.ExecuteScalar( - new Sql().Select("id") - .From(RepositoryFactory.SqlSyntax) - .Where(RepositoryFactory.SqlSyntax, dto => dto.UniqueId == key)); + NPoco.Sql.BuilderFor(new SqlContext(RepositoryFactory.SqlSyntax, uow.Database)) + .Select("id") + .From() + .Where(dto => dto.UniqueId == key)); case UmbracoObjectTypes.RecycleBin: case UmbracoObjectTypes.Stylesheet: case UmbracoObjectTypes.MemberGroup: @@ -127,9 +129,10 @@ namespace Umbraco.Core.Services case UmbracoObjectTypes.Member: case UmbracoObjectTypes.DataType: return uow.Database.ExecuteScalar( - new Sql().Select("uniqueID") - .From(RepositoryFactory.SqlSyntax) - .Where(RepositoryFactory.SqlSyntax, dto => dto.NodeId == id)); + NPoco.Sql.BuilderFor(new SqlContext(RepositoryFactory.SqlSyntax, uow.Database)) + .Select("uniqueID") + .From() + .Where(dto => dto.NodeId == id)); case UmbracoObjectTypes.RecycleBin: case UmbracoObjectTypes.Stylesheet: case UmbracoObjectTypes.MemberGroup: @@ -342,7 +345,7 @@ namespace Umbraco.Core.Services using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork())) { var query = repository.Query.Where(x => x.ParentId == parentId); - var contents = repository.GetByQuery(query, objectTypeId); + var contents = repository.GetByQuery(query, objectTypeId).ToList(); // run within using! return contents; } @@ -492,7 +495,10 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.GetUnitOfWork()) { - var sql = new Sql().Select("nodeObjectType").From(RepositoryFactory.SqlSyntax).Where(RepositoryFactory.SqlSyntax, x => x.NodeId == id); + var sql = NPoco.Sql.BuilderFor(new SqlContext(RepositoryFactory.SqlSyntax, uow.Database)) + .Select("nodeObjectType") + .From() + .Where(x => x.NodeId == id); var nodeObjectTypeId = uow.Database.ExecuteScalar(sql); var objectTypeId = nodeObjectTypeId; return UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); @@ -508,7 +514,10 @@ namespace Umbraco.Core.Services { using (var uow = UowProvider.GetUnitOfWork()) { - var sql = new Sql().Select("nodeObjectType").From(RepositoryFactory.SqlSyntax).Where(RepositoryFactory.SqlSyntax, x => x.UniqueId == key); + var sql = NPoco.Sql.BuilderFor(new SqlContext(RepositoryFactory.SqlSyntax, uow.Database)) + .Select("nodeObjectType") + .From() + .Where(x => x.UniqueId == key); var nodeObjectTypeId = uow.Database.ExecuteScalar(sql); var objectTypeId = nodeObjectTypeId; return UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index fffa158a46..c16ce5a1a8 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Web; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -244,10 +245,10 @@ namespace Umbraco.Core.Sync // // FIXME not true if we're running on a background thread, assuming we can? - var sql = new Sql().Select("*") - .From(_appContext.DatabaseContext.SqlSyntax) - .Where(_appContext.DatabaseContext.SqlSyntax, dto => dto.Id > _lastId) - .OrderBy(_appContext.DatabaseContext.SqlSyntax, dto => dto.Id); + var sql = _appContext.DatabaseContext.Sql().SelectAll() + .From() + .Where(dto => dto.Id > _lastId) + .OrderBy(dto => dto.Id); var dtos = _appContext.DatabaseContext.Database.Fetch(sql); if (dtos.Count <= 0) return; @@ -348,9 +349,9 @@ namespace Umbraco.Core.Sync /// and it should instead cold-boot. private void EnsureInstructions() { - var sql = new Sql().Select("*") - .From(_appContext.DatabaseContext.SqlSyntax) - .Where(_appContext.DatabaseContext.SqlSyntax, dto => dto.Id == _lastId); + var sql = _appContext.DatabaseContext.Sql().SelectAll() + .From() + .Where(dto => dto.Id == _lastId); var dtos = _appContext.DatabaseContext.Database.Fetch(sql); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index ac36147368..26137a72bc 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -92,6 +92,10 @@ ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll True + + ..\packages\NPoco.3.1.0-u005\lib\net45\NPoco.dll + True + ..\packages\Owin.1.0\lib\net40\Owin.dll @@ -333,6 +337,7 @@ + @@ -368,6 +373,9 @@ + + Component + @@ -445,7 +453,6 @@ - @@ -465,6 +472,9 @@ + + + @@ -755,7 +765,6 @@ - @@ -766,7 +775,7 @@ - + @@ -964,19 +973,13 @@ - - - - + - - - @@ -997,7 +1000,6 @@ - @@ -1012,7 +1014,7 @@ - + @@ -1045,8 +1047,8 @@ - - + + @@ -1150,7 +1152,6 @@ - diff --git a/src/Umbraco.Core/Umbraco.Core.csproj.DotSettings b/src/Umbraco.Core/Umbraco.Core.csproj.DotSettings index 662f95686e..73e96563f9 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj.DotSettings +++ b/src/Umbraco.Core/Umbraco.Core.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.Core/packages.config b/src/Umbraco.Core/packages.config index d3aa7b8ba5..1cbbee0585 100644 --- a/src/Umbraco.Core/packages.config +++ b/src/Umbraco.Core/packages.config @@ -13,6 +13,7 @@ + diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index 916277329e..2a3bfdc290 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -2,12 +2,12 @@ - +
      - + - + @@ -52,7 +52,7 @@ - + @@ -64,9 +64,9 @@ - + - + @@ -121,13 +121,13 @@ - + - + - + - + diff --git a/src/Umbraco.Tests/EnumerableExtensionsTests.cs b/src/Umbraco.Tests/EnumerableExtensionsTests.cs index 8edba760a1..da69ab9d40 100644 --- a/src/Umbraco.Tests/EnumerableExtensionsTests.cs +++ b/src/Umbraco.Tests/EnumerableExtensionsTests.cs @@ -156,6 +156,15 @@ namespace Umbraco.Tests CollectionAssert.AreEquivalent(integers, flattened); } + [Test] + public void InGroupsOf_CanRepeat() + { + var integers = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; + var inGroupsOf = integers.InGroupsOf(2); + Assert.AreEqual(5, inGroupsOf.Count()); + Assert.AreEqual(5, inGroupsOf.Count()); // again + } + [TestCase] public void DistinctBy_ReturnsDistinctElements_AndResetsIteratorCorrectly() { diff --git a/src/Umbraco.Tests/Migrations/AlterMigrationTests.cs b/src/Umbraco.Tests/Migrations/AlterMigrationTests.cs index 49c0e1e8ac..9168ee6ace 100644 --- a/src/Umbraco.Tests/Migrations/AlterMigrationTests.cs +++ b/src/Umbraco.Tests/Migrations/AlterMigrationTests.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using Moq; +using NPoco; using NUnit.Framework; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; @@ -19,6 +20,7 @@ namespace Umbraco.Tests.Migrations { // Arrange var sqlSyntax = new SqlCeSyntaxProvider(); + // fixme Database vs UmbracoDatabase var context = new MigrationContext(DatabaseProviders.SqlServerCE, new Database("test", "System.Data.SqlClient"), Mock.Of(), sqlSyntax); var stub = new DropForeignKeyMigrationStub(sqlSyntax, Mock.Of()); diff --git a/src/Umbraco.Tests/Migrations/MigrationRunnerTests.cs b/src/Umbraco.Tests/Migrations/MigrationRunnerTests.cs index ee1639d41a..5be8f9bda9 100644 --- a/src/Umbraco.Tests/Migrations/MigrationRunnerTests.cs +++ b/src/Umbraco.Tests/Migrations/MigrationRunnerTests.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using Moq; +using NPoco; using NUnit.Framework; using Semver; using Umbraco.Core.Logging; @@ -29,6 +30,7 @@ namespace Umbraco.Tests.Migrations var ctx = runner.InitializeMigrations( //new List {new DoRunMigration(), new DoNotRunMigration()}, migrations.ToList(), + // fixme Database vs UmbracoDatabase new Database("umbracoDbDSN") , DatabaseProviders.SqlServerCE, Mock.Of(), true); @@ -48,6 +50,7 @@ namespace Umbraco.Tests.Migrations var ctx = runner.InitializeMigrations( //new List {new DoRunMigration(), new DoNotRunMigration()}, migrations.ToList(), + // fixme Database vs UmbracoDatabase new Database("umbracoDbDSN") , DatabaseProviders.SqlServerCE, Mock.Of(), true); @@ -67,6 +70,7 @@ namespace Umbraco.Tests.Migrations var ctx = runner.InitializeMigrations( //new List {new DoRunMigration(), new DoNotRunMigration()}, migrations.ToList(), + // fixme Database vs UmbracoDatabase new Database("umbracoDbDSN") , DatabaseProviders.SqlServerCE, Mock.Of(), true); diff --git a/src/Umbraco.Tests/Migrations/Upgrades/BaseUpgradeTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/BaseUpgradeTest.cs index 8faa4f8717..e21ee81e2b 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/BaseUpgradeTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/BaseUpgradeTest.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text.RegularExpressions; using Moq; +using NPoco; using NUnit.Framework; using Semver; using Umbraco.Core; @@ -58,7 +59,6 @@ namespace Umbraco.Tests.Migrations.Upgrades } var logger = Mock.Of(); - var sqlHelper = Mock.Of(); var sql = GetSyntaxProvider(); //Setup the MigrationRunner @@ -83,11 +83,11 @@ namespace Umbraco.Tests.Migrations.Upgrades new UpdateCmsContentVersionTable(sql, logger), new UpdateCmsPropertyTypeGroupTable(sql, logger)); - bool upgraded = migrationRunner.Execute(db, provider, sqlHelper, true); + bool upgraded = migrationRunner.Execute(db, provider, sql, true); Assert.That(upgraded, Is.True); - var schemaHelper = new DatabaseSchemaHelper(db, Mock.Of(), sqlHelper); + var schemaHelper = new DatabaseSchemaHelper(db, Mock.Of(), sql); bool hasTabTable = schemaHelper.TableExist("cmsTab"); bool hasPropertyTypeGroupTable = schemaHelper.TableExist("cmsPropertyTypeGroup"); diff --git a/src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs index 5ef07ef569..d8d4bd5fe2 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/SqlCeDataUpgradeTest.cs @@ -68,7 +68,7 @@ namespace Umbraco.Tests.Migrations.Upgrades public override UmbracoDatabase GetConfiguredDatabase() { - return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", "System.Data.SqlServerCe.4.0", Mock.Of()); + return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoNPocoTests.sdf;Flush Interval=1;", "System.Data.SqlServerCe.4.0", Mock.Of()); } public override DatabaseProviders GetDatabaseProvider() @@ -79,6 +79,6 @@ namespace Umbraco.Tests.Migrations.Upgrades public override string GetDatabaseSpecificSqlScript() { return SqlScripts.SqlResources.SqlCe_SchemaAndData_4110; - } + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Migrations/Upgrades/SqlCeUpgradeTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/SqlCeUpgradeTest.cs index 856a8fcf4e..6e889313f8 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/SqlCeUpgradeTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/SqlCeUpgradeTest.cs @@ -17,13 +17,13 @@ namespace Umbraco.Tests.Migrations.Upgrades { public override void DatabaseSpecificSetUp() { - string filePath = string.Concat(Path, "\\UmbracoPetaPocoTests.sdf"); + string filePath = string.Concat(Path, "\\UmbracoNPocoTests.sdf"); if (!File.Exists(filePath)) { try { - //Delete database file before continueing + //Delete database file before continueing if (File.Exists(filePath)) { File.Delete(filePath); @@ -47,7 +47,7 @@ namespace Umbraco.Tests.Migrations.Upgrades { TestHelper.ClearDatabase(); } - + } public override void DatabaseSpecificTearDown() @@ -65,7 +65,7 @@ namespace Umbraco.Tests.Migrations.Upgrades public override UmbracoDatabase GetConfiguredDatabase() { - return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", "System.Data.SqlServerCe.4.0", Mock.Of()); + return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoNPocoTests.sdf;Flush Interval=1;", "System.Data.SqlServerCe.4.0", Mock.Of()); } public override DatabaseProviders GetDatabaseProvider() diff --git a/src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs index f64b7041ea..0b761d0a67 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs @@ -4,6 +4,7 @@ using System.Data.SqlServerCe; using System.IO; using System.Text.RegularExpressions; using Moq; +using NPoco; using NUnit.Framework; using SQLCE4Umbraco; using Umbraco.Core.Configuration; @@ -58,7 +59,7 @@ namespace Umbraco.Tests.Migrations.Upgrades AppDomain.CurrentDomain.SetData("DataDirectory", Path); //Delete database file before continueing - string filePath = string.Concat(Path, "\\UmbracoPetaPocoTests.sdf"); + string filePath = string.Concat(Path, "\\UmbracoNPocoTests.sdf"); if (File.Exists(filePath)) { File.Delete(filePath); @@ -88,18 +89,18 @@ namespace Umbraco.Tests.Migrations.Upgrades //legacy API database connection close SqlCeContextGuardian.CloseBackgroundConnection(); - string filePath = string.Concat(Path, "\\UmbracoPetaPocoTests.sdf"); + string filePath = string.Concat(Path, "\\UmbracoNPocoTests.sdf"); if (File.Exists(filePath)) { File.Delete(filePath); } } - + public string Path { get; set; } public UmbracoDatabase GetConfiguredDatabase() { - return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;", "System.Data.SqlServerCe.4.0", Mock.Of()); + return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoNPocoTests.sdf;Flush Interval=1;", "System.Data.SqlServerCe.4.0", Mock.Of()); } public string GetDatabaseSpecificSqlScript() diff --git a/src/Umbraco.Tests/Migrations/Upgrades/ValidateV7UpgradeTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/ValidateV7UpgradeTest.cs index 3e49663546..6822728ad4 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/ValidateV7UpgradeTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/ValidateV7UpgradeTest.cs @@ -1,5 +1,6 @@ using System.Linq; using Moq; +using NPoco; using NUnit.Framework; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; @@ -19,6 +20,7 @@ namespace Umbraco.Tests.Migrations.Upgrades { var sqlSyntax = new SqlCeSyntaxProvider(); var migration = new AddIndexToCmsMacroTable(true, sqlSyntax, Mock.Of()); + // fixme Database vs UmbracoDatabase var migrationContext = new MigrationContext(DatabaseProviders.SqlServerCE, new Database("test", "System.Data.SqlClient"), Mock.Of(), sqlSyntax); migration.GetUpExpressions(migrationContext); @@ -35,6 +37,7 @@ namespace Umbraco.Tests.Migrations.Upgrades { var sqlSyntax = new SqlCeSyntaxProvider(); var migration = new AddIndexToCmsMacroPropertyTable(true, sqlSyntax, Mock.Of()); + // fixme Database vs UmbracoDatabase var migrationContext = new MigrationContext(DatabaseProviders.SqlServerCE, new Database("test", "System.Data.SqlClient"), Mock.Of(), sqlSyntax); migration.GetUpExpressions(migrationContext); diff --git a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs index 86b321c9e3..6fa7499553 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs @@ -1,13 +1,17 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using AutoMapper; +using LightInject; using Moq; using NUnit.Framework; using umbraco; using umbraco.cms.presentation; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; +using Umbraco.Core.Manifest; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; @@ -37,10 +41,11 @@ namespace Umbraco.Tests.Models.Mapping public void Setup() { var nullCacheHelper = CacheHelper.CreateDisabledCacheHelper(); + var logger = Mock.Of(); //Create an app context using mocks var appContext = new ApplicationContext( - new DatabaseContext(Mock.Of(), Mock.Of(), Mock.Of(), "test"), + new DatabaseContext(Mock.Of(), logger, Mock.Of(), "test"), //Create service context using mocks new ServiceContext( @@ -52,13 +57,14 @@ namespace Umbraco.Tests.Models.Mapping fileService: _fileService.Object), nullCacheHelper, - new ProfilingLogger(Mock.Of(), Mock.Of())); + new ProfilingLogger(logger, Mock.Of())); //create a fake property editor resolver to return fake property editors Func> typeListProducerList = Enumerable.Empty; _propertyEditorResolver = new Mock( //ctor args - Mock.Of(), Mock.Of(), typeListProducerList, nullCacheHelper.RuntimeCache); + Mock.Of(), logger, typeListProducerList, + new ManifestBuilder(nullCacheHelper.RuntimeCache, new ManifestParser(logger, new DirectoryInfo(TestHelper.CurrentAssemblyDirectory), Mock.Of()))); Mapper.Initialize(configuration => { diff --git a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs index 5883562f3b..db6bab1160 100644 --- a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs +++ b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs @@ -27,14 +27,14 @@ namespace Umbraco.Tests.Persistence new DefaultDatabaseFactory(Core.Configuration.GlobalSettings.UmbracoConnectionName, Mock.Of()), Mock.Of(), new SqlCeSyntaxProvider(), "System.Data.SqlServerCe.4.0"); - //unfortunately we have to set this up because the PetaPocoExtensions require singleton access + //unfortunately we have to set this up because the NPocoExtensions require singleton access ApplicationContext.Current = new ApplicationContext( CacheHelper.CreateDisabledCacheHelper(), new ProfilingLogger(Mock.Of(), Mock.Of())) { DatabaseContext = _dbContext, IsReady = true - }; + }; } [TearDown] @@ -78,10 +78,10 @@ namespace Umbraco.Tests.Persistence //Get the connectionstring settings from config var settings = ConfigurationManager.ConnectionStrings[Core.Configuration.GlobalSettings.UmbracoConnectionName]; - //by default the conn string is: Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1; + //by default the conn string is: Datasource=|DataDirectory|UmbracoNPocoTests.sdf;Flush Interval=1; //we'll just replace the sdf file with our custom one: //Create the Sql CE database - var engine = new SqlCeEngine(settings.ConnectionString.Replace("UmbracoPetaPocoTests", "DatabaseContextTests")); + var engine = new SqlCeEngine(settings.ConnectionString.Replace("UmbracoNPocoTests", "DatabaseContextTests")); engine.CreateDatabase(); var dbFactory = new DefaultDatabaseFactory(engine.LocalConnectionString, "System.Data.SqlServerCe.4.0", Mock.Of()); @@ -96,7 +96,7 @@ namespace Umbraco.Tests.Persistence var appCtx = new ApplicationContext( new DatabaseContext(Mock.Of(), Mock.Of(), Mock.Of(), "test"), - new ServiceContext(migrationEntryService: Mock.Of()), + new ServiceContext(migrationEntryService: Mock.Of()), CacheHelper.CreateDisabledCacheHelper(), new ProfilingLogger(Mock.Of(), Mock.Of())); diff --git a/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs b/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs index 841db8198c..f97b030dda 100644 --- a/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs +++ b/src/Umbraco.Tests/Persistence/FaultHandling/ConnectionRetryTest.cs @@ -10,7 +10,7 @@ namespace Umbraco.Tests.Persistence.FaultHandling public class ConnectionRetryTest { [Test] - public void PetaPocoConnection_Cant_Connect_To_SqlDatabase_With_Invalid_User() + public void Cant_Connect_To_SqlDatabase_With_Invalid_User() { // Arrange const string providerName = "System.Data.SqlClient"; @@ -24,7 +24,7 @@ namespace Umbraco.Tests.Persistence.FaultHandling } [Test] - public void PetaPocoConnection_Cant_Connect_To_SqlDatabase_Because_Of_Network() + public void Cant_Connect_To_SqlDatabase_Because_Of_Network() { // Arrange const string providerName = "System.Data.SqlClient"; diff --git a/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs b/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs index 70daccc74c..3ebd40577b 100644 --- a/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs +++ b/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Data; +using System.Data.Common; using System.Linq; using System.Text; using System.Threading.Tasks; using Moq; +using NPoco; using NUnit.Framework; using Semver; using Umbraco.Core; @@ -22,6 +24,43 @@ namespace Umbraco.Tests.Persistence.Migrations [TestFixture] public class MigrationStartupHandlerTests { + // NPoco wants a DbConnection and NOT an IDbConnection + // and DbConnection is hard to mock... + private class MockConnection : DbConnection + { + protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) + { + return Mock.Of(); // enough here + } + + public override void Close() + { + throw new NotImplementedException(); + } + + public override void ChangeDatabase(string databaseName) + { + throw new NotImplementedException(); + } + + public override void Open() + { + throw new NotImplementedException(); + } + + public override string ConnectionString { get; set; } + + protected override DbCommand CreateDbCommand() + { + throw new NotImplementedException(); + } + + public override string Database { get; } + public override string DataSource { get; } + public override string ServerVersion { get; } + public override ConnectionState State => ConnectionState.Open; // else NPoco reopens + } + [Test] public void Executes_For_Any_Product_Name_When_Not_Specified() { @@ -29,9 +68,8 @@ namespace Umbraco.Tests.Persistence.Migrations var testHandler1 = new TestMigrationHandler(changed1); testHandler1.OnApplicationStarting(Mock.Of(), new ApplicationContext(CacheHelper.CreateDisabledCacheHelper(), new ProfilingLogger(Mock.Of(), Mock.Of()))); - var conn = new Mock(); - conn.Setup(x => x.BeginTransaction(It.IsAny())).Returns(Mock.Of()); - var db = new Mock(conn.Object); + var conn = new MockConnection(); + var db = new Mock(conn); var runner1 = new MigrationRunner(Mock.Of(), Mock.Of(), Mock.Of(), new SemVersion(1), new SemVersion(2), "Test1", new IMigration[] { Mock.Of() }); @@ -49,9 +87,8 @@ namespace Umbraco.Tests.Persistence.Migrations var testHandler2 = new TestMigrationHandler("Test2", changed2); testHandler2.OnApplicationStarting(Mock.Of(), new ApplicationContext(CacheHelper.CreateDisabledCacheHelper(), new ProfilingLogger(Mock.Of(), Mock.Of()))); - var conn = new Mock(); - conn.Setup(x => x.BeginTransaction(It.IsAny())).Returns(Mock.Of()); - var db = new Mock(conn.Object); + var conn = new MockConnection(); + var db = new Mock(conn); var runner1 = new MigrationRunner(Mock.Of(), Mock.Of(), Mock.Of(), new SemVersion(1), new SemVersion(2), "Test1", new IMigration[] { Mock.Of()}); diff --git a/src/Umbraco.Tests/Persistence/NPocoExtensionsTest.cs b/src/Umbraco.Tests/Persistence/NPocoExtensionsTest.cs new file mode 100644 index 0000000000..eb4bdfc23e --- /dev/null +++ b/src/Umbraco.Tests/Persistence/NPocoExtensionsTest.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using NUnit.Framework; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence; +using Umbraco.Tests.TestHelpers; + +namespace Umbraco.Tests.Persistence +{ + // fixme.npoco - is this still appropriate? + // + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] + [TestFixture] + public class NPocoExtensionsTest : BaseDatabaseFactoryTest + { + [SetUp] + public override void Initialize() + { + base.Initialize(); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + [Test] + public void Can_Bulk_Insert() + { + // Arrange + var db = DatabaseContext.Database; + + var servers = new List(); + for (var i = 0; i < 1000; i++) + { + servers.Add(new ServerRegistrationDto + { + ServerAddress = "address" + i, + ServerIdentity = "computer" + i, + DateRegistered = DateTime.Now, + IsActive = true, + DateAccessed = DateTime.Now + }); + } + + // Act + using (ProfilingLogger.TraceDuration("starting insert", "finished insert")) + { + db.BulkInsertRecords(SqlSyntax, servers); + } + + // Assert + Assert.That(db.ExecuteScalar("SELECT COUNT(*) FROM umbracoServer"), Is.EqualTo(1000)); + } + + [Test] + public void Generate_Bulk_Import_Sql() + { + // Arrange + var db = DatabaseContext.Database; + + var servers = new List(); + for (var i = 0; i < 2; i++) + { + servers.Add(new ServerRegistrationDto + { + ServerAddress = "address" + i, + ServerIdentity = "computer" + i, + DateRegistered = DateTime.Now, + IsActive = true, + DateAccessed = DateTime.Now + }); + } + db.OpenSharedConnection(); + + // Act + string[] sql; + db.GenerateBulkInsertCommand(servers, db.Connection, out sql); + db.CloseSharedConnection(); + + // Assert + Assert.That(sql[0], + Is.EqualTo("INSERT INTO [umbracoServer] ([umbracoServer].[address], [umbracoServer].[computerName], [umbracoServer].[registeredDate], [umbracoServer].[lastNotifiedDate], [umbracoServer].[isActive], [umbracoServer].[isMaster]) VALUES (@0,@1,@2,@3,@4,@5), (@6,@7,@8,@9,@10,@11)")); + } + + + [Test] + public void Generate_Bulk_Import_Sql_Exceeding_Max_Params() + { + // Arrange + var db = DatabaseContext.Database; + + var servers = new List(); + for (var i = 0; i < 1500; i++) + { + servers.Add(new ServerRegistrationDto + { + ServerAddress = "address" + i, + ServerIdentity = "computer" + i, + DateRegistered = DateTime.Now, + IsActive = true, + DateAccessed = DateTime.Now, + IsMaster = true + }); + } + db.OpenSharedConnection(); + + // Act + string[] sql; + db.GenerateBulkInsertCommand(servers, db.Connection, out sql); + db.CloseSharedConnection(); + + // Assert + Assert.That(sql.Length, Is.EqualTo(5)); + foreach (var s in sql) + { + Assert.LessOrEqual(Regex.Matches(s, "@\\d+").Count, 2000); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/PetaPocoCachesTest.cs b/src/Umbraco.Tests/Persistence/PetaPocoCachesTest.cs new file mode 100644 index 0000000000..965341d7ea --- /dev/null +++ b/src/Umbraco.Tests/Persistence/PetaPocoCachesTest.cs @@ -0,0 +1,193 @@ +using System; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Tests.Services; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Entities; + +namespace Umbraco.Tests.Persistence +{ + // fixme.npoco - what shall we do with those tests? + // + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] + [TestFixture, Ignore] + public class PetaPocoCachesTest : BaseServiceTest + { + /// + /// This tests the peta poco caches + /// + /// + /// This test WILL fail. This is because we cannot stop PetaPoco from creating more cached items for queries such as + /// ContentTypeRepository.GetAll(1,2,3,4); + /// when combined with other GetAll queries that pass in an array of Ids, each query generated for different length + /// arrays will produce a unique query which then gets added to the cache. + /// + /// This test confirms this, if you analyze the DIFFERENCE output below you can see why the cached queries grow. + /// + //[Test] + //public void Check_Peta_Poco_Caches() + //{ + // var result = new List>>(); + + // Database.PocoData.UseLongKeys = true; + + // for (int i = 0; i < 2; i++) + // { + // int id1, id2, id3; + // string alias; + // CreateStuff(out id1, out id2, out id3, out alias); + // QueryStuff(id1, id2, id3, alias); + + // double totalBytes1; + // IEnumerable keys; + // Console.Write(PocoData.PrintDebugCacheReport(out totalBytes1, out keys)); + + // result.Add(new Tuple>(totalBytes1, keys.Count(), keys)); + // } + + // for (int index = 0; index < result.Count; index++) + // { + // var tuple = result[index]; + // Console.WriteLine("Bytes: {0}, Delegates: {1}", tuple.Item1, tuple.Item2); + // if (index != 0) + // { + // Console.WriteLine("----------------DIFFERENCE---------------------"); + // var diff = tuple.Item3.Except(result[index - 1].Item3); + // foreach (var d in diff) + // { + // Console.WriteLine(d); + // } + // } + + // } + + // var allByteResults = result.Select(x => x.Item1).Distinct(); + // var totalKeys = result.Select(x => x.Item2).Distinct(); + + // Assert.AreEqual(1, allByteResults.Count()); + // Assert.AreEqual(1, totalKeys.Count()); + //} + + //[Test] + //public void Verify_Memory_Expires() + //{ + // Database.PocoData.SlidingExpirationSeconds = 2; + + // var managedCache = new Database.ManagedCache(); + + // int id1, id2, id3; + // string alias; + // CreateStuff(out id1, out id2, out id3, out alias); + // QueryStuff(id1, id2, id3, alias); + + // var count1 = managedCache.GetCache().GetCount(); + // Console.WriteLine("Keys = " + count1); + // Assert.Greater(count1, 0); + + // Thread.Sleep(10000); + + // var count2 = managedCache.GetCache().GetCount(); + // Console.WriteLine("Keys = " + count2); + // Assert.Less(count2, count1); + //} + + private void QueryStuff(int id1, int id2, int id3, string alias1) + { + var contentService = ServiceContext.ContentService; + + ServiceContext.TagService.GetTagsForEntity(id1); + + ServiceContext.TagService.GetAllContentTags(); + + ServiceContext.TagService.GetTagsForEntity(id2); + + ServiceContext.TagService.GetTagsForEntity(id3); + + contentService.CountDescendants(id3); + + contentService.CountChildren(id3); + + contentService.Count(contentTypeAlias: alias1); + + contentService.Count(); + + contentService.GetById(Guid.NewGuid()); + + contentService.GetByLevel(2); + + contentService.GetChildren(id1); + + contentService.GetDescendants(id2); + + contentService.GetVersions(id3); + + contentService.GetRootContent(); + + contentService.GetContentForExpiration(); + + contentService.GetContentForRelease(); + + contentService.GetContentInRecycleBin(); + + ((ContentService)contentService).GetPublishedDescendants(new Content("Test", -1, new ContentType(-1)) + { + Id = id1, + Path = "-1," + id1 + }); + + contentService.GetByVersion(Guid.NewGuid()); + } + + private void CreateStuff(out int id1, out int id2, out int id3, out string alias) + { + var contentService = ServiceContext.ContentService; + + var ctAlias = "umbTextpage" + Guid.NewGuid().ToString("N"); + alias = ctAlias; + + for (int i = 0; i < 20; i++) + { + contentService.CreateContentWithIdentity("Test", -1, "umbTextpage", 0); + } + var contentTypeService = ServiceContext.ContentTypeService; + var contentType = MockedContentTypes.CreateSimpleContentType(ctAlias, "test Doc Type"); + contentTypeService.Save(contentType); + for (int i = 0; i < 20; i++) + { + contentService.CreateContentWithIdentity("Test", -1, ctAlias, 0); + } + var parent = contentService.CreateContentWithIdentity("Test", -1, ctAlias, 0); + id1 = parent.Id; + + for (int i = 0; i < 20; i++) + { + contentService.CreateContentWithIdentity("Test", parent, ctAlias); + } + IContent current = parent; + for (int i = 0; i < 20; i++) + { + current = contentService.CreateContentWithIdentity("Test", current, ctAlias); + } + contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory" + Guid.NewGuid().ToString("N"), "Mandatory Doc Type", true); + contentType.PropertyGroups.First().PropertyTypes.Add( + new PropertyType("test", DataTypeDatabaseType.Ntext, "tags") + { + DataTypeDefinitionId = 1041 + }); + contentTypeService.Save(contentType); + var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1); + content1.SetTags("tags", new[] { "hello", "world", "some", "tags" }, true); + contentService.Publish(content1); + id2 = content1.Id; + + var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1); + content2.SetTags("tags", new[] { "hello", "world", "some", "tags" }, true); + contentService.Publish(content2); + id3 = content2.Id; + + contentService.MoveToRecycleBin(content1); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs b/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs deleted file mode 100644 index 96aae1bcc2..0000000000 --- a/src/Umbraco.Tests/Persistence/PetaPocoExtensionsTest.cs +++ /dev/null @@ -1,308 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence; -using Umbraco.Core.Services; -using Umbraco.Tests.Services; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Entities; - -namespace Umbraco.Tests.Persistence -{ - [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] - [TestFixture, NUnit.Framework.Ignore] - public class PetaPocoCachesTest : BaseServiceTest - { - /// - /// This tests the peta poco caches - /// - /// - /// This test WILL fail. This is because we cannot stop PetaPoco from creating more cached items for queries such as - /// ContentTypeRepository.GetAll(1,2,3,4); - /// when combined with other GetAll queries that pass in an array of Ids, each query generated for different length - /// arrays will produce a unique query which then gets added to the cache. - /// - /// This test confirms this, if you analyze the DIFFERENCE output below you can see why the cached queries grow. - /// - [Test] - public void Check_Peta_Poco_Caches() - { - var result = new List>>(); - - Database.PocoData.UseLongKeys = true; - - for (int i = 0; i < 2; i++) - { - int id1, id2, id3; - string alias; - CreateStuff(out id1, out id2, out id3, out alias); - QueryStuff(id1, id2, id3, alias); - - double totalBytes1; - IEnumerable keys; - Console.Write(Database.PocoData.PrintDebugCacheReport(out totalBytes1, out keys)); - - result.Add(new Tuple>(totalBytes1, keys.Count(), keys)); - } - - for (int index = 0; index < result.Count; index++) - { - var tuple = result[index]; - Console.WriteLine("Bytes: {0}, Delegates: {1}", tuple.Item1, tuple.Item2); - if (index != 0) - { - Console.WriteLine("----------------DIFFERENCE---------------------"); - var diff = tuple.Item3.Except(result[index - 1].Item3); - foreach (var d in diff) - { - Console.WriteLine(d); - } - } - - } - - var allByteResults = result.Select(x => x.Item1).Distinct(); - var totalKeys = result.Select(x => x.Item2).Distinct(); - - Assert.AreEqual(1, allByteResults.Count()); - Assert.AreEqual(1, totalKeys.Count()); - } - - [Test] - public void Verify_Memory_Expires() - { - Database.PocoData.SlidingExpirationSeconds = 2; - - var managedCache = new Database.ManagedCache(); - - int id1, id2, id3; - string alias; - CreateStuff(out id1, out id2, out id3, out alias); - QueryStuff(id1, id2, id3, alias); - - var count1 = managedCache.GetCache().GetCount(); - Console.WriteLine("Keys = " + count1); - Assert.Greater(count1, 0); - - Thread.Sleep(10000); - - var count2 = managedCache.GetCache().GetCount(); - Console.WriteLine("Keys = " + count2); - Assert.Less(count2, count1); - } - - private void QueryStuff(int id1, int id2, int id3, string alias1) - { - var contentService = ServiceContext.ContentService; - - ServiceContext.TagService.GetTagsForEntity(id1); - - ServiceContext.TagService.GetAllContentTags(); - - ServiceContext.TagService.GetTagsForEntity(id2); - - ServiceContext.TagService.GetTagsForEntity(id3); - - contentService.CountDescendants(id3); - - contentService.CountChildren(id3); - - contentService.Count(contentTypeAlias: alias1); - - contentService.Count(); - - contentService.GetById(Guid.NewGuid()); - - contentService.GetByLevel(2); - - contentService.GetChildren(id1); - - contentService.GetDescendants(id2); - - contentService.GetVersions(id3); - - contentService.GetRootContent(); - - contentService.GetContentForExpiration(); - - contentService.GetContentForRelease(); - - contentService.GetContentInRecycleBin(); - - ((ContentService)contentService).GetPublishedDescendants(new Content("Test", -1, new ContentType(-1)) - { - Id = id1, - Path = "-1," + id1 - }); - - contentService.GetByVersion(Guid.NewGuid()); - } - - private void CreateStuff(out int id1, out int id2, out int id3, out string alias) - { - var contentService = ServiceContext.ContentService; - - var ctAlias = "umbTextpage" + Guid.NewGuid().ToString("N"); - alias = ctAlias; - - for (int i = 0; i < 20; i++) - { - contentService.CreateContentWithIdentity("Test", -1, "umbTextpage", 0); - } - var contentTypeService = ServiceContext.ContentTypeService; - var contentType = MockedContentTypes.CreateSimpleContentType(ctAlias, "test Doc Type"); - contentTypeService.Save(contentType); - for (int i = 0; i < 20; i++) - { - contentService.CreateContentWithIdentity("Test", -1, ctAlias, 0); - } - var parent = contentService.CreateContentWithIdentity("Test", -1, ctAlias, 0); - id1 = parent.Id; - - for (int i = 0; i < 20; i++) - { - contentService.CreateContentWithIdentity("Test", parent, ctAlias); - } - IContent current = parent; - for (int i = 0; i < 20; i++) - { - current = contentService.CreateContentWithIdentity("Test", current, ctAlias); - } - contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory" + Guid.NewGuid().ToString("N"), "Mandatory Doc Type", true); - contentType.PropertyGroups.First().PropertyTypes.Add( - new PropertyType("test", DataTypeDatabaseType.Ntext, "tags") - { - DataTypeDefinitionId = 1041 - }); - contentTypeService.Save(contentType); - var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1); - content1.SetTags("tags", new[] { "hello", "world", "some", "tags" }, true); - contentService.Publish(content1); - id2 = content1.Id; - - var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1); - content2.SetTags("tags", new[] { "hello", "world", "some", "tags" }, true); - contentService.Publish(content2); - id3 = content2.Id; - - contentService.MoveToRecycleBin(content1); - } - } - - [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)] - [TestFixture] - public class PetaPocoExtensionsTest : BaseDatabaseFactoryTest - { - [SetUp] - public override void Initialize() - { - base.Initialize(); - } - - [TearDown] - public override void TearDown() - { - base.TearDown(); - } - - [Test] - public void Can_Bulk_Insert() - { - // Arrange - var db = DatabaseContext.Database; - - var servers = new List(); - for (var i = 0; i < 1000; i++) - { - servers.Add(new ServerRegistrationDto - { - ServerAddress = "address" + i, - ServerIdentity = "computer" + i, - DateRegistered = DateTime.Now, - IsActive = true, - DateAccessed = DateTime.Now - }); - } - - // Act - using (ProfilingLogger.TraceDuration("starting insert", "finished insert")) - { - db.BulkInsertRecords(SqlSyntax, servers); - } - - // Assert - Assert.That(db.ExecuteScalar("SELECT COUNT(*) FROM umbracoServer"), Is.EqualTo(1000)); - } - - [Test] - public void Generate_Bulk_Import_Sql() - { - // Arrange - var db = DatabaseContext.Database; - - var servers = new List(); - for (var i = 0; i < 2; i++) - { - servers.Add(new ServerRegistrationDto - { - ServerAddress = "address" + i, - ServerIdentity = "computer" + i, - DateRegistered = DateTime.Now, - IsActive = true, - DateAccessed = DateTime.Now - }); - } - db.OpenSharedConnection(); - - // Act - string[] sql; - db.GenerateBulkInsertCommand(servers, db.Connection, out sql); - db.CloseSharedConnection(); - - // Assert - Assert.That(sql[0], - Is.EqualTo("INSERT INTO [umbracoServer] ([umbracoServer].[address], [umbracoServer].[computerName], [umbracoServer].[registeredDate], [umbracoServer].[lastNotifiedDate], [umbracoServer].[isActive], [umbracoServer].[isMaster]) VALUES (@0,@1,@2,@3,@4,@5), (@6,@7,@8,@9,@10,@11)")); - } - - - [Test] - public void Generate_Bulk_Import_Sql_Exceeding_Max_Params() - { - // Arrange - var db = DatabaseContext.Database; - - var servers = new List(); - for (var i = 0; i < 1500; i++) - { - servers.Add(new ServerRegistrationDto - { - ServerAddress = "address" + i, - ServerIdentity = "computer" + i, - DateRegistered = DateTime.Now, - IsActive = true, - DateAccessed = DateTime.Now, - IsMaster = true - }); - } - db.OpenSharedConnection(); - - // Act - string[] sql; - db.GenerateBulkInsertCommand(servers, db.Connection, out sql); - db.CloseSharedConnection(); - - // Assert - Assert.That(sql.Length, Is.EqualTo(5)); - foreach (var s in sql) - { - Assert.LessOrEqual(Regex.Matches(s, "@\\d+").Count, 2000); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs index 99b3a3cdce..d926d3ce43 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ContentRepositorySqlClausesTest.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; @@ -23,21 +24,21 @@ namespace Umbraco.Tests.Persistence.Querying .InnerJoin("[umbracoNode]").On("[cmsContent].[nodeId] = [umbracoNode].[id]") .Where("([umbracoNode].[nodeObjectType] = @0)", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972")); - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.VersionId, right => right.VersionId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectType); + var sql = Sql(); + sql.SelectAll() + .From() + .InnerJoin() + .On(left => left.VersionId, right => right.VersionId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectType); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length); - for (int i = 0; i < expected.Arguments.Length; i++) + for (var i = 0; i < expected.Arguments.Length; i++) { Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]); } @@ -50,8 +51,8 @@ namespace Umbraco.Tests.Persistence.Querying { var NodeObjectType = new Guid(Constants.ObjectTypes.Document); - var expected = new Sql(); - expected.Select("*") + var expected = Sql(); + expected.SelectAll() .From("[cmsDocument]") .InnerJoin("[cmsContentVersion]").On("[cmsDocument].[versionId] = [cmsContentVersion].[VersionId]") .InnerJoin("[cmsContent]").On("[cmsContentVersion].[ContentId] = [cmsContent].[nodeId]") @@ -59,17 +60,17 @@ namespace Umbraco.Tests.Persistence.Querying .Where("([umbracoNode].[nodeObjectType] = @0)", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972")) .Where("([umbracoNode].[id] = @0)", 1050); - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.VersionId, right => right.VersionId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectType) - .Where(SqlSyntax, x => x.NodeId == 1050); + var sql = Sql(); + sql.SelectAll() + .From() + .InnerJoin() + .On(left => left.VersionId, right => right.VersionId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectType) + .Where(x => x.NodeId == 1050); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); @@ -99,19 +100,19 @@ namespace Umbraco.Tests.Persistence.Querying .Where("([cmsContentVersion].[VersionId] = @0)", new Guid("2b543516-a944-4ee6-88c6-8813da7aaa07")) .OrderBy("[cmsContentVersion].[VersionDate] DESC"); - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.VersionId, right => right.VersionId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectType) - .Where(SqlSyntax, x => x.NodeId == 1050) - .Where(SqlSyntax, x => x.VersionId == versionId) - .OrderByDescending(SqlSyntax, x => x.VersionDate); + var sql = Sql(); + sql.SelectAll() + .From() + .InnerJoin() + .On(left => left.VersionId, right => right.VersionId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectType) + .Where(x => x.NodeId == 1050) + .Where(x => x.VersionId == versionId) + .OrderByDescending(x => x.VersionDate); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length); @@ -136,13 +137,13 @@ namespace Umbraco.Tests.Persistence.Querying expected.Where("([cmsPropertyData].[contentNodeId] = @0)", 1050); expected.Where("([cmsPropertyData].[versionId] = @0)", new Guid("2b543516-a944-4ee6-88c6-8813da7aaa07")); - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.PropertyTypeId, right => right.Id) - .Where(SqlSyntax, x => x.NodeId == id) - .Where(SqlSyntax, x => x.VersionId == versionId); + var sql = Sql(); + sql.SelectAll() + .From() + .InnerJoin() + .On(left => left.PropertyTypeId, right => right.Id) + .Where(x => x.NodeId == id) + .Where(x => x.VersionId == versionId); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length); diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs index bf2d0693ae..6c16ce927a 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; @@ -15,9 +16,8 @@ namespace Umbraco.Tests.Persistence.Querying public void Can_Verify_Base_Clause() { var nodeObjectType = new Guid(Constants.ObjectTypes.DocumentType); - var sqlSyntaxProvider = new SqlCeSyntaxProvider(); - var expected = new Sql(); + var expected = Sql(); expected.Select("*") .From("[cmsDocumentType]") .RightJoin("[cmsContentType]") @@ -27,15 +27,15 @@ namespace Umbraco.Tests.Persistence.Querying .Where("([umbracoNode].[nodeObjectType] = @0)", new Guid("a2cb7800-f571-4787-9638-bc48539a0efb")) .Where("([cmsDocumentType].[IsDefault] = @0)", true); - var sql = new Sql(); - sql.Select("*") - .From(sqlSyntaxProvider) - .RightJoin(sqlSyntaxProvider) - .On(sqlSyntaxProvider, left => left.NodeId, right => right.ContentTypeNodeId) - .InnerJoin(sqlSyntaxProvider) - .On(sqlSyntaxProvider, left => left.NodeId, right => right.NodeId) - .Where(sqlSyntaxProvider, x => x.NodeObjectType == nodeObjectType) - .Where(sqlSyntaxProvider, x => x.IsDefault == true); + var sql = Sql(); + sql.SelectAll() + .From() + .RightJoin() + .On(left => left.NodeId, right => right.ContentTypeNodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == nodeObjectType) + .Where(x => x.IsDefault == true); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); @@ -52,10 +52,9 @@ namespace Umbraco.Tests.Persistence.Querying public void Can_Verify_Base_Where_Clause() { var nodeObjectType = new Guid(Constants.ObjectTypes.DocumentType); - var sqlSyntaxProvider = new SqlCeSyntaxProvider(); - var expected = new Sql(); - expected.Select("*") + var expected = Sql(); + expected.SelectAll() .From("[cmsDocumentType]") .RightJoin("[cmsContentType]") .On("[cmsContentType].[nodeId] = [cmsDocumentType].[contentTypeNodeId]") @@ -65,16 +64,16 @@ namespace Umbraco.Tests.Persistence.Querying .Where("[cmsDocumentType].[IsDefault] = @0", true) .Where("([umbracoNode].[id] = @0)", 1050); - var sql = new Sql(); - sql.Select("*") - .From(sqlSyntaxProvider) - .RightJoin(sqlSyntaxProvider) - .On(sqlSyntaxProvider, left => left.NodeId, right => right.ContentTypeNodeId) - .InnerJoin(sqlSyntaxProvider) - .On(sqlSyntaxProvider, left => left.NodeId, right => right.NodeId) - .Where(sqlSyntaxProvider, x => x.NodeObjectType == nodeObjectType) - .Where(sqlSyntaxProvider, x => x.IsDefault) - .Where(sqlSyntaxProvider, x => x.NodeId == 1050); + var sql = Sql(); + sql.SelectAll() + .From() + .RightJoin() + .On(left => left.NodeId, right => right.ContentTypeNodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == nodeObjectType) + .Where(x => x.IsDefault) + .Where(x => x.NodeId == 1050); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); @@ -90,20 +89,19 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Can_Verify_PerformQuery_Clause() { - var sqlSyntaxProvider = new SqlCeSyntaxProvider(); - var expected = new Sql(); - expected.Select("*") + var expected = Sql(); + expected.SelectAll() .From("[cmsPropertyTypeGroup]") .RightJoin("[cmsPropertyType]").On("[cmsPropertyTypeGroup].[id] = [cmsPropertyType].[propertyTypeGroupId]") .InnerJoin("[cmsDataType]").On("[cmsPropertyType].[dataTypeId] = [cmsDataType].[nodeId]"); - var sql = new Sql(); - sql.Select("*") - .From(sqlSyntaxProvider) - .RightJoin(sqlSyntaxProvider) - .On(sqlSyntaxProvider, left => left.Id, right => right.PropertyTypeGroupId) - .InnerJoin(sqlSyntaxProvider) - .On(sqlSyntaxProvider, left => left.DataTypeId, right => right.DataTypeId); + var sql = Sql(); + sql.SelectAll() + .From() + .RightJoin() + .On(left => left.Id, right => right.PropertyTypeGroupId) + .InnerJoin() + .On(left => left.DataTypeId, right => right.DataTypeId); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); @@ -113,16 +111,15 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Can_Verify_AllowedContentTypeIds_Clause() { - var expected = new Sql(); - var sqlSyntaxProvider = new SqlCeSyntaxProvider(); - expected.Select("*") + var expected = Sql(); + expected.SelectAll() .From("[cmsContentTypeAllowedContentType]") .Where("([cmsContentTypeAllowedContentType].[Id] = @0)", 1050); - var sql = new Sql(); - sql.Select("*") - .From(sqlSyntaxProvider) - .Where(sqlSyntaxProvider, x => x.Id == 1050); + var sql = Sql(); + sql.SelectAll() + .From() + .Where(x => x.Id == 1050); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); @@ -138,22 +135,21 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Can_Verify_PropertyGroupCollection_Clause() { - var expected = new Sql(); - var sqlSyntaxProvider = new SqlCeSyntaxProvider(); - expected.Select("*") + var expected = Sql(); + expected.SelectAll() .From("[cmsPropertyTypeGroup]") .RightJoin("[cmsPropertyType]").On("[cmsPropertyTypeGroup].[id] = [cmsPropertyType].[propertyTypeGroupId]") .InnerJoin("[cmsDataType]").On("[cmsPropertyType].[dataTypeId] = [cmsDataType].[nodeId]") .Where("([cmsPropertyType].[contentTypeId] = @0)", 1050); - var sql = new Sql(); - sql.Select("*") - .From(sqlSyntaxProvider) - .RightJoin(sqlSyntaxProvider) - .On(sqlSyntaxProvider, left => left.Id, right => right.PropertyTypeGroupId) - .InnerJoin(sqlSyntaxProvider) - .On(sqlSyntaxProvider, left => left.DataTypeId, right => right.DataTypeId) - .Where(sqlSyntaxProvider, x => x.ContentTypeId == 1050); + var sql = Sql(); + sql.SelectAll() + .From() + .RightJoin() + .On(left => left.Id, right => right.PropertyTypeGroupId) + .InnerJoin() + .On(left => left.DataTypeId, right => right.DataTypeId) + .Where(x => x.ContentTypeId == 1050); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs b/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs index 8c299d414b..006d2dc659 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs @@ -3,12 +3,12 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using NPoco; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Relators; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Tests.TestHelpers; diff --git a/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs index 55805282a4..5ba86af69d 100644 --- a/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; @@ -21,12 +22,12 @@ namespace Umbraco.Tests.Persistence.Querying .InnerJoin("[umbracoNode]").On("[cmsDataType].[nodeId] = [umbracoNode].[id]") .Where("([umbracoNode].[nodeObjectType] = @0)", new Guid("30a2a501-1978-4ddb-a57b-f7efed43ba3c")); - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.DataTypeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + var sql = Sql(); + sql.SelectAll() + .From() + .InnerJoin() + .On(left => left.DataTypeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); diff --git a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs index 9100a77eb0..ce8fa1068a 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs @@ -1,11 +1,13 @@ using System; using System.Linq.Expressions; using Moq; +using NPoco; using NUnit.Framework; 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.Mappers; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; @@ -14,7 +16,7 @@ using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Persistence.Querying { [TestFixture] - public class ExpressionTests + public class ExpressionTests : BaseUsingSqlCeSyntax { // [Test] // public void Can_Query_With_Content_Type_Alias() @@ -34,9 +36,8 @@ namespace Umbraco.Tests.Persistence.Querying public void Can_Verify_Path_StartsWith_Predicate_In_Same_Result() { //Arrange - var sqlSyntax = new SqlCeSyntaxProvider(); Expression> predicate = content => content.Path.StartsWith("-1"); - var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(sqlSyntax, new ContentMapper(sqlSyntax)); + var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(SqlContext.SqlSyntax, new ContentMapper(SqlContext.SqlSyntax)); var result = modelToSqlExpressionHelper.Visit(predicate); Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); @@ -49,9 +50,8 @@ namespace Umbraco.Tests.Persistence.Querying public void Can_Verify_ParentId_StartsWith_Predicate_In_Same_Result() { //Arrange - var sqlSyntax = new SqlCeSyntaxProvider(); Expression> predicate = content => content.ParentId == -1; - var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(sqlSyntax, new ContentMapper(sqlSyntax)); + var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(SqlContext.SqlSyntax, new ContentMapper(SqlContext.SqlSyntax)); var result = modelToSqlExpressionHelper.Visit(predicate); Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); @@ -63,9 +63,8 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Equals_Operator_For_Value_Gets_Escaped() { - var sqlSyntax = new SqlCeSyntaxProvider(); Expression> predicate = user => user.Username == "hello@world.com"; - var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(sqlSyntax, new UserMapper(sqlSyntax)); + var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(SqlContext.SqlSyntax, new UserMapper(SqlContext.SqlSyntax)); var result = modelToSqlExpressionHelper.Visit(predicate); Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); @@ -79,7 +78,7 @@ namespace Umbraco.Tests.Persistence.Querying { var sqlSyntax = new SqlCeSyntaxProvider(); Expression> predicate = user => user.Username.Equals("hello@world.com"); - var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(sqlSyntax, new UserMapper(sqlSyntax)); + var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(SqlContext.SqlSyntax, new UserMapper(SqlContext.SqlSyntax)); var result = modelToSqlExpressionHelper.Visit(predicate); Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); @@ -93,8 +92,10 @@ namespace Umbraco.Tests.Persistence.Querying { //mysql escapes backslashes, so we'll test with that var sqlSyntax = new MySqlSyntaxProvider(Mock.Of()); + var sqlContext = new SqlContext(sqlSyntax, SqlContext.PocoDataFactory, DatabaseType.MySQL); + Expression> predicate = user => user.Username.Equals("mydomain\\myuser"); - var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(sqlSyntax, new UserMapper(sqlSyntax)); + var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper(sqlContext.SqlSyntax, new UserMapper(sqlContext.SqlSyntax)); var result = modelToSqlExpressionHelper.Visit(predicate); Console.WriteLine("Model to Sql ExpressionHelper: \n" + result); @@ -109,8 +110,10 @@ namespace Umbraco.Tests.Persistence.Querying { //mysql escapes backslashes, so we'll test with that var sqlSyntax = new MySqlSyntaxProvider(Mock.Of()); + var sqlContext = new SqlContext(sqlSyntax, SqlContext.PocoDataFactory, DatabaseType.MySQL); + Expression> predicate = user => user.Login.StartsWith("mydomain\\myuser"); - var modelToSqlExpressionHelper = new PocoToSqlExpressionHelper(sqlSyntax); + var modelToSqlExpressionHelper = new PocoToSqlExpressionHelper(sqlContext); var result = modelToSqlExpressionHelper.Visit(predicate); Console.WriteLine("Poco to Sql ExpressionHelper: \n" + result); diff --git a/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs index 5d4501eb15..6fcbd6643e 100644 --- a/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/MediaRepositorySqlClausesTest.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; @@ -22,14 +23,14 @@ namespace Umbraco.Tests.Persistence.Querying .InnerJoin("[umbracoNode]").On("[cmsContent].[nodeId] = [umbracoNode].[id]") .Where("([umbracoNode].[nodeObjectType] = @0)", new Guid("b796f64c-1f99-4ffb-b886-4bf4bc011a9c")); - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + var sql = Sql(); + sql.SelectAll() + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); diff --git a/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs b/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs index 6d6ec4ed2c..f1893f6ee5 100644 --- a/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; @@ -21,12 +22,12 @@ namespace Umbraco.Tests.Persistence.Querying .InnerJoin("[umbracoNode]").On("[cmsContentType].[nodeId] = [umbracoNode].[id]") .Where("([umbracoNode].[nodeObjectType] = @0)", new Guid("4ea4382b-2f5a-4c2b-9587-ae9b3cf3602e")); - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + var sql = Sql(); + sql.SelectAll() + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); diff --git a/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs b/src/Umbraco.Tests/Persistence/Querying/NPocoSqlTests.cs similarity index 63% rename from src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs rename to src/Umbraco.Tests/Persistence/Querying/NPocoSqlTests.cs index 705e625d47..de0081be23 100644 --- a/src/Umbraco.Tests/Persistence/Querying/PetaPocoSqlTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/NPocoSqlTests.cs @@ -1,18 +1,15 @@ using System; -using System.Linq; +using NPoco; using NUnit.Framework; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Repositories; using Umbraco.Tests.TestHelpers; using Umbraco.Core.Persistence.Querying; namespace Umbraco.Tests.Persistence.Querying { [TestFixture] - public class PetaPocoSqlTests : BaseUsingSqlCeSyntax + public class NPocoSqlTests : BaseUsingSqlCeSyntax { //x => @@ -20,8 +17,8 @@ namespace Umbraco.Tests.Persistence.Querying public void Where_Clause_With_Starts_With_Additional_Parameters() { var content = new NodeDto() { NodeId = 123, Path = "-1,123" }; - var sql = new Sql("SELECT *").From(SqlSyntax) - .Where(SqlSyntax, x => x.Path.SqlStartsWith(content.Path, TextColumnType.NVarchar)); + var sql = Sql().SelectAll().From() + .Where(x => x.Path.SqlStartsWith(content.Path, TextColumnType.NVarchar)); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[path]) LIKE upper(@0))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(1, sql.Arguments.Length); @@ -32,8 +29,8 @@ namespace Umbraco.Tests.Persistence.Querying public void Where_Clause_With_Starts_With_By_Variable() { var content = new NodeDto() {NodeId = 123, Path = "-1,123"}; - var sql = new Sql("SELECT *").From(SqlSyntax) - .Where(SqlSyntax, x => x.Path.StartsWith(content.Path) && x.NodeId != content.NodeId); + var sql = Sql().SelectAll().From() + .Where(x => x.Path.StartsWith(content.Path) && x.NodeId != content.NodeId); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ((upper([umbracoNode].[path]) LIKE upper(@0) AND ([umbracoNode].[id] <> @1)))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(2, sql.Arguments.Length); @@ -44,9 +41,9 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Where_Clause_With_Not_Starts_With() { - var level = 1; - var sql = new Sql("SELECT *").From(SqlSyntax) - .Where(SqlSyntax, x => x.Level == level && !x.Path.StartsWith("-20")); + const int level = 1; + var sql = Sql().SelectAll().From() + .Where(x => x.Level == level && !x.Path.StartsWith("-20")); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ((([umbracoNode].[level] = @0) AND NOT (upper([umbracoNode].[path]) LIKE upper(@1))))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(2, sql.Arguments.Length); @@ -57,8 +54,9 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Where_Clause_With_EqualsFalse_Starts_With() { - var level = 1; - var sql = new Sql("SELECT *").From(SqlSyntax).Where(SqlSyntax, x => x.Level == level && x.Path.StartsWith("-20") == false); + const int level = 1; + var sql = Sql().SelectAll().From() + .Where(x => x.Level == level && x.Path.StartsWith("-20") == false); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ((([umbracoNode].[level] = @0) AND NOT (upper([umbracoNode].[path]) LIKE upper(@1))))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(2, sql.Arguments.Length); @@ -69,8 +67,8 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Where_Clause_With_Equals_Clause() { - var sql = new Sql("SELECT *").From(SqlSyntax) - .Where(SqlSyntax, x => x.Text.Equals("Hello@world.com")); + var sql = Sql().SelectAll().From() + .Where(x => x.Text.Equals("Hello@world.com")); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[text]) = upper(@0))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(1, sql.Arguments.Length); @@ -80,8 +78,8 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Where_Clause_With_False_Boolean() { - var sql = new Sql("SELECT *").From(SqlSyntax) - .Where(SqlSyntax, x => !x.Trashed); + var sql = Sql().SelectAll().From() + .Where(x => x.Trashed == false); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (NOT ([umbracoNode].[trashed] = @0))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(1, sql.Arguments.Length); @@ -91,7 +89,7 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Where_Clause_With_EqualsFalse_Boolean() { - var sql = new Sql("SELECT *").From(SqlSyntax).Where(SqlSyntax, x => x.Trashed == false); + var sql = Sql().SelectAll().From().Where(x => x.Trashed == false); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (NOT ([umbracoNode].[trashed] = @0))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(1, sql.Arguments.Length); @@ -101,8 +99,8 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Where_Clause_With_Boolean() { - var sql = new Sql("SELECT *").From(SqlSyntax) - .Where(SqlSyntax, x => x.Trashed); + var sql = Sql().SelectAll().From() + .Where(x => x.Trashed); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[trashed] = @0)", sql.SQL.Replace("\n", " ")); Assert.AreEqual(1, sql.Arguments.Length); @@ -112,8 +110,8 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Where_Clause_With_ToUpper() { - var sql = new Sql("SELECT *").From(SqlSyntax) - .Where(SqlSyntax, x => x.Text.ToUpper() == "hello".ToUpper()); + var sql = Sql().SelectAll().From() + .Where(x => x.Text.ToUpper() == "hello".ToUpper()); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ((upper([umbracoNode].[text]) = upper(@0)))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(1, sql.Arguments.Length); @@ -123,8 +121,8 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Where_Clause_With_ToString() { - var sql = new Sql("SELECT *").From(SqlSyntax) - .Where(SqlSyntax, x => x.Text == 1.ToString()); + var sql = Sql().SelectAll().From() + .Where(x => x.Text == 1.ToString()); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (([umbracoNode].[text] = @0))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(1, sql.Arguments.Length); @@ -134,8 +132,8 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Where_Clause_With_Wildcard() { - var sql = new Sql("SELECT *").From(SqlSyntax) - .Where(SqlSyntax, x => x.Text.StartsWith("D")); + var sql = Sql().SelectAll().From() + .Where(x => x.Text.StartsWith("D")); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[text]) LIKE upper(@0))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(1, sql.Arguments.Length); @@ -145,8 +143,8 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Where_Clause_Single_Constant() { - var sql = new Sql("SELECT *").From(SqlSyntax) - .Where(SqlSyntax, x => x.NodeId == 2); + var sql = Sql().SelectAll().From() + .Where(x => x.NodeId == 2); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (([umbracoNode].[id] = @0))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(1, sql.Arguments.Length); @@ -156,8 +154,8 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Where_Clause_And_Constant() { - var sql = new Sql("SELECT *").From(SqlSyntax) - .Where(SqlSyntax, x => x.NodeId != 2 && x.NodeId != 3); + var sql = Sql().SelectAll().From() + .Where(x => x.NodeId != 2 && x.NodeId != 3); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ((([umbracoNode].[id] <> @0) AND ([umbracoNode].[id] <> @1)))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(2, sql.Arguments.Length); @@ -168,8 +166,8 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Where_Clause_Or_Constant() { - var sql = new Sql("SELECT *").From(SqlSyntax) - .Where(SqlSyntax, x => x.Text == "hello" || x.NodeId == 3); + var sql = Sql().SelectAll().From() + .Where(x => x.Text == "hello" || x.NodeId == 3); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ((([umbracoNode].[text] = @0) OR ([umbracoNode].[id] = @1)))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(2, sql.Arguments.Length); @@ -180,11 +178,11 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Can_Select_From_With_Type() { - var expected = new Sql(); - expected.Select("*").From("[cmsContent]"); + var expected = Sql(); + expected.SelectAll().From("[cmsContent]"); - var sql = new Sql(); - sql.Select("*").From(SqlSyntax); + var sql = Sql(); + sql.SelectAll().From(); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); @@ -194,16 +192,16 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Can_InnerJoin_With_Types() { - var expected = new Sql(); - expected.Select("*") + var expected = Sql(); + expected.SelectAll() .From("[cmsDocument]") .InnerJoin("[cmsContentVersion]") .On("[cmsDocument].[versionId] = [cmsContentVersion].[VersionId]"); - var sql = new Sql(); - sql.Select("*").From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.VersionId, right => right.VersionId); + var sql = Sql(); + sql.SelectAll().From() + .InnerJoin() + .On(left => left.VersionId, right => right.VersionId); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); @@ -213,11 +211,11 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Can_OrderBy_With_Type() { - var expected = new Sql(); - expected.Select("*").From("[cmsContent]").OrderBy("([cmsContent].[contentType])"); + var expected = Sql(); + expected.SelectAll().From("[cmsContent]").OrderBy("([cmsContent].[contentType])"); - var sql = new Sql(); - sql.Select("*").From(SqlSyntax).OrderBy(SqlSyntax, x => x.ContentTypeId); + var sql = Sql(); + sql.SelectAll().From().OrderBy(x => x.ContentTypeId); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); @@ -227,11 +225,11 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Can_GroupBy_With_Type() { - var expected = new Sql(); - expected.Select("*").From("[cmsContent]").GroupBy("[contentType]"); + var expected = Sql(); + expected.SelectAll().From("[cmsContent]").GroupBy("[contentType]"); - var sql = new Sql(); - sql.Select("*").From(SqlSyntax).GroupBy(SqlSyntax, x => x.ContentTypeId); + var sql = Sql(); + sql.SelectAll().From().GroupBy(x => x.ContentTypeId); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); @@ -241,11 +239,11 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Can_Use_Where_Predicate() { - var expected = new Sql(); - expected.Select("*").From("[cmsContent]").Where("([cmsContent].[nodeId] = @0)", 1045); + var expected = Sql(); + expected.SelectAll().From("[cmsContent]").Where("([cmsContent].[nodeId] = @0)", 1045); - var sql = new Sql(); - sql.Select("*").From(SqlSyntax).Where(SqlSyntax, x => x.NodeId == 1045); + var sql = Sql(); + sql.SelectAll().From().Where(x => x.NodeId == 1045); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); @@ -255,17 +253,17 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Can_Use_Where_And_Predicate() { - var expected = new Sql(); - expected.Select("*") + var expected = Sql(); + expected.SelectAll() .From("[cmsContent]") .Where("([cmsContent].[nodeId] = @0)", 1045) .Where("([cmsContent].[contentType] = @0)", 1050); - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .Where(SqlSyntax, x => x.NodeId == 1045) - .Where(SqlSyntax, x => x.ContentTypeId == 1050); + var sql = Sql(); + sql.SelectAll() + .From() + .Where(x => x.NodeId == 1045) + .Where(x => x.ContentTypeId == 1050); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); diff --git a/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs b/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs index 62582ace64..9844e6fb26 100644 --- a/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/QueryBuilderTests.cs @@ -1,4 +1,5 @@ using System; +using NPoco; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; @@ -18,11 +19,11 @@ namespace Umbraco.Tests.Persistence.Querying public void Can_Build_StartsWith_Query_For_IContent() { // Arrange - var sql = new Sql(); - sql.Select("*"); + var sql = Sql(); + sql.SelectAll(); sql.From("umbracoNode"); - var query = new Query(SqlSyntax, MappingResolver).Where(x => x.Path.StartsWith("-1")); + var query = new Query(SqlContext.SqlSyntax, MappingResolver).Where(x => x.Path.StartsWith("-1")); // Act var translator = new SqlTranslator(sql, query); @@ -45,11 +46,11 @@ namespace Umbraco.Tests.Persistence.Querying public void Can_Build_ParentId_Query_For_IContent() { // Arrange - var sql = new Sql(); - sql.Select("*"); + var sql = Sql(); + sql.SelectAll(); sql.From("umbracoNode"); - var query = new Query(SqlSyntax, MappingResolver).Where(x => x.ParentId == -1); + var query = new Query(SqlContext.SqlSyntax, MappingResolver).Where(x => x.ParentId == -1); // Act var translator = new SqlTranslator(sql, query); @@ -72,11 +73,11 @@ namespace Umbraco.Tests.Persistence.Querying public void Can_Build_ContentTypeAlias_Query_For_IContentType() { // Arrange - var sql = new Sql(); - sql.Select("*"); + var sql = Sql(); + sql.SelectAll(); sql.From("umbracoNode"); - var query = new Query(SqlSyntax, MappingResolver).Where(x => x.Alias == "umbTextpage"); + var query = new Query(SqlContext.SqlSyntax, MappingResolver).Where(x => x.Alias == "umbTextpage"); // Act var translator = new SqlTranslator(sql, query); @@ -102,18 +103,18 @@ namespace Umbraco.Tests.Persistence.Querying var id = 1046; var nodeObjectTypeId = new Guid(Constants.ObjectTypes.Document); - var sql = new Sql(); - sql.Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.VersionId, right => right.VersionId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == nodeObjectTypeId); + var sql = Sql(); + sql.SelectAll() + .From() + .InnerJoin() + .On(left => left.VersionId, right => right.VersionId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == nodeObjectTypeId); - var query = new Query(SqlSyntax, MappingResolver).Where(x => x.Path.StartsWith(path) && x.Id != id && x.Published == true && x.Trashed == false); + var query = new Query(SqlContext.SqlSyntax, MappingResolver).Where(x => x.Path.StartsWith(path) && x.Id != id && x.Published == true && x.Trashed == false); // Act var translator = new SqlTranslator(sql, query); diff --git a/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs index 29c2960cf8..6be6bd5f6e 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/AuditRepositoryTest.cs @@ -15,7 +15,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Add_Audit_Entry() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repo = new AuditRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, MappingResolver)) { diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index c78abce427..f118bf1b55 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -19,6 +19,7 @@ using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Tests.Persistence.Repositories { @@ -48,17 +49,17 @@ namespace Umbraco.Tests.Persistence.Repositories private ContentRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository, out TemplateRepository templateRepository) { - templateRepository = new TemplateRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of()); - var tagRepository = new TagRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, Mock.Of()); - contentTypeRepository = new ContentTypeRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, templateRepository, Mock.Of()); - var repository = new ContentRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, contentTypeRepository, templateRepository, tagRepository, Mock.Of(), Mock.Of()); + templateRepository = new TemplateRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, Mock.Of(), Mock.Of(), Mock.Of(), MappingResolver); + var tagRepository = new TagRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, MappingResolver); + contentTypeRepository = new ContentTypeRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, templateRepository, MappingResolver); + var repository = new ContentRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, contentTypeRepository, templateRepository, tagRepository, Mock.Of(), MappingResolver); return repository; } [Test] public void Rebuild_Xml_Structures_With_Non_Latest_Version() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -110,7 +111,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Rebuild_All_Xml_Structures() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -157,7 +158,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Rebuild_All_Xml_Structures_For_Content_Type() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -223,7 +224,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Ensures_Permissions_Are_Set_If_Parent_Entity_Permissions_Exist() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; @@ -257,7 +258,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_On_ContentRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -281,7 +282,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_With_Default_Template() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; TemplateRepository templateRepository; @@ -313,7 +314,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Save_Content_With_AtSign_In_Name_On_ContentRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -347,7 +348,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Multiple_Adds_On_ContentRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -378,7 +379,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Verify_Fresh_Entity_Is_Not_Dirty() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -396,7 +397,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_On_ContentRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -419,7 +420,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Update_With_Null_Template() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -441,7 +442,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_On_ContentRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -470,7 +471,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_On_ContentRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -496,7 +497,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetByQuery_On_ContentRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -514,7 +515,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_All_With_Many_Version() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -541,11 +542,25 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void RegexAliasTest() + { + var regex = VersionableRepositoryBaseAliasRegex.For(new SqlServerSyntaxProvider()); + Assert.AreEqual(@"(\[\w+]\.\[\w+])\s+AS\s+(\[\w+])", regex.ToString()); + const string sql = "SELECT [table].[column1] AS [alias1], [table].[column2] AS [alias2] FROM [table];"; + var matches = regex.Matches(sql); + Assert.AreEqual(2, matches.Count); + Assert.AreEqual("[table].[column1]", matches[0].Groups[1].Value); + Assert.AreEqual("[alias1]", matches[0].Groups[2].Value); + Assert.AreEqual("[table].[column2]", matches[1].Groups[1].Value); + Assert.AreEqual("[alias2]", matches[1].Groups[2].Value); + } + [Test] public void Can_Perform_GetPagedResultsByQuery_ForFirstPage_On_ContentRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -566,7 +581,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetPagedResultsByQuery_ForSecondPage_On_ContentRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -587,7 +602,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetPagedResultsByQuery_WithSinglePage_On_ContentRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -608,7 +623,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetPagedResultsByQuery_WithDescendingOrder_On_ContentRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -629,7 +644,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingSome_On_ContentRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -650,7 +665,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingAll_On_ContentRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -671,7 +686,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_By_Param_Ids_On_ContentRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -690,7 +705,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_On_ContentRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -711,7 +726,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Exists_On_ContentRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -730,7 +745,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Count_On_ContentRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -749,7 +764,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Verify_Keys_Set() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) @@ -770,7 +785,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Get_Content_By_Guid_Key() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 2c1404683f..1d27925fc5 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -75,7 +75,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Maps_Templates_Correctly() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var templateRepo = new TemplateRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Logger, SqlSyntax, Mock.Of(), Mock.Of(), Mock.Of(), MappingResolver)) using (var repository = CreateRepository(unitOfWork)) @@ -110,7 +110,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Move() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.DocumentTypeContainerGuid)) using (var repository = CreateRepository(unitOfWork)) @@ -156,7 +156,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Create_Container() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); EntityContainer container; using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.DocumentTypeContainerGuid)) @@ -176,7 +176,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Delete_Container() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); EntityContainer container; using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.DocumentTypeContainerGuid)) @@ -201,7 +201,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Create_Container_Containing_Media_Types() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.MediaTypeContainerGuid)) using (var repository = CreateRepository(unitOfWork)) @@ -222,7 +222,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Delete_Container_Containing_Media_Types() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); EntityContainer container; IMediaType contentType; @@ -258,7 +258,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_On_ContentTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -289,7 +289,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_On_ContentTypeRepository_After_Model_Mapping() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -338,7 +338,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_On_ContentTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -414,7 +414,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_On_ContentTypeRepository_After_Model_Mapping() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -477,7 +477,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_On_ContentTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -501,7 +501,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_With_Heirarchy_On_ContentTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -531,7 +531,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Query_On_ContentTypeRepository_Sort_By_Name() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -560,7 +560,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_On_ContentTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -578,7 +578,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_By_Guid_On_ContentTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -600,7 +600,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_By_Missing_Guid_On_ContentTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -616,7 +616,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_On_ContentTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -638,7 +638,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_By_Guid_On_ContentTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -661,7 +661,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Exists_On_ContentTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -678,7 +678,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Update_ContentType_With_PropertyType_Removed() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -702,7 +702,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Verify_PropertyTypes_On_SimpleTextpage() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -720,7 +720,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Verify_PropertyTypes_On_Textpage() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -738,7 +738,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Verify_PropertyType_With_No_Group() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -772,7 +772,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Verify_AllowedChildContentTypes_On_ContentType() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -806,7 +806,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Verify_Removal_Of_Used_PropertyType_From_ContentType() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository repository; using (var contentRepository = CreateRepository(unitOfWork, out repository)) @@ -833,7 +833,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Verify_Addition_Of_PropertyType_After_ContentType_Is_Used() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository repository; using (var contentRepository = CreateRepository(unitOfWork, out repository)) @@ -861,7 +861,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Verify_Usage_Of_New_PropertyType_On_Content() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository repository; using (var contentRepository = CreateRepository(unitOfWork, out repository)) @@ -897,7 +897,7 @@ namespace Umbraco.Tests.Persistence.Repositories () { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository repository; using (var contentRepository = CreateRepository(unitOfWork, out repository)) diff --git a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs index bc5314abb1..075093c92e 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs @@ -1,12 +1,14 @@ using System; using System.Data; using System.Linq; +using System.Reflection; using System.Text.RegularExpressions; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -32,6 +34,23 @@ namespace Umbraco.Tests.Persistence.Repositories base.Initialize(); } + protected override CacheHelper CreateCacheHelper() + { + // hackish, but it works + var testName = TestContext.CurrentContext.Test.Name; + if (testName == "Can_Get_Pre_Value_As_String_With_Cache" + || testName == "Can_Get_Pre_Value_Collection_With_Cache") + { + return new CacheHelper( + new ObjectCacheRuntimeCacheProvider(), + new StaticCacheProvider(), + new StaticCacheProvider(), + new IsolatedRuntimeCache(type => new ObjectCacheRuntimeCacheProvider())); // default would be NullCacheProvider + } + + return base.CreateCacheHelper(); + } + private IDataTypeDefinitionRepository CreateRepository(IDatabaseUnitOfWork unitOfWork) { return Container.GetInstance(unitOfWork); @@ -39,7 +58,7 @@ namespace Umbraco.Tests.Persistence.Repositories private EntityContainerRepository CreateContainerRepository(IDatabaseUnitOfWork unitOfWork) { - return new EntityContainerRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, Mock.Of(), Constants.ObjectTypes.DataTypeContainerGuid); + return new EntityContainerRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Logger, SqlSyntax, MappingResolver, Constants.ObjectTypes.DataTypeContainerGuid); } [TestCase("UmbracoPreVal87-21,3,48", 3, true)] @@ -62,7 +81,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Move() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var containerRepository = CreateContainerRepository(unitOfWork)) using (var repository = CreateRepository(unitOfWork)) @@ -82,7 +101,7 @@ namespace Umbraco.Tests.Persistence.Repositories repository.AddOrUpdate(dataType); unitOfWork.Commit(); - //create a + //create a var dataType2 = (IDataTypeDefinition)new DataTypeDefinition(dataType.Id, Constants.PropertyEditors.RadioButtonListAlias) { Name = "dt2" @@ -109,14 +128,14 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Create_Container() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); EntityContainer container; using (var containerRepository = CreateContainerRepository(unitOfWork)) { container = new EntityContainer(Constants.ObjectTypes.DataTypeGuid) { Name = "blah" }; containerRepository.AddOrUpdate(container); - unitOfWork.Commit(); + unitOfWork.Commit(); Assert.That(container.Id, Is.GreaterThan(0)); } using (var containerRepository = CreateContainerRepository(unitOfWork)) @@ -129,7 +148,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Delete_Container() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); EntityContainer container; using (var containerRepository = CreateContainerRepository(unitOfWork)) @@ -154,7 +173,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Create_Container_Containing_Data_Types() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var containerRepository = CreateContainerRepository(unitOfWork)) using (var repository = CreateRepository(unitOfWork)) @@ -174,7 +193,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Delete_Container_Containing_Data_Types() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); EntityContainer container; IDataTypeDefinition dataType; @@ -187,7 +206,7 @@ namespace Umbraco.Tests.Persistence.Repositories dataType = new DataTypeDefinition(container.Id, Constants.PropertyEditors.RadioButtonListAlias) { Name = "test" }; repository.AddOrUpdate(dataType); - unitOfWork.Commit(); + unitOfWork.Commit(); } using (var containerRepository = CreateContainerRepository(unitOfWork)) using (var repository = CreateRepository(unitOfWork)) @@ -208,7 +227,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Create() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); int id; using (var repository = CreateRepository(unitOfWork)) @@ -237,7 +256,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_On_DataTypeDefinitionRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -247,16 +266,16 @@ namespace Umbraco.Tests.Persistence.Repositories // Assert Assert.That(dataTypeDefinition, Is.Not.Null); Assert.That(dataTypeDefinition.HasIdentity, Is.True); - Assert.That(dataTypeDefinition.Name, Is.EqualTo("Dropdown")); + Assert.That(dataTypeDefinition.Name, Is.EqualTo("Dropdown")); } - + } [Test] public void Can_Perform_GetAll_On_DataTypeDefinitionRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -276,7 +295,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_With_Params_On_DataTypeDefinitionRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -296,7 +315,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetByQuery_On_DataTypeDefinitionRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -316,7 +335,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Count_On_DataTypeDefinitionRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -334,7 +353,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_On_DataTypeDefinitionRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -361,7 +380,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_On_DataTypeDefinitionRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -395,7 +414,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_On_DataTypeDefinitionRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -427,7 +446,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Exists_On_DataTypeDefinitionRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -445,7 +464,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_Pre_Value_Collection() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); int dtid; @@ -470,7 +489,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_Pre_Value_As_String() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); int dtid; @@ -495,21 +514,15 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_Pre_Value_Collection_With_Cache() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - var cache = new CacheHelper( - new ObjectCacheRuntimeCacheProvider(), - new StaticCacheProvider(), - new StaticCacheProvider(), - new IsolatedRuntimeCache(type => new ObjectCacheRuntimeCacheProvider())); - DataTypeDefinition dtd; using (var repository = Container.GetInstance(unitOfWork)) { dtd = new DataTypeDefinition(-1, Constants.PropertyEditors.RadioButtonListAlias) { Name = "test" }; repository.AddOrUpdate(dtd); - unitOfWork.Commit(); + unitOfWork.Commit(); } DatabaseContext.Database.Insert(new DataTypePreValueDto() { DataTypeNodeId = dtd.Id, SortOrder = 0, Value = "test1" }); @@ -521,7 +534,10 @@ namespace Umbraco.Tests.Persistence.Repositories var collection = repository.GetPreValuesCollectionByDataTypeId(dtd.Id); } - var cached = cache.IsolatedRuntimeCache.GetCache().Result + // note: see CreateCacheHelper, this test uses a special cache + var cache = CacheHelper.IsolatedRuntimeCache.GetCache(); + Assert.IsTrue(cache); + var cached = cache.Result .GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + dtd.Id + "-"); Assert.IsNotNull(cached); @@ -532,15 +548,9 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_Pre_Value_As_String_With_Cache() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - var cache = new CacheHelper( - new ObjectCacheRuntimeCacheProvider(), - new StaticCacheProvider(), - new StaticCacheProvider(), - new IsolatedRuntimeCache(type => new ObjectCacheRuntimeCacheProvider())); - DataTypeDefinition dtd; using (var repository = Container.GetInstance(unitOfWork)) { @@ -558,7 +568,10 @@ namespace Umbraco.Tests.Persistence.Repositories var val = repository.GetPreValueAsString(Convert.ToInt32(id)); } - var cached = cache.IsolatedRuntimeCache.GetCache().Result + // note: see CreateCacheHelper, this test uses a special cache + var cache = CacheHelper.IsolatedRuntimeCache.GetCache(); + Assert.IsTrue(cache); + var cached = cache.Result .GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + dtd.Id + "-"); Assert.IsNotNull(cached); diff --git a/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs index f0c428473f..9febecaef4 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs @@ -36,7 +36,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_By_Key_On_DictionaryRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) @@ -69,7 +69,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_By_UniqueId_On_DictionaryRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) @@ -102,7 +102,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_On_DictionaryRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) @@ -136,7 +136,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_On_DictionaryRepository_When_No_Language_Assigned() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) @@ -163,7 +163,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_On_DictionaryRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -184,7 +184,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_With_Params_On_DictionaryRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -204,7 +204,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetByQuery_On_DictionaryRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -224,7 +224,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Count_On_DictionaryRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -242,7 +242,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_On_DictionaryRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var languageRepository = Container.GetInstance(unitOfWork)) using (var repository = CreateRepository(unitOfWork)) @@ -273,7 +273,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_On_DictionaryRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -300,7 +300,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_WithNewTranslation_On_DictionaryRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); var repository = CreateRepository(unitOfWork); @@ -328,7 +328,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_On_DictionaryRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -349,7 +349,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Exists_On_DictionaryRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { diff --git a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs index 1b00f0089c..c68a1fb76a 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs @@ -30,7 +30,7 @@ namespace Umbraco.Tests.Persistence.Repositories private int CreateTestData(string isoName, out ContentType ct) { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentRepository contentRepo; @@ -54,7 +54,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Create_And_Get_By_Id() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentType ct; @@ -89,7 +89,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Create_And_Get_By_Id_Empty_lang() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentType ct; @@ -122,7 +122,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Cant_Create_Duplicate_Domain_Name() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentType ct; @@ -155,7 +155,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Delete() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentType ct; @@ -190,7 +190,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Update() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentType ct; @@ -242,7 +242,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Exists() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentType ct; @@ -273,7 +273,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Get_By_Name() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentType ct; @@ -304,7 +304,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Get_All() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentType ct; @@ -335,7 +335,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Get_All_Ids() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentType ct; @@ -368,7 +368,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Get_All_Without_Wildcards() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentType ct; @@ -403,7 +403,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Get_All_For_Content() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentType ct; @@ -454,7 +454,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Get_All_For_Content_Without_Wildcards() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentType ct; diff --git a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs index bcad6e0720..9e30c9e71a 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs @@ -37,8 +37,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_On_LanguageRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); + unitOfWork.Database.EnableSqlTrace = true; using (var repository = CreateRepository(unitOfWork)) { // Act @@ -55,7 +56,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Perform_Get_By_Iso_Code_On_LanguageRepository() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -81,7 +82,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Perform_Get_By_Culture_Name_On_LanguageRepository() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -108,7 +109,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Get_WhenIdDoesntExist_ReturnsNull() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -124,7 +125,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_On_LanguageRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -144,7 +145,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_With_Params_On_LanguageRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -164,7 +165,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetByQuery_On_LanguageRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -184,7 +185,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Count_On_LanguageRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -202,7 +203,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_On_LanguageRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -222,7 +223,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_On_LanguageRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -248,7 +249,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_On_LanguageRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -269,7 +270,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Exists_On_LanguageRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { diff --git a/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs index 6ea233aa1a..9e4655baa3 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs @@ -30,7 +30,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Cannot_Add_Duplicate_Macros() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); // Act @@ -48,7 +48,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Cannot_Update_To_Duplicate_Macro_Alias() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); // Act @@ -66,7 +66,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Instantiate_Repository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); // Act @@ -81,7 +81,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_On_Repository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = new MacroRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, MappingResolver)) { @@ -111,7 +111,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_On_Repository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = new MacroRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, MappingResolver)) { @@ -128,7 +128,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetByQuery_On_Repository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = new MacroRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, MappingResolver)) { @@ -145,7 +145,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Count_On_Repository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = new MacroRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, MappingResolver)) { @@ -162,7 +162,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_On_Repository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = new MacroRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, MappingResolver)) { @@ -183,7 +183,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_On_Repository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = new MacroRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, MappingResolver)) { @@ -224,7 +224,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_On_Repository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = new MacroRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, MappingResolver)) { @@ -245,7 +245,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Exists_On_Repository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = new MacroRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, MappingResolver)) { @@ -263,7 +263,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Add_Property_For_Macro() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = new MacroRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, MappingResolver)) { @@ -290,7 +290,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Add_New_Macro_With_Property() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = new MacroRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, MappingResolver)) { @@ -315,7 +315,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Remove_Macro_Property() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = new MacroRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, MappingResolver)) { @@ -340,7 +340,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Add_Remove_Macro_Properties() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = new MacroRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, MappingResolver)) { @@ -371,7 +371,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Update_Property_For_Macro() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = new MacroRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, MappingResolver)) { @@ -399,7 +399,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Update_Macro_Property_Alias() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = new MacroRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, MappingResolver)) { @@ -428,7 +428,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void CreateTestData() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); using (var unitOfWork = provider.GetUnitOfWork()) using (var repository = new MacroRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, MappingResolver)) { diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index 0a53bbae7d..3cbed428f2 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -44,7 +44,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Rebuild_All_Xml_Structures() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -72,7 +72,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Rebuild_All_Xml_Structures_For_Content_Type() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -113,7 +113,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_On_MediaRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -137,7 +137,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Multiple_Adds_On_MediaRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -168,7 +168,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Multiple_Adds_On_MediaRepository_With_RepositoryResolver() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -199,7 +199,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Verify_Fresh_Entity_Is_Not_Dirty() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -218,7 +218,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_On_MediaRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -242,7 +242,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_On_MediaRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -266,7 +266,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_On_MediaRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -293,7 +293,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetByQuery_On_MediaRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -311,7 +311,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetPagedResultsByQuery_ForFirstPage_On_MediaRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -332,7 +332,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetPagedResultsByQuery_ForSecondPage_On_MediaRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -353,7 +353,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetPagedResultsByQuery_WithSinglePage_On_MediaRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -374,7 +374,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetPagedResultsByQuery_WithDescendingOrder_On_MediaRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -395,7 +395,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetPagedResultsByQuery_WitAlternateOrder_On_MediaRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -416,7 +416,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingSome_On_MediaRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -437,7 +437,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingAll_On_MediaRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -458,7 +458,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_By_Param_Ids_On_MediaRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -478,7 +478,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_On_MediaRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -498,7 +498,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Exists_On_MediaRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) @@ -520,7 +520,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Count_On_MediaRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs index fd7c440527..d85ff5a72e 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -40,7 +40,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Move() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var containerRepository = CreateContainerRepository(unitOfWork)) using (var repository = CreateRepository(unitOfWork)) @@ -86,7 +86,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Create_Container() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); EntityContainer container; using (var containerRepository = CreateContainerRepository(unitOfWork)) @@ -106,7 +106,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Delete_Container() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); EntityContainer container; using (var containerRepository = CreateContainerRepository(unitOfWork)) @@ -132,7 +132,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Create_Container_Containing_Media_Types() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var containerRepository = CreateContainerRepository(unitOfWork)) using (var repository = CreateRepository(unitOfWork)) @@ -153,7 +153,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Delete_Container_Containing_Media_Types() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); EntityContainer container; IMediaType contentType; @@ -189,7 +189,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_On_MediaTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -212,7 +212,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_On_MediaTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -249,7 +249,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_On_MediaTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -274,7 +274,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_On_MediaTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -293,7 +293,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_By_Guid_On_MediaTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -313,7 +313,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_On_MediaTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -334,7 +334,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_By_Guid_On_MediaTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -359,7 +359,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Exists_On_MediaTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -376,7 +376,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Update_MediaType_With_PropertyType_Removed() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -403,7 +403,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Verify_PropertyTypes_On_Video_MediaType() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -424,7 +424,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Verify_PropertyTypes_On_File_MediaType() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index c47c46b1be..23796ca3ca 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Xml.Linq; using Moq; +using NPoco; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration.UmbracoSettings; @@ -46,14 +47,14 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Rebuild_All_Xml_Structures() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MemberTypeRepository memberTypeRepository; MemberGroupRepository memberGroupRepository; using (var repository = CreateRepository(unitOfWork, out memberTypeRepository, out memberGroupRepository)) { var memberType1 = CreateTestMemberType(); - + for (var i = 0; i < 100; i++) { var member = MockedMember.CreateSimpleMember(memberType1, "blah" + i, "blah" + i + "@example.com", "blah", "blah" + i); @@ -74,7 +75,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Rebuild_All_Xml_Structures_For_Content_Type() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MemberTypeRepository memberTypeRepository; MemberGroupRepository memberGroupRepository; @@ -102,7 +103,7 @@ namespace Umbraco.Tests.Persistence.Repositories } unitOfWork.Commit(); - //delete all xml + //delete all xml unitOfWork.Database.Execute("DELETE FROM cmsContentXml"); Assert.AreEqual(0, unitOfWork.Database.ExecuteScalar("SELECT COUNT(*) FROM cmsContentXml")); @@ -117,7 +118,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void MemberRepository_Can_Get_Member_By_Id() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MemberTypeRepository memberTypeRepository; MemberGroupRepository memberGroupRepository; @@ -135,7 +136,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_Members_By_Ids() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MemberTypeRepository memberTypeRepository; MemberGroupRepository memberGroupRepository; @@ -157,7 +158,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void MemberRepository_Can_Get_All_Members() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MemberTypeRepository memberTypeRepository; MemberGroupRepository memberGroupRepository; @@ -182,7 +183,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void MemberRepository_Can_Perform_GetByQuery_With_Key() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MemberTypeRepository memberTypeRepository; MemberGroupRepository memberGroupRepository; @@ -204,7 +205,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Persist_Member() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MemberTypeRepository memberTypeRepository; MemberGroupRepository memberGroupRepository; @@ -215,19 +216,19 @@ namespace Umbraco.Tests.Persistence.Repositories var sut = repository.Get(member.Id); Assert.That(sut, Is.Not.Null); - Assert.That(sut.HasIdentity, Is.True); + Assert.That(sut.HasIdentity, Is.True); Assert.That(sut.Properties.Any(x => x.HasIdentity == false || x.Id == 0), Is.False); Assert.That(sut.Name, Is.EqualTo("Johnny Hefty")); Assert.That(sut.Email, Is.EqualTo("johnny@example.com")); Assert.That(sut.RawPasswordValue, Is.EqualTo("123")); - Assert.That(sut.Username, Is.EqualTo("hefty")); + Assert.That(sut.Username, Is.EqualTo("hefty")); } } [Test] public void New_Member_Has_Built_In_Properties_By_Default() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MemberTypeRepository memberTypeRepository; MemberGroupRepository memberGroupRepository; @@ -261,7 +262,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void MemberRepository_Does_Not_Replace_Password_When_Null() { IMember sut; - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MemberTypeRepository memberTypeRepository; MemberGroupRepository memberGroupRepository; @@ -290,7 +291,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void MemberRepository_Can_Update_Email_And_Login_When_Changed() { IMember sut; - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MemberTypeRepository memberTypeRepository; MemberGroupRepository memberGroupRepository; @@ -327,9 +328,9 @@ namespace Umbraco.Tests.Persistence.Repositories var translator = new SqlTranslator(sqlSubquery, query); var subquery = translator.Translate(); var sql = GetBaseQuery(false) - .Append(new Sql("WHERE umbracoNode.id IN (" + subquery.SQL + ")", subquery.Arguments)) - .OrderByDescending(SqlSyntax, x => x.VersionDate) - .OrderBy(SqlSyntax, x => x.SortOrder); + .Append("WHERE umbracoNode.id IN (" + subquery.SQL + ")", subquery.Arguments) + .OrderByDescending(x => x.VersionDate) + .OrderBy(x => x.SortOrder); Console.WriteLine(sql.SQL); Assert.That(sql.SQL, Is.Not.Empty); @@ -337,7 +338,7 @@ namespace Umbraco.Tests.Persistence.Repositories private IMember CreateTestMember(IMemberType memberType = null, string name = null, string email = null, string password = null, string username = null, Guid? key = null) { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MemberTypeRepository memberTypeRepository; MemberGroupRepository memberGroupRepository; @@ -347,7 +348,7 @@ namespace Umbraco.Tests.Persistence.Repositories { memberType = MockedContentTypes.CreateSimpleMemberType(); memberTypeRepository.AddOrUpdate(memberType); - unitOfWork.Commit(); + unitOfWork.Commit(); } var member = MockedMember.CreateSimpleMember(memberType, name ?? "Johnny Hefty", email ?? "johnny@example.com", password ?? "123", username ?? "hefty", key); @@ -360,7 +361,7 @@ namespace Umbraco.Tests.Persistence.Repositories private IMemberType CreateTestMemberType(string alias = null) { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MemberTypeRepository memberTypeRepository; MemberGroupRepository memberGroupRepository; @@ -373,22 +374,22 @@ namespace Umbraco.Tests.Persistence.Repositories } } - private Sql GetBaseQuery(bool isCount) + private Sql GetBaseQuery(bool isCount) { if (isCount) { - var sqlCount = new Sql() - .Select("COUNT(*)") - .From(SqlSyntax) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.ContentTypeId) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + var sqlCount = NPoco.Sql.BuilderFor(new SqlContext(SqlSyntax, DatabaseContext.Database)) + .SelectCount() + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == NodeObjectTypeId); return sqlCount; } - var sql = new Sql(); + var sql = NPoco.Sql.BuilderFor(new SqlContext(SqlSyntax, DatabaseContext.Database)); sql.Select("umbracoNode.*", "cmsContent.contentType", "cmsContentType.alias AS ContentTypeAlias", "cmsContentVersion.VersionId", "cmsContentVersion.VersionDate", "cmsMember.Email", "cmsMember.LoginName", "cmsMember.Password", "cmsPropertyData.id AS PropertyDataId", "cmsPropertyData.propertytypeid", @@ -397,33 +398,33 @@ namespace Umbraco.Tests.Persistence.Repositories "cmsPropertyType.Name", "cmsPropertyType.mandatory", "cmsPropertyType.validationRegExp", "cmsPropertyType.sortOrder AS PropertyTypeSortOrder", "cmsPropertyType.propertyTypeGroupId", "cmsPropertyType.dataTypeId", "cmsDataType.propertyEditorAlias", "cmsDataType.dbType") - .From(SqlSyntax) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.ContentTypeId) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.ContentTypeId, right => right.ContentTypeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.DataTypeId, right => right.DataTypeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.PropertyTypeId, right => right.Id) + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .LeftJoin().On(left => left.ContentTypeId, right => right.ContentTypeId) + .LeftJoin().On(left => left.DataTypeId, right => right.DataTypeId) + .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) .Append("AND cmsPropertyData.versionId = cmsContentVersion.VersionId") - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; } - private Sql GetSubquery() + private Sql GetSubquery() { - var sql = new Sql(); + var sql = NPoco.Sql.BuilderFor(new SqlContext(SqlSyntax, DatabaseContext.Database)); sql.Select("umbracoNode.id") - .From(SqlSyntax) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.ContentTypeId) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.ContentTypeId, right => right.ContentTypeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.DataTypeId, right => right.DataTypeId) - .LeftJoin(SqlSyntax).On(SqlSyntax, left => left.PropertyTypeId, right => right.Id) + .From() + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.ContentTypeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .InnerJoin().On(left => left.NodeId, right => right.NodeId) + .LeftJoin().On(left => left.ContentTypeId, right => right.ContentTypeId) + .LeftJoin().On(left => left.DataTypeId, right => right.DataTypeId) + .LeftJoin().On(left => left.PropertyTypeId, right => right.Id) .Append("AND cmsPropertyData.versionId = cmsContentVersion.VersionId") - .Where(SqlSyntax, x => x.NodeObjectType == NodeObjectTypeId); + .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs index b045aac137..9f9ff91842 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -38,7 +38,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Persist_Member_Type() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -62,7 +62,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Cannot_Persist_Member_Type_Without_Alias() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -77,7 +77,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_All_Member_Types() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -101,7 +101,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_All_Member_Types_By_Guid_Ids() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -125,7 +125,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_Member_Types_By_Guid_Id() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -152,7 +152,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_All_Members_When_No_Properties_Assigned() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -179,7 +179,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_Member_Type_By_Id() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -194,7 +194,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_Member_Type_By_Guid_Id() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -209,7 +209,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Built_In_Member_Type_Properties_Are_Automatically_Added_When_Creating() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -229,7 +229,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Built_In_Member_Type_Properties_Are_Not_Reused_For_Different_Member_Types() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -250,7 +250,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Delete_MemberType() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { diff --git a/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs index 4d28e909ca..071a419477 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs @@ -20,7 +20,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void CreateNotification() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); var repo = new NotificationsRepository(unitOfWork, SqlSyntax); @@ -40,7 +40,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void GetUserNotifications() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); var repo = new NotificationsRepository(unitOfWork, SqlSyntax); @@ -66,7 +66,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void GetEntityNotifications() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); var repo = new NotificationsRepository(unitOfWork, SqlSyntax); @@ -93,7 +93,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Delete_By_Entity() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); var repo = new NotificationsRepository(unitOfWork, SqlSyntax); @@ -120,7 +120,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Delete_By_User() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); var repo = new NotificationsRepository(unitOfWork, SqlSyntax); diff --git a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs index db3dd297ff..1115751688 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs @@ -25,7 +25,7 @@ namespace Umbraco.Tests.Persistence.Repositories { var content = CreateTestData(3).ToArray(); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repo = new PublicAccessRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, MappingResolver)) { @@ -54,8 +54,9 @@ namespace Umbraco.Tests.Persistence.Repositories { var content = CreateTestData(3).ToArray(); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); + unitOfWork.Database.EnableSqlTrace = true; using (var repo = new PublicAccessRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, MappingResolver)) { var entry = new PublicAccessEntry(content[0], content[1], content[2], new[] @@ -87,12 +88,57 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Can_Add2() + { + var content = CreateTestData(3).ToArray(); + + var provider = new NPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + unitOfWork.Database.EnableSqlTrace = true; + using (var repo = new PublicAccessRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, MappingResolver)) + { + var entry = new PublicAccessEntry(content[0], content[1], content[2], new[] + { + new PublicAccessRule + { + RuleValue = "test", + RuleType = "RoleName" + }, + new PublicAccessRule + { + RuleValue = "test2", + RuleType = "RoleName2" + }, + }); + repo.AddOrUpdate(entry); + unitOfWork.Commit(); + + var found = repo.GetAll().ToArray(); + + Assert.AreEqual(1, found.Length); + Assert.AreEqual(content[0].Id, found[0].ProtectedNodeId); + Assert.AreEqual(content[1].Id, found[0].LoginNodeId); + Assert.AreEqual(content[2].Id, found[0].NoAccessNodeId); + Assert.IsTrue(found[0].HasIdentity); + Assert.AreNotEqual(default(DateTime), found[0].CreateDate); + Assert.AreNotEqual(default(DateTime), found[0].UpdateDate); + Assert.AreEqual(2, found[0].Rules.Count()); + Assert.AreEqual("test", found[0].Rules.First().RuleValue); + Assert.AreEqual("RoleName", found[0].Rules.First().RuleType); + Assert.AreNotEqual(default(DateTime), found[0].Rules.First().CreateDate); + Assert.AreNotEqual(default(DateTime), found[0].Rules.First().UpdateDate); + Assert.IsTrue(found[0].Rules.First().HasIdentity); + Assert.AreEqual("test2", found[0].Rules.Skip(1).First().RuleValue); + } + } + [Test] public void Can_Update() { var content = CreateTestData(3).ToArray(); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repo = new PublicAccessRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, MappingResolver)) { @@ -129,7 +175,7 @@ namespace Umbraco.Tests.Persistence.Repositories { var content = CreateTestData(3).ToArray(); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repo = new PublicAccessRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, MappingResolver)) { @@ -156,7 +202,7 @@ namespace Umbraco.Tests.Persistence.Repositories { var content = CreateTestData(3).ToArray(); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repo = new PublicAccessRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, MappingResolver)) { @@ -193,7 +239,7 @@ namespace Umbraco.Tests.Persistence.Repositories { var content = CreateTestData(3).ToArray(); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repo = new PublicAccessRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, MappingResolver)) { @@ -236,7 +282,7 @@ namespace Umbraco.Tests.Persistence.Repositories private IEnumerable CreateTestData(int count) { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository ctRepo; using (var repo = CreateRepository(unitOfWork, out ctRepo)) diff --git a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs index 49f53bef1f..6ed8062a33 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RelationRepositoryTest.cs @@ -39,7 +39,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_On_RelationRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); RelationTypeRepository repositoryType; using (var repository = CreateRepository(unitOfWork, out repositoryType)) @@ -61,7 +61,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_On_RelationRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); RelationTypeRepository repositoryType; using (var repository = CreateRepository(unitOfWork, out repositoryType)) @@ -86,7 +86,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_On_RelationRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); RelationTypeRepository repositoryType; using (var repository = CreateRepository(unitOfWork, out repositoryType)) @@ -108,7 +108,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_On_RelationRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); RelationTypeRepository repositoryType; using (var repository = CreateRepository(unitOfWork, out repositoryType)) @@ -130,7 +130,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_On_RelationRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); RelationTypeRepository repositoryType; using (var repository = CreateRepository(unitOfWork, out repositoryType)) @@ -151,7 +151,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_With_Params_On_RelationRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); RelationTypeRepository repositoryType; using (var repository = CreateRepository(unitOfWork, out repositoryType)) @@ -172,7 +172,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Exists_On_RelationRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); RelationTypeRepository repositoryType; using (var repository = CreateRepository(unitOfWork, out repositoryType)) @@ -192,7 +192,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Count_On_RelationRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); RelationTypeRepository repositoryType; using (var repository = CreateRepository(unitOfWork, out repositoryType)) @@ -211,7 +211,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetByQuery_On_RelationRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); RelationTypeRepository repositoryType; using (var repository = CreateRepository(unitOfWork, out repositoryType)) @@ -233,7 +233,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Delete_Content_And_Verify_Relation_Is_Removed() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); RelationTypeRepository repositoryType; using (var repository = CreateRepository(unitOfWork, out repositoryType)) @@ -263,7 +263,7 @@ namespace Umbraco.Tests.Persistence.Repositories var relateContent = new RelationType(new Guid(Constants.ObjectTypes.Document), new Guid("C66BA18E-EAF3-4CFF-8A22-41B16D66A972"), "relateContentOnCopy") { IsBidirectional = true, Name = "Relate Content on Copy" }; var relateContentType = new RelationType(new Guid(Constants.ObjectTypes.DocumentType), new Guid("A2CB7800-F571-4787-9638-BC48539A0EFB"), "relateContentTypeOnCopy") { IsBidirectional = true, Name = "Relate ContentType on Copy" }; - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); var relationTypeRepository = new RelationTypeRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, MappingResolver); var relationRepository = new RelationRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, relationTypeRepository, MappingResolver); diff --git a/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs index cd4263df03..34817131f1 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/RelationTypeRepositoryTest.cs @@ -37,7 +37,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_On_RelationTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -60,7 +60,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_On_RelationTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -86,7 +86,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_On_RelationTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -107,7 +107,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_On_RelationTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -127,7 +127,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_On_RelationTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -147,7 +147,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_With_Params_On_RelationTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -167,7 +167,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Exists_On_RelationTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -186,7 +186,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Count_On_RelationTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -204,7 +204,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetByQuery_On_RelationTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -233,7 +233,7 @@ namespace Umbraco.Tests.Persistence.Repositories var relateContent = new RelationType(new Guid(Constants.ObjectTypes.Document), new Guid("C66BA18E-EAF3-4CFF-8A22-41B16D66A972"), "relateContentOnCopy") { IsBidirectional = true, Name = "Relate Content on Copy" }; var relateContentType = new RelationType(new Guid(Constants.ObjectTypes.DocumentType), new Guid("A2CB7800-F571-4787-9638-BC48539A0EFB"), "relateContentTypeOnCopy") { IsBidirectional = true, Name = "Relate ContentType on Copy" }; - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); var repository = new RelationTypeRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, MappingResolver); diff --git a/src/Umbraco.Tests/Persistence/Repositories/ServerRegistrationRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ServerRegistrationRepositoryTest.cs index cf5b0e9a3e..8ddb8d45f1 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ServerRegistrationRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ServerRegistrationRepositoryTest.cs @@ -39,7 +39,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Cannot_Add_Duplicate_Server_Identities() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); // Act @@ -57,7 +57,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Cannot_Update_To_Duplicate_Server_Identities() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); // Act @@ -75,14 +75,14 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Instantiate_Repository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); // Act using (var repository = CreateRepository(unitOfWork)) { // Assert - Assert.That(repository, Is.Not.Null); + Assert.That(repository, Is.Not.Null); } } @@ -90,7 +90,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_On_Repository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -100,17 +100,17 @@ namespace Umbraco.Tests.Persistence.Repositories // Assert Assert.That(server, Is.Not.Null); Assert.That(server.HasIdentity, Is.True); - Assert.That(server.ServerAddress, Is.EqualTo("http://localhost")); + Assert.That(server.ServerAddress, Is.EqualTo("http://localhost")); } - + } [Test] public void Can_Perform_GetAll_On_Repository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -118,9 +118,9 @@ namespace Umbraco.Tests.Persistence.Repositories var servers = repository.GetAll(); // Assert - Assert.That(servers.Count(), Is.EqualTo(3)); + Assert.That(servers.Count(), Is.EqualTo(3)); } - + } // queries are not supported due to in-memory caching @@ -129,7 +129,7 @@ namespace Umbraco.Tests.Persistence.Repositories //public void Can_Perform_GetByQuery_On_Repository() //{ // // Arrange - // var provider = new PetaPocoUnitOfWorkProvider(Logger); + // var provider = new NPocoUnitOfWorkProvider(Logger); // var unitOfWork = provider.GetUnitOfWork(); // using (var repository = CreateRepository(unitOfWork)) // { @@ -138,7 +138,7 @@ namespace Umbraco.Tests.Persistence.Repositories // var result = repository.GetByQuery(query); // // Assert - // Assert.AreEqual(1, result.Count()); + // Assert.AreEqual(1, result.Count()); // } //} @@ -146,7 +146,7 @@ namespace Umbraco.Tests.Persistence.Repositories //public void Can_Perform_Count_On_Repository() //{ // // Arrange - // var provider = new PetaPocoUnitOfWorkProvider(Logger); + // var provider = new NPocoUnitOfWorkProvider(Logger); // var unitOfWork = provider.GetUnitOfWork(); // using (var repository = CreateRepository(unitOfWork)) // { @@ -155,7 +155,7 @@ namespace Umbraco.Tests.Persistence.Repositories // int count = repository.Count(query); // // Assert - // Assert.That(count, Is.EqualTo(2)); + // Assert.That(count, Is.EqualTo(2)); // } //} @@ -163,7 +163,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_On_Repository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -174,15 +174,15 @@ namespace Umbraco.Tests.Persistence.Repositories // Assert Assert.That(server.HasIdentity, Is.True); - Assert.That(server.Id, Is.EqualTo(4));//With 3 existing entries the Id should be 4 - } + Assert.That(server.Id, Is.EqualTo(4));//With 3 existing entries the Id should be 4 + } } [Test] public void Can_Perform_Update_On_Repository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -199,15 +199,15 @@ namespace Umbraco.Tests.Persistence.Repositories // Assert Assert.That(serverUpdated, Is.Not.Null); Assert.That(serverUpdated.ServerAddress, Is.EqualTo("https://umbraco.com")); - Assert.That(serverUpdated.IsActive, Is.EqualTo(true)); - } + Assert.That(serverUpdated.IsActive, Is.EqualTo(true)); + } } [Test] public void Can_Perform_Delete_On_Repository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -220,15 +220,15 @@ namespace Umbraco.Tests.Persistence.Repositories var exists = repository.Exists(3); // Assert - Assert.That(exists, Is.False); - } + Assert.That(exists, Is.False); + } } [Test] public void Can_Perform_Exists_On_Repository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -238,7 +238,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Assert Assert.That(exists, Is.True); - Assert.That(doesntExist, Is.False); + Assert.That(doesntExist, Is.False); } } @@ -250,7 +250,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void CreateTestData() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); using (var unitOfWork = provider.GetUnitOfWork()) using (var repository = CreateRepository(unitOfWork)) { diff --git a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs index a71191236b..011bf49a21 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs @@ -43,7 +43,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_On_Repository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -66,7 +66,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Multiple_Adds_On_Repository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -99,7 +99,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Create_Tag_Relations() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepository)) @@ -131,7 +131,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Append_Tag_Relations() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepository)) @@ -172,7 +172,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Replace_Tag_Relations() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepository)) @@ -216,7 +216,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Merge_Tag_Relations() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepository)) @@ -258,7 +258,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Clear_Tag_Relations() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepository)) @@ -296,7 +296,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Remove_Specific_Tags_From_Property() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepository)) @@ -342,7 +342,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_Tags_For_Content_By_Id() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepository)) @@ -389,7 +389,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_Tags_For_Content_By_Key() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepository)) @@ -436,7 +436,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_All() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepository)) @@ -473,7 +473,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_All_With_Ids() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepository)) @@ -515,7 +515,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_Tags_For_Content_For_Group() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepository)) @@ -561,7 +561,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_Tags_For_Property_By_Id() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepository)) @@ -608,7 +608,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_Tags_For_Property_By_Key() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepository)) @@ -655,7 +655,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_Tags_For_Property_For_Group() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepository)) @@ -704,7 +704,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_Tags_For_Entity_Type() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; ContentTypeRepository contentTypeRepository; @@ -766,7 +766,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_Tags_For_Entity_Type_For_Group() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; ContentTypeRepository contentTypeRepository; @@ -823,7 +823,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Cascade_Deletes_Tag_Relations() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); ContentTypeRepository contentTypeRepository; using (var contentRepository = CreateContentRepository(unitOfWork, out contentTypeRepository)) @@ -864,7 +864,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_Tagged_Entities_For_Tag_Group() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; ContentTypeRepository contentTypeRepository; @@ -951,7 +951,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Get_Tagged_Entities_For_Tag() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); MediaTypeRepository mediaTypeRepository; ContentTypeRepository contentTypeRepository; diff --git a/src/Umbraco.Tests/Persistence/Repositories/TaskRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TaskRepositoryTest.cs index 87138d15bf..33c961fcab 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TaskRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TaskRepositoryTest.cs @@ -16,7 +16,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Delete() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repo = new TaskRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, MappingResolver)) { @@ -43,7 +43,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Add() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repo = new TaskRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, MappingResolver)) { @@ -75,7 +75,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Update() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repo = new TaskRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, MappingResolver)) { @@ -111,7 +111,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Get_By_Id() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repo = new TaskRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, MappingResolver)) { @@ -139,7 +139,7 @@ namespace Umbraco.Tests.Persistence.Repositories { CreateTestData(false, 20); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repo = new TaskRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, MappingResolver)) { @@ -154,7 +154,7 @@ namespace Umbraco.Tests.Persistence.Repositories CreateTestData(false, 10); CreateTestData(true, 5); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repo = new TaskRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, MappingResolver)) { @@ -169,7 +169,7 @@ namespace Umbraco.Tests.Persistence.Repositories CreateTestData(false, 10, -20); CreateTestData(false, 5, -21); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repo = new TaskRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, MappingResolver)) { @@ -184,7 +184,7 @@ namespace Umbraco.Tests.Persistence.Repositories CreateTestData(false, 10); CreateTestData(true, 5); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repo = new TaskRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, MappingResolver)) { @@ -195,7 +195,7 @@ namespace Umbraco.Tests.Persistence.Repositories private void CreateTestData(bool closed, int count, int entityId = -1) { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repo = new TaskRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, MappingResolver)) { diff --git a/src/Umbraco.Tests/Persistence/Repositories/TaskTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TaskTypeRepositoryTest.cs index bceb8d54d7..23f2957f16 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TaskTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TaskTypeRepositoryTest.cs @@ -15,7 +15,7 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void Can_Delete() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); var taskType = new TaskType("asdfasdf"); using (var repo = new TaskRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, MappingResolver)) diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index 931425ae7f..9b726e2184 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -47,7 +47,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Instantiate_Repository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); // Act @@ -64,7 +64,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_MasterPage_Detect_Content() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -87,7 +87,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_MasterPage_With_Default_Content() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork, Mock.Of(x => x.DefaultRenderingEngine == RenderingEngine.WebForms))) { @@ -113,7 +113,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_MasterPage_With_Default_Content_With_Parent() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork, Mock.Of(x => x.DefaultRenderingEngine == RenderingEngine.WebForms))) { @@ -142,7 +142,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_View() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -162,7 +162,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_View_With_Default_Content() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -188,7 +188,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_View_With_Default_Content_With_Parent() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -216,7 +216,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_Unique_Alias() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -245,7 +245,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_Unique_Alias() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -280,7 +280,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_MasterPage() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -310,7 +310,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_View() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -340,7 +340,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_MasterPage() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -369,7 +369,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_View() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -398,7 +398,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_When_Assigned_To_Doc() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var templateRepository = CreateRepository(unitOfWork)) @@ -443,7 +443,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_On_Nested_Templates() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -484,7 +484,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Get_Template_Tree() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -510,7 +510,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Get_All() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -537,7 +537,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Get_Children() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -561,7 +561,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Get_Children_At_Root() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -582,7 +582,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Get_Descendants() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -606,7 +606,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Path_Is_Set_Correctly_On_Creation() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -670,7 +670,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Path_Is_Set_Correctly_On_Update() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index 4569e63959..8d0bbdff77 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -44,7 +44,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_On_UserRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); UserTypeRepository userTypeRepository; using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) @@ -65,7 +65,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Multiple_Adds_On_UserRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); UserTypeRepository userTypeRepository; using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) @@ -90,7 +90,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Verify_Fresh_Entity_Is_Not_Dirty() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); UserTypeRepository userTypeRepository; using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) @@ -112,7 +112,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_On_UserRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); UserTypeRepository userTypeRepository; using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) @@ -162,7 +162,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_On_UserRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); UserTypeRepository userTypeRepository; using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) @@ -193,7 +193,7 @@ namespace Umbraco.Tests.Persistence.Repositories //public void Can_Perform_Delete_On_UserRepository_With_Permissions_Assigned() //{ // // Arrange - // var provider = new PetaPocoUnitOfWorkProvider(Logger); + // var provider = new NPocoUnitOfWorkProvider(Logger); // var unitOfWork = provider.GetUnitOfWork(); // UserTypeRepository userTypeRepository; //using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) @@ -223,7 +223,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_On_UserRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); UserTypeRepository userTypeRepository; using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) @@ -244,7 +244,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetByQuery_On_UserRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); UserTypeRepository userTypeRepository; using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) @@ -264,7 +264,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_By_Param_Ids_On_UserRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); UserTypeRepository userTypeRepository; using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) @@ -285,7 +285,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_On_UserRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); UserTypeRepository userTypeRepository; using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) @@ -306,7 +306,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Exists_On_UserRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); UserTypeRepository userTypeRepository; using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) @@ -325,7 +325,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Count_On_UserRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); UserTypeRepository userTypeRepository; using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) @@ -345,7 +345,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Remove_Section_For_User() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); UserTypeRepository userTypeRepository; using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) @@ -380,7 +380,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Add_Section_For_User() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); UserTypeRepository userTypeRepository; using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) @@ -424,7 +424,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Update_Section_For_User() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); UserTypeRepository userTypeRepository; using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) @@ -452,7 +452,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Get_Users_Assigned_To_Section() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); UserTypeRepository userTypeRepository; using (var repository = CreateRepository(unitOfWork, out userTypeRepository)) @@ -469,7 +469,7 @@ namespace Umbraco.Tests.Persistence.Repositories var users = repository.GetUsersAssignedToSection("test"); - // Assert + // Assert Assert.AreEqual(2, users.Count()); var names = users.Select(x => x.Username).ToArray(); Assert.IsTrue(names.Contains("TestUser1")); @@ -481,11 +481,11 @@ namespace Umbraco.Tests.Persistence.Repositories public void Default_User_Permissions_Based_On_User_Type() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var utRepo = new UserTypeRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Logger, SqlSyntax, MappingResolver)) using (var repository = new UserRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Logger, SqlSyntax, utRepo, MappingResolver)) - { + { // Act var user1 = MockedUser.CreateUser(CreateAndCommitUserType(), "1", "test", "media"); @@ -532,7 +532,7 @@ namespace Umbraco.Tests.Persistence.Repositories private IUserType CreateAndCommitUserType() { - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = new UserTypeRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Logger, SqlSyntax, MappingResolver)) { diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserTypeRepositoryTest.cs index d3bc64ba4f..012d5c4f0b 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserTypeRepositoryTest.cs @@ -39,7 +39,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Add_On_UserTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -59,7 +59,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Multiple_Adds_On_UserTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -83,7 +83,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Verify_Fresh_Entity_Is_Not_Dirty() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -104,7 +104,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Update_On_UserTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -131,7 +131,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Delete_On_UserTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -161,7 +161,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Get_On_UserTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -186,7 +186,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetByQuery_On_UserTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -205,7 +205,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_By_Param_Ids_On_UserTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -225,7 +225,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_GetAll_On_UserTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -245,7 +245,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Exists_On_UserTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { @@ -263,7 +263,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Perform_Count_On_UserTypeRepository() { // Arrange - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var repository = CreateRepository(unitOfWork)) { diff --git a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs b/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs index e637e98183..7b9a67a607 100644 --- a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs +++ b/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs @@ -1,6 +1,7 @@ using System; using System.IO; using Moq; +using NPoco; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; @@ -49,7 +50,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_umbracoNode_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -60,7 +61,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_umbracoAccess_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -72,7 +73,7 @@ namespace Umbraco.Tests.Persistence [Test] public void Can_Create_umbracoAccessRule_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -86,7 +87,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsContentType2ContentType_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -99,7 +100,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsContentTypeAllowedContentType_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -113,7 +114,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsContentType_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -126,7 +127,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsContentVersion_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -141,7 +142,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsContentXml_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -156,7 +157,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsDataType_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -169,7 +170,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsDataTypePreValues_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -183,7 +184,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsDictionary_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -195,7 +196,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsLanguageText_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -209,7 +210,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsTemplate_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -222,7 +223,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsDocument_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -238,7 +239,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsDocumentType_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -253,7 +254,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_umbracoDomains_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -266,7 +267,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_umbracoLanguage_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -278,7 +279,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_umbracoLog_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -290,7 +291,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsMacro_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -302,7 +303,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsMember_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -317,7 +318,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsMember2MemberGroup_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -333,7 +334,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsMemberType_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -347,7 +348,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsPreviewXml_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -363,7 +364,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsPropertyData_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -380,7 +381,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsPropertyType_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -396,7 +397,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsPropertyTypeGroup_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -410,7 +411,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_umbracoRelation_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -424,7 +425,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_umbracoRelationType_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -436,7 +437,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsStylesheet_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -449,7 +450,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsStylesheetProperty_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -462,7 +463,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsTags_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -474,7 +475,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsTagRelationship_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -494,7 +495,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsTask_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -510,7 +511,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_cmsTaskType_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -522,7 +523,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_umbracoDeployDependency_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -535,7 +536,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_umbracoDeployChecksum_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -547,7 +548,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_umbracoUser_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -560,7 +561,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_umbracoUserType_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); @@ -572,7 +573,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_umbracoUser2app_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -586,7 +587,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_umbracoUser2NodeNotify_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); @@ -601,7 +602,7 @@ namespace Umbraco.Tests.Persistence public void Can_Create_umbracoUser2NodePermission_Table() { - using (Transaction transaction = DatabaseContext.Database.GetTransaction()) + using (var transaction = DatabaseContext.Database.GetTransaction()) { DatabaseSchemaHelper.CreateTable(); DatabaseSchemaHelper.CreateTable(); diff --git a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs index b5ae4a5ed5..7951df2dc6 100644 --- a/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs +++ b/src/Umbraco.Tests/Persistence/SyntaxProvider/SqlCeSyntaxProviderTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using NPoco; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; @@ -20,17 +21,15 @@ namespace Umbraco.Tests.Persistence.SyntaxProvider [Test] public void Can_Generate_Delete_SubQuery_Statement() { - var sqlSyntax = new SqlCeSyntaxProvider(); - var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media); - var subQuery = new Sql() + var subQuery = Sql() .Select("DISTINCT cmsContentXml.nodeId") - .From(sqlSyntax) - .InnerJoin(sqlSyntax) - .On(sqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(sqlSyntax, dto => dto.NodeObjectType == mediaObjectType); + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeObjectType == mediaObjectType); - var sqlOutput = sqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); + var sqlOutput = SqlContext.SqlSyntax.GetDeleteSubquery("cmsContentXml", "nodeId", subQuery); Assert.AreEqual(@"DELETE FROM [cmsContentXml] WHERE [nodeId] IN (SELECT [nodeId] FROM (SELECT DISTINCT cmsContentXml.nodeId FROM [cmsContentXml] @@ -47,15 +46,13 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine, [Test] public void Can_Generate_Create_Table_Statement() { - var sqlSyntax = new SqlCeSyntaxProvider(); - var type = typeof (NodeDto); - var definition = DefinitionFactory.GetTableDefinition(type, sqlSyntax); + var definition = DefinitionFactory.GetTableDefinition(type, SqlContext.SqlSyntax); - string create = sqlSyntax.Format(definition); - string primaryKey = sqlSyntax.FormatPrimaryKey(definition); - var indexes = sqlSyntax.Format(definition.Indexes); - var keys = sqlSyntax.Format(definition.ForeignKeys); + string create = SqlContext.SqlSyntax.Format(definition); + string primaryKey = SqlContext.SqlSyntax.FormatPrimaryKey(definition); + var indexes = SqlContext.SqlSyntax.Format(definition.Indexes); + var keys = SqlContext.SqlSyntax.Format(definition.ForeignKeys); Console.WriteLine(create); Console.WriteLine(primaryKey); @@ -142,7 +139,6 @@ WHERE (([umbracoNode].[nodeObjectType] = @0))) x)".Replace(Environment.NewLine, TableName = "TheTable", SchemaName = "dbo" }; - } - + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index 3523b61943..19b52c55a8 100644 --- a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs +++ b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs @@ -145,7 +145,7 @@ namespace Umbraco.Tests.Services var pages = MockedContent.CreateTextpageContent(contentType, -1, 100); ServiceContext.ContentService.Save(pages, 0); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var tRepository = new TemplateRepository(unitOfWork, DisabledCache, Logger, SqlSyntax, Mock.Of(), Mock.Of(), Mock.Of(), MappingResolver)) @@ -177,7 +177,7 @@ namespace Umbraco.Tests.Services var pages = MockedContent.CreateTextpageContent(contentType, -1, 1000); ServiceContext.ContentService.Save(pages, 0); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var tRepository = new TemplateRepository(unitOfWork, DisabledCache, Logger, SqlSyntax, Mock.Of(), Mock.Of(), Mock.Of(), MappingResolver)) using (var tagRepo = new TagRepository(unitOfWork, DisabledCache, Logger, SqlSyntax, MappingResolver)) @@ -206,7 +206,7 @@ namespace Umbraco.Tests.Services var pages = MockedContent.CreateTextpageContent(contentType, -1, 100); ServiceContext.ContentService.Save(pages, 0); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var tRepository = new TemplateRepository(unitOfWork, DisabledCache, Logger, SqlSyntax, Mock.Of(), Mock.Of(), Mock.Of(), MappingResolver)) @@ -240,7 +240,7 @@ namespace Umbraco.Tests.Services var pages = MockedContent.CreateTextpageContent(contentType, -1, 1000); ServiceContext.ContentService.Save(pages, 0); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); using (var tRepository = new TemplateRepository(unitOfWork, DisabledCache, Logger, SqlSyntax, Mock.Of(), Mock.Of(), Mock.Of(), MappingResolver)) using (var tagRepo = new TagRepository(unitOfWork, DisabledCache, Logger, SqlSyntax, MappingResolver)) diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 1346812c2f..5dea7521c0 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -745,7 +745,7 @@ namespace Umbraco.Tests.Services var content = contentService.GetById(NodeDto.NodeIdSeed + 1); bool published = contentService.Publish(content, 0); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var uow = provider.GetUnitOfWork(); Assert.IsTrue(uow.Database.Exists(content.Id)); @@ -807,7 +807,7 @@ namespace Umbraco.Tests.Services } var allContent = rootContent.Concat(rootContent.SelectMany(x => x.Descendants())); //for testing we need to clear out the contentXml table so we can see if it worked - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); using (var uow = provider.GetUnitOfWork()) { uow.Database.TruncateTable(SqlSyntax, "cmsContentXml"); @@ -841,7 +841,7 @@ namespace Umbraco.Tests.Services } var allContent = rootContent.Concat(rootContent.SelectMany(x => x.Descendants())).ToList(); //for testing we need to clear out the contentXml table so we can see if it worked - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); using (var uow = provider.GetUnitOfWork()) { @@ -1331,8 +1331,10 @@ namespace Umbraco.Tests.Services [Test] public void Can_Save_Lazy_Content() - { - var unitOfWork = PetaPocoUnitOfWorkProvider.CreateUnitOfWork(Logger); + { + var databaseFactory = new DefaultDatabaseFactory(Umbraco.Core.Configuration.GlobalSettings.UmbracoConnectionName, Logger); + var provider = new NPocoUnitOfWorkProvider(databaseFactory); + var unitOfWork = provider.GetUnitOfWork(); var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); var root = ServiceContext.ContentService.GetById(NodeDto.NodeIdSeed + 1); @@ -1447,7 +1449,7 @@ namespace Umbraco.Tests.Services contentService.Save(content); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); using (var uow = provider.GetUnitOfWork()) { @@ -1471,7 +1473,7 @@ namespace Umbraco.Tests.Services contentService.Save(content); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); using (var uow = provider.GetUnitOfWork()) { diff --git a/src/Umbraco.Tests/Services/MacroServiceTests.cs b/src/Umbraco.Tests/Services/MacroServiceTests.cs index 7267568025..7a6f68ff40 100644 --- a/src/Umbraco.Tests/Services/MacroServiceTests.cs +++ b/src/Umbraco.Tests/Services/MacroServiceTests.cs @@ -25,7 +25,7 @@ namespace Umbraco.Tests.Services { base.CreateTestData(); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); using (var unitOfWork = provider.GetUnitOfWork()) using (var repository = new MacroRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, MappingResolver)) { diff --git a/src/Umbraco.Tests/Services/MediaServiceTests.cs b/src/Umbraco.Tests/Services/MediaServiceTests.cs index 12d4b4b0aa..35c2a9d1b9 100644 --- a/src/Umbraco.Tests/Services/MediaServiceTests.cs +++ b/src/Umbraco.Tests/Services/MediaServiceTests.cs @@ -90,7 +90,7 @@ namespace Umbraco.Tests.Services mediaService.Save(media); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); var uow = provider.GetUnitOfWork(); Assert.IsTrue(uow.Database.Exists(media.Id)); diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 17b4e3499c..3e7a59a228 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using NPoco; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Events; @@ -1017,12 +1018,12 @@ namespace Umbraco.Tests.Services result.LastLoginDate.TruncateTo(DateTimeExtensions.DateTruncate.Second)); //now ensure the col is correct - var sql = new Sql().Select("cmsPropertyData.*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, dto => dto.PropertyTypeId, dto => dto.Id) - .Where(SqlSyntax, dto => dto.NodeId == member.Id) - .Where(SqlSyntax, dto => dto.Alias == Constants.Conventions.Member.LastLoginDate); + var sql = NPoco.Sql.BuilderFor(new SqlContext(SqlSyntax, DatabaseContext.Database)).Select("cmsPropertyData.*") + .From() + .InnerJoin() + .On(dto => dto.PropertyTypeId, dto => dto.Id) + .Where(dto => dto.NodeId == member.Id) + .Where(dto => dto.Alias == Constants.Conventions.Member.LastLoginDate); var colResult = DatabaseContext.Database.Fetch(sql); @@ -1056,7 +1057,7 @@ namespace Umbraco.Tests.Services var customMember = MockedMember.CreateSimpleMember(memberType, "hello", "hello@test.com", "hello", "hello"); ServiceContext.MemberService.Save(customMember); - var provider = new PetaPocoUnitOfWorkProvider(Logger); + var provider = new NPocoUnitOfWorkProvider(Logger); using (var uow = provider.GetUnitOfWork()) { diff --git a/src/Umbraco.Tests/Services/PerformanceTests.cs b/src/Umbraco.Tests/Services/PerformanceTests.cs index f92866d81f..b362ff3597 100644 --- a/src/Umbraco.Tests/Services/PerformanceTests.cs +++ b/src/Umbraco.Tests/Services/PerformanceTests.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Xml.Linq; +using NPoco; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; @@ -286,8 +287,8 @@ namespace Umbraco.Tests.Services DatabaseContext.Database.BulkInsertRecords(SqlSyntax, nodes); //re-get the nodes with ids - var sql = new Sql(); - sql.Select("*").From(SqlSyntax).Where(SqlSyntax, x => x.NodeObjectType == customObjectType); + var sql = NPoco.Sql.BuilderFor(new SqlContext(SqlSyntax, DatabaseContext.Database)); + sql.SelectAll().From().Where(x => x.NodeObjectType == customObjectType); nodes = DatabaseContext.Database.Fetch(sql); //create the cmsContent data, each with a new content type id (so we can query on it later if needed) diff --git a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs index da7a1079ed..b4560b072b 100644 --- a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs +++ b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs @@ -24,7 +24,7 @@ namespace Umbraco.Tests.Services [TestFixture, RequiresSTA] public class ThreadSafetyServiceTest : BaseDatabaseFactoryTest { - private PerThreadPetaPocoUnitOfWorkProvider _uowProvider; + private PerThreadNPocoUnitOfWorkProvider _uowProvider; private PerThreadDatabaseFactory _dbFactory; [SetUp] @@ -47,7 +47,7 @@ namespace Umbraco.Tests.Services //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 repositoryFactory = new RepositoryFactory(SqlSyntax, Container); - _uowProvider = new PerThreadPetaPocoUnitOfWorkProvider(_dbFactory); + _uowProvider = new PerThreadNPocoUnitOfWorkProvider(_dbFactory); var evtMsgs = new TransientMessagesFactory(); ApplicationContext.Services = new ServiceContext( repositoryFactory, @@ -254,11 +254,11 @@ namespace Umbraco.Tests.Services /// /// Creates a UOW with a Database object per thread /// - internal class PerThreadPetaPocoUnitOfWorkProvider : DisposableObject, IDatabaseUnitOfWorkProvider + internal class PerThreadNPocoUnitOfWorkProvider : DisposableObject, IDatabaseUnitOfWorkProvider { private readonly PerThreadDatabaseFactory _dbFactory; - public PerThreadPetaPocoUnitOfWorkProvider(PerThreadDatabaseFactory dbFactory) + public PerThreadNPocoUnitOfWorkProvider(PerThreadDatabaseFactory dbFactory) { _dbFactory = dbFactory; } @@ -267,7 +267,7 @@ namespace Umbraco.Tests.Services { //Create or get a database instance for this thread. var db = _dbFactory.CreateDatabase(); - return new PetaPocoUnitOfWork(db); + return new NPocoUnitOfWork(db); } protected override void DisposeResources() diff --git a/src/Umbraco.Tests/Services/UserServiceTests.cs b/src/Umbraco.Tests/Services/UserServiceTests.cs index a9ebc159ec..6b522efddd 100644 --- a/src/Umbraco.Tests/Services/UserServiceTests.cs +++ b/src/Umbraco.Tests/Services/UserServiceTests.cs @@ -4,12 +4,9 @@ using System.Security.Cryptography; using System.Text; using NUnit.Framework; using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Web._Legacy.Actions; namespace Umbraco.Tests.Services @@ -397,9 +394,10 @@ namespace Umbraco.Tests.Services var user1 = ServiceContext.UserService.CreateUserWithIdentity("test1", "test1@test.com", userType); - var result1 = ServiceContext.UserService.GetUserById((int)user1.Id); + var result1 = ServiceContext.UserService.GetUserById(user1.Id); //expect 2 sections by default - Assert.AreEqual(2, result1.AllowedSections.Count()); + // 3: see commit eb59d33 + Assert.AreEqual(3, result1.AllowedSections.Count()); //adds some allowed sections user1.AddAllowedSection("test1"); @@ -408,9 +406,9 @@ namespace Umbraco.Tests.Services user1.AddAllowedSection("test4"); ServiceContext.UserService.Save(user1); - result1 = ServiceContext.UserService.GetUserById((int)user1.Id); - //expect 6 sections including the two default sections - Assert.AreEqual(6, result1.AllowedSections.Count()); + result1 = ServiceContext.UserService.GetUserById(user1.Id); + //expect 7 sections including the two default sections + Assert.AreEqual(7, result1.AllowedSections.Count()); //simulate clearing the sections foreach (var s in user1.AllowedSections) @@ -424,7 +422,7 @@ namespace Umbraco.Tests.Services //assert //re-get - result1 = ServiceContext.UserService.GetUserById((int)user1.Id); + result1 = ServiceContext.UserService.GetUserById(user1.Id); Assert.AreEqual(2, result1.AllowedSections.Count()); } @@ -547,7 +545,7 @@ namespace Umbraco.Tests.Services Assert.That(updatedItem.StartMediaId, Is.EqualTo(originalUser.StartMediaId)); Assert.That(updatedItem.Email, Is.EqualTo(originalUser.Email)); Assert.That(updatedItem.Username, Is.EqualTo(originalUser.Username)); - Assert.That(updatedItem.AllowedSections.Count(), Is.EqualTo(2)); + Assert.That(updatedItem.AllowedSections.Count(), Is.EqualTo(3)); // 3: see commit eb59d33 } } } diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index e0ed618da2..abc3c0ee40 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -39,7 +39,7 @@ namespace Umbraco.Tests.TestHelpers { /// /// Use this abstract class for tests that requires a Sql Ce database populated with the umbraco db schema. - /// The PetaPoco Database class should be used through the . + /// The NPoco Database class should be used through the . /// [TestFixture, RequiresSTA] public abstract class BaseDatabaseFactoryTest : BaseUmbracoApplicationTest @@ -86,12 +86,12 @@ namespace Umbraco.Tests.TestHelpers } protected override void SetupApplicationContext() - { + { var dbFactory = new DefaultDatabaseFactory( GetDbConnectionString(), GetDbProviderName(), Logger); - + var evtMsgs = new TransientMessagesFactory(); _appContext = new ApplicationContext( //assign the db context @@ -99,7 +99,7 @@ namespace Umbraco.Tests.TestHelpers //assign the service context new ServiceContext( Container.GetInstance(), - new PetaPocoUnitOfWorkProvider(dbFactory), + new NPocoUnitOfWorkProvider(dbFactory), new FileUnitOfWorkProvider(), new PublishingStrategy(evtMsgs, Logger), CacheHelper, @@ -149,7 +149,7 @@ namespace Umbraco.Tests.TestHelpers ///
      protected virtual string GetDbConnectionString() { - return @"Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf;Flush Interval=1;"; + return @"Datasource=|DataDirectory|UmbracoNPocoTests.sdf;Flush Interval=1;"; } /// @@ -168,7 +168,7 @@ namespace Umbraco.Tests.TestHelpers Core.Configuration.GlobalSettings.UmbracoConnectionName, GetDbConnectionString()); - _dbPath = string.Concat(path, "\\UmbracoPetaPocoTests.sdf"); + _dbPath = string.Concat(path, "\\UmbracoNPocoTests.sdf"); //create a new database file if // - is the first test in the session @@ -181,7 +181,7 @@ namespace Umbraco.Tests.TestHelpers || (DatabaseTestBehavior == DatabaseBehavior.NewDbFileAndSchemaPerTest || DatabaseTestBehavior == DatabaseBehavior.EmptyDbFilePerTest) || (_isFirstTestInFixture && DatabaseTestBehavior == DatabaseBehavior.NewDbFileAndSchemaPerFixture)) { - + using (ProfilingLogger.TraceDuration("Remove database file")) { RemoveDatabaseFile(ex => @@ -210,7 +210,7 @@ namespace Umbraco.Tests.TestHelpers } } - + /// /// sets up resolvers before resolution is frozen @@ -256,7 +256,7 @@ namespace Umbraco.Tests.TestHelpers //Create the umbraco database and its base data schemaHelper.CreateDatabaseSchema(_appContext); - //close the connections, we're gonna read this baby in as a byte array so we don't have to re-initialize the + //close the connections, we're gonna read this baby in as a byte array so we don't have to re-initialize the // damn db for each test CloseDbConnections(); @@ -291,7 +291,7 @@ namespace Umbraco.Tests.TestHelpers private void CloseDbConnections() { - //Ensure that any database connections from a previous test is disposed. + //Ensure that any database connections from a previous test is disposed. //This is really just double safety as its also done in the TearDown. if (ApplicationContext != null && DatabaseContext != null && DatabaseContext.Database != null) DatabaseContext.Database.Dispose(); @@ -332,7 +332,7 @@ namespace Umbraco.Tests.TestHelpers string path = TestHelper.CurrentAssemblyDirectory; try { - string filePath = string.Concat(path, "\\UmbracoPetaPocoTests.sdf"); + string filePath = string.Concat(path, "\\UmbracoNPocoTests.sdf"); if (File.Exists(filePath)) { File.Delete(filePath); @@ -368,7 +368,7 @@ namespace Umbraco.Tests.TestHelpers doc.LoadXml(GetXmlContent(templateId)); return doc; }); - + PublishedContentCache.UnitTesting = true; var httpContext = GetHttpContextFactory(url, routeData).HttpContext; @@ -398,7 +398,7 @@ namespace Umbraco.Tests.TestHelpers protected virtual string GetXmlContent(int templateId) { return @" - @@ -411,7 +411,7 @@ namespace Umbraco.Tests.TestHelpers 1 This is some content]]> - + diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index e6065e780d..24a5c5f34d 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -193,9 +193,14 @@ namespace Umbraco.Tests.TestHelpers } } - protected virtual void SetupCacheHelper() + private void SetupCacheHelper() { - CacheHelper = CacheHelper.CreateDisabledCacheHelper(); + CacheHelper = CreateCacheHelper(); + } + + protected virtual CacheHelper CreateCacheHelper() + { + return CacheHelper.CreateDisabledCacheHelper(); } /// @@ -211,7 +216,7 @@ namespace Umbraco.Tests.TestHelpers //assign the service context new ServiceContext( Container.GetInstance(), - new PetaPocoUnitOfWorkProvider(Logger), + new NPocoUnitOfWorkProvider(Logger), new FileUnitOfWorkProvider(), new PublishingStrategy(evtMsgs, Logger), CacheHelper, @@ -241,7 +246,6 @@ namespace Umbraco.Tests.TestHelpers Assembly.Load("Umbraco.Core"), Assembly.Load("umbraco"), Assembly.Load("Umbraco.Tests"), - Assembly.Load("businesslogic"), Assembly.Load("cms"), Assembly.Load("controls"), } diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index f1ff37bbb6..c88df8ba4f 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -1,5 +1,6 @@ using LightInject; using Moq; +using NPoco; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; @@ -9,28 +10,31 @@ using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Profiling; using Umbraco.Core.DependencyInjection; +using Umbraco.Core.Persistence; namespace Umbraco.Tests.TestHelpers { [TestFixture] public abstract class BaseUsingSqlCeSyntax { - protected virtual SqlCeSyntaxProvider SqlSyntax - { - get { return new SqlCeSyntaxProvider(); } - } - private MappingResolver _mappingResolver; - protected IMappingResolver MappingResolver + + protected IMappingResolver MappingResolver => _mappingResolver; + + protected SqlContext SqlContext { get; private set; } + + protected Sql Sql() { - get { return _mappingResolver; } + return NPoco.Sql.BuilderFor(SqlContext); } [SetUp] public virtual void Initialize() { + var sqlSyntax = new SqlCeSyntaxProvider(); + var container = new ServiceContainer(); - container.RegisterSingleton(factory => SqlSyntax); + container.RegisterSingleton(factory => sqlSyntax); container.RegisterSingleton(factory => Mock.Of()); container.RegisterSingleton(factory => Mock.Of()); @@ -43,6 +47,10 @@ namespace Umbraco.Tests.TestHelpers logger, false); + var mappers = new MapperCollection { new PocoMapper() }; + var pocoDataFactory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, mappers).Init()); + SqlContext = new SqlContext(sqlSyntax, pocoDataFactory, DatabaseType.SQLCe); + Resolution.Freeze(); SetUp(); } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index d079b3bb83..fb3e318538 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -94,6 +94,10 @@ ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll True + + ..\packages\NPoco.3.1.0-u005\lib\net45\NPoco.dll + True + False ..\packages\NUnit.2.6.2\lib\nunit.framework.dll @@ -197,6 +201,7 @@ + @@ -295,7 +300,7 @@ - + @@ -405,7 +410,7 @@ - + diff --git a/src/Umbraco.Tests/packages.config b/src/Umbraco.Tests/packages.config index 7b5780daa2..3c4ac7ecb4 100644 --- a/src/Umbraco.Tests/packages.config +++ b/src/Umbraco.Tests/packages.config @@ -19,6 +19,7 @@ + diff --git a/src/Umbraco.Web/Install/InstallSteps/MajorVersion7UpgradeReport.cs b/src/Umbraco.Web/Install/InstallSteps/MajorVersion7UpgradeReport.cs index f1903f96a4..74c3c683c4 100644 --- a/src/Umbraco.Web/Install/InstallSteps/MajorVersion7UpgradeReport.cs +++ b/src/Umbraco.Web/Install/InstallSteps/MajorVersion7UpgradeReport.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Persistence; diff --git a/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs b/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs index a03f9e36a6..92325ecaa7 100644 --- a/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs +++ b/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core; using Umbraco.Core.Events; using Umbraco.Core.Models; diff --git a/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs b/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs index e51d6d59b8..66958a2c9d 100644 --- a/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs +++ b/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NPoco; using Umbraco.Core.Events; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; @@ -32,19 +33,19 @@ namespace Umbraco.Web.Strategies.Migrations var target = new Version(6, 0, 0); if (e.ConfiguredVersion < target) { - var sql = new Sql(); - sql.Select("*") - .From(_sqlSyntax) - .InnerJoin(_sqlSyntax) - .On(_sqlSyntax, left => left.VersionId, right => right.VersionId) - .InnerJoin(_sqlSyntax) - .On(_sqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(_sqlSyntax) - .On(_sqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(_sqlSyntax, x => x.NodeObjectType == new Guid(Constants.ObjectTypes.Document)) - .Where(_sqlSyntax, x => x.Path.StartsWith("-1")); + var sql = Sql.BuilderFor(new SqlContext(_sqlSyntax, e.MigrationContext.Database)) + .SelectAll() + .From() + .InnerJoin() + .On(left => left.VersionId, right => right.VersionId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(x => x.NodeObjectType == new Guid(Constants.ObjectTypes.Document)) + .Where(x => x.Path.StartsWith("-1")); - var dtos = e.MigrationContext.Database.Fetch(sql); + var dtos = e.MigrationContext.Database.Fetch(sql); var toUpdate = new List(); var versionGroup = dtos.GroupBy(x => x.NodeId); foreach (var grp in versionGroup) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 5ae1a5ec51..66e9b2c6e7 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -197,6 +197,10 @@ ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll True + + ..\packages\NPoco.3.1.0-u005\lib\net45\NPoco.dll + True + False ..\packages\Owin.1.0\lib\net40\Owin.dll @@ -225,6 +229,7 @@ False ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll + diff --git a/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs b/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs index e1890326fb..ad71ccf720 100644 --- a/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/DisableBrowserCacheAttribute.cs @@ -24,6 +24,9 @@ namespace Umbraco.Web.WebApi.Filters base.OnActionExecuted(actionExecutedContext); + // happens if exception + if (actionExecutedContext.Response == null) return; + //NOTE: Until we upgraded to WebApi 2, this didn't work correctly and we had to revert to using // HttpContext.Current responses. I've changed this back to what it should be now since it works // and now with WebApi2, the HttpContext.Current responses dont! Anyways, all good now. diff --git a/src/Umbraco.Web/WebServices/XmlDataIntegrityController.cs b/src/Umbraco.Web/WebServices/XmlDataIntegrityController.cs index 3e95c9ee22..589801a829 100644 --- a/src/Umbraco.Web/WebServices/XmlDataIntegrityController.cs +++ b/src/Umbraco.Web/WebServices/XmlDataIntegrityController.cs @@ -1,5 +1,6 @@ using System; using System.Web.Http; +using NPoco; using Umbraco.Core; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; @@ -37,13 +38,13 @@ namespace Umbraco.Web.WebServices { var totalPublished = Services.ContentService.CountPublished(); - var subQuery = new Sql() + var subQuery = DatabaseContext.Sql() .Select("DISTINCT cmsContentXml.nodeId") - .From(DatabaseContext.SqlSyntax) - .InnerJoin(DatabaseContext.SqlSyntax) - .On(DatabaseContext.SqlSyntax, left => left.NodeId, right => right.NodeId); + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId); - var totalXml = ApplicationContext.DatabaseContext.Database.ExecuteScalar("SELECT COUNT(*) FROM (" + subQuery.SQL + ") as tmp"); + var totalXml = DatabaseContext.Database.ExecuteScalar("SELECT COUNT(*) FROM (" + subQuery.SQL + ") as tmp"); return totalXml == totalPublished; } @@ -53,13 +54,13 @@ namespace Umbraco.Web.WebServices { var total = Services.MediaService.Count(); var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media); - var subQuery = new Sql() - .Select("Count(*)") - .From(DatabaseContext.SqlSyntax) - .InnerJoin(DatabaseContext.SqlSyntax) - .On(DatabaseContext.SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(DatabaseContext.SqlSyntax, dto => dto.NodeObjectType == mediaObjectType); - var totalXml = ApplicationContext.DatabaseContext.Database.ExecuteScalar(subQuery); + var subQuery = DatabaseContext.Sql() + .SelectCount() + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeObjectType == mediaObjectType); + var totalXml = DatabaseContext.Database.ExecuteScalar(subQuery); return totalXml == total; } @@ -69,13 +70,13 @@ namespace Umbraco.Web.WebServices { var total = Services.MemberService.Count(); var memberObjectType = Guid.Parse(Constants.ObjectTypes.Member); - var subQuery = new Sql() - .Select("Count(*)") - .From(DatabaseContext.SqlSyntax) - .InnerJoin(DatabaseContext.SqlSyntax) - .On(DatabaseContext.SqlSyntax, left => left.NodeId, right => right.NodeId) - .Where(DatabaseContext.SqlSyntax, dto => dto.NodeObjectType == memberObjectType); - var totalXml = ApplicationContext.DatabaseContext.Database.ExecuteScalar(subQuery); + var subQuery = DatabaseContext.Sql() + .SelectCount() + .From() + .InnerJoin() + .On(left => left.NodeId, right => right.NodeId) + .Where(dto => dto.NodeObjectType == memberObjectType); + var totalXml = DatabaseContext.Database.ExecuteScalar(subQuery); return totalXml == total; } diff --git a/src/Umbraco.Web/packages.config b/src/Umbraco.Web/packages.config index abafc5dcc2..97242e1500 100644 --- a/src/Umbraco.Web/packages.config +++ b/src/Umbraco.Web/packages.config @@ -29,6 +29,7 @@ + diff --git a/src/UmbracoExamine/DataServices/PropertyAliasDto.cs b/src/UmbracoExamine/DataServices/PropertyAliasDto.cs index 84e63ccf5c..64c3c5b14c 100644 --- a/src/UmbracoExamine/DataServices/PropertyAliasDto.cs +++ b/src/UmbracoExamine/DataServices/PropertyAliasDto.cs @@ -1,7 +1,7 @@ namespace UmbracoExamine.DataServices { /// - /// A Dto object for returning property aliases from PetaPoco + /// A Dto object for returning property aliases from NPoco /// public class PropertyAliasDto { diff --git a/src/UmbracoExamine/UmbracoExamine.csproj b/src/UmbracoExamine/UmbracoExamine.csproj index 7f137d11d0..21721da6b6 100644 --- a/src/UmbracoExamine/UmbracoExamine.csproj +++ b/src/UmbracoExamine/UmbracoExamine.csproj @@ -95,12 +95,21 @@ ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll + + ..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\NPoco.3.1.0-u005\lib\net45\NPoco.dll + True + 3.5 + diff --git a/src/UmbracoExamine/packages.config b/src/UmbracoExamine/packages.config index 722e13f2d5..3b7d941dd4 100644 --- a/src/UmbracoExamine/packages.config +++ b/src/UmbracoExamine/packages.config @@ -2,5 +2,7 @@ + + \ No newline at end of file diff --git a/src/umbraco.cms/packages.config b/src/umbraco.cms/packages.config index c00e2f0c3d..12c1341ac8 100644 --- a/src/umbraco.cms/packages.config +++ b/src/umbraco.cms/packages.config @@ -3,5 +3,6 @@ + \ No newline at end of file diff --git a/src/umbraco.cms/umbraco.cms.csproj b/src/umbraco.cms/umbraco.cms.csproj index 492272eb1d..c261253c33 100644 --- a/src/umbraco.cms/umbraco.cms.csproj +++ b/src/umbraco.cms/umbraco.cms.csproj @@ -123,6 +123,10 @@ ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll True + + ..\packages\NPoco.3.1.0-u005\lib\net45\NPoco.dll + True + System @@ -134,6 +138,7 @@ System.Data + System.Security From a3158b48ac7275180e1d145427b4b7ae0fdb9a4f Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Apr 2016 15:18:14 +0200 Subject: [PATCH 073/101] fix merge --- src/Umbraco.Web/Umbraco.Web.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index d0bbd2f7cb..91de7f69bf 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1102,10 +1102,10 @@ ASPXCodeBehind - - ASPXCodeBehind + ASPXCodeBehind + From 76054bb78338c9997bbf63c5e6821f15151e2044 Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 12 Apr 2016 15:36:28 +0200 Subject: [PATCH 074/101] Enabling sorting by the Name column. OrderDirection is only reversed if reclicking the same sort column. --- .../src/common/services/listviewhelper.service.js | 9 ++++----- .../src/views/components/umb-table.html | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js index e211218ce1..0dda0d4a96 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js @@ -248,13 +248,12 @@ function setSorting(field, allow, options) { if (allow) { - options.orderBy = field; - - if (options.orderDirection === "desc") { - options.orderDirection = "asc"; - } else { + if (options.orderBy === field && options.orderDirection === 'asc') { options.orderDirection = "desc"; + } else { + options.orderDirection = "asc"; } + options.orderBy = field; } } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html index 8d9b45e6be..fb5e992d81 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-table.html @@ -10,7 +10,7 @@ ng-checked="isSelectedAll()">
      - + Name From 1361e017a25588c61a0fe240872a403ea4a9d68c Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Apr 2016 18:07:25 +0200 Subject: [PATCH 075/101] Merge branch 'u4-222' of https://github.com/AndyButland/Umbraco-CMS into AndyButland-u4-222 Conflicts: src/Umbraco.Core/Security/BackOfficeUserManager.cs src/Umbraco.Web.UI.Client/src/less/pages/login.less src/Umbraco.Web.UI.Client/src/routes.js src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml src/Umbraco.Web.UI/web.Template.config src/Umbraco.Web/Editors/AuthenticationController.cs src/Umbraco.Web/Editors/BackOfficeController.cs src/Umbraco.Web/Umbraco.Web.csproj --- .../UmbracoSettings/ISecuritySection.cs | 2 + .../UmbracoSettings/SecurityElement.cs | 17 ++ .../Security/BackOfficeUserManager.cs | 2 +- src/Umbraco.Core/Security/EmailService.cs | 23 +++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../UmbracoSettings/SecurityElementTests.cs | 9 ++ .../UmbracoSettings/umbracoSettings.config | 5 +- .../src/common/resources/auth.resource.js | 152 ++++++++++++++++++ .../src/common/services/user.service.js | 32 ++++ .../src/less/pages/login.less | 9 +- src/Umbraco.Web.UI.Client/src/routes.js | 6 +- .../views/common/dialogs/login.controller.js | 115 +++++++++++-- .../src/views/common/dialogs/login.html | 146 +++++++++++++---- .../config/umbracoSettings.config | 3 + src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 12 ++ .../umbraco/config/lang/en_us.xml | 12 ++ src/Umbraco.Web.UI/web.Template.config | 2 +- .../Editors/AuthenticationController.cs | 126 ++++++++++++--- .../Editors/BackOfficeController.cs | 1 + .../HtmlHelperBackOfficeExtensions.cs | 5 + .../Models/RequestPasswordResetModel.cs | 12 ++ src/Umbraco.Web/Models/SetPasswordModel.cs | 20 +++ .../Models/ValidatePasswordResetCodeModel.cs | 16 ++ src/Umbraco.Web/Umbraco.Web.csproj | 3 + src/umbraco.businesslogic/UmbracoSettings.cs | 8 + 25 files changed, 664 insertions(+), 75 deletions(-) create mode 100644 src/Umbraco.Core/Security/EmailService.cs create mode 100644 src/Umbraco.Web/Models/RequestPasswordResetModel.cs create mode 100644 src/Umbraco.Web/Models/SetPasswordModel.cs create mode 100644 src/Umbraco.Web/Models/ValidatePasswordResetCodeModel.cs diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs index c3a1df301d..c44c0cf0df 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ISecuritySection.cs @@ -6,6 +6,8 @@ bool HideDisabledUsersInBackoffice { get; } + bool AllowPasswordReset { get; } + string AuthCookieName { get; } string AuthCookieDomain { get; } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs index 34642c8c90..f280b3e20c 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/SecurityElement.cs @@ -28,6 +28,18 @@ namespace Umbraco.Core.Configuration.UmbracoSettings } } + [ConfigurationProperty("allowPasswordReset")] + internal InnerTextConfigurationElement AllowPasswordReset + { + get + { + return new OptionalInnerTextConfigurationElement( + (InnerTextConfigurationElement)this["allowPasswordReset"], + //set the default + true); + } + } + [ConfigurationProperty("authCookieName")] internal InnerTextConfigurationElement AuthCookieName { @@ -62,6 +74,11 @@ namespace Umbraco.Core.Configuration.UmbracoSettings get { return HideDisabledUsersInBackoffice; } } + bool ISecuritySection.AllowPasswordReset + { + get { return AllowPasswordReset; } + } + string ISecuritySection.AuthCookieName { get { return AuthCookieName; } diff --git a/src/Umbraco.Core/Security/BackOfficeUserManager.cs b/src/Umbraco.Core/Security/BackOfficeUserManager.cs index dfe59e1783..ba7e615771 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserManager.cs @@ -132,7 +132,7 @@ namespace Umbraco.Core.Security // BodyFormat = "Your security code is: {0}" //}); - //manager.EmailService = new EmailService(); + manager.EmailService = new EmailService(); //manager.SmsService = new SmsService(); } diff --git a/src/Umbraco.Core/Security/EmailService.cs b/src/Umbraco.Core/Security/EmailService.cs new file mode 100644 index 0000000000..0a4ca95ceb --- /dev/null +++ b/src/Umbraco.Core/Security/EmailService.cs @@ -0,0 +1,23 @@ +using System.Net.Mail; +using System.Threading.Tasks; +using Microsoft.AspNet.Identity; + +namespace Umbraco.Core.Security +{ + public class EmailService : IIdentityMessageService + { + public async Task SendAsync(IdentityMessage message) + { + using (var client = new SmtpClient()) + using (var mailMessage = new MailMessage()) + { + mailMessage.Body = message.Body; + mailMessage.To.Add(message.Destination); + mailMessage.Subject = message.Subject; + mailMessage.IsBodyHtml = true; + + await client.SendMailAsync(mailMessage); + } + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 01774dc7a2..ce34b6ef8f 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -490,6 +490,7 @@ + diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/SecurityElementTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/SecurityElementTests.cs index 8fbf4a1523..58a9a438a2 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/SecurityElementTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/SecurityElementTests.cs @@ -11,16 +11,25 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings { Assert.IsTrue(SettingsSection.Security.KeepUserLoggedIn == true); } + [Test] public void HideDisabledUsersInBackoffice() { Assert.IsTrue(SettingsSection.Security.HideDisabledUsersInBackoffice == false); } + + [Test] + public void AllowPasswordReset() + { + Assert.IsTrue(SettingsSection.Security.AllowPasswordReset == true); + } + [Test] public void AuthCookieDomain() { Assert.IsTrue(SettingsSection.Security.AuthCookieDomain == null); } + [Test] public void AuthCookieName() { diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config b/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config index 80f61371c2..80eaee77d3 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config @@ -110,7 +110,10 @@ false - + + + true + diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js index f32602bda6..b170df3c97 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js @@ -51,7 +51,159 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { }), 'Login failed for user ' + username); }, + + /** + * @ngdoc method + * @name umbraco.resources.authResource#performRequestPasswordReset + * @methodOf umbraco.resources.authResource + * + * @description + * Checks to see if the provided email address is a valid user account and sends a link + * to allow them to reset their password + * + * ##usage + *
      +         * authResource.performRequestPasswordReset(email)
      +         *    .then(function(data) {
      +         *        //Do stuff for password reset request...
      +         *    });
      +         * 
      + * @param {string} email Email address of backoffice user + * @returns {Promise} resourcePromise object + * + */ + performRequestPasswordReset: function (email) { + + if (!email) { + return angularHelper.rejectedPromise({ + errorMsg: 'Email address cannot be empty' + }); + } + + var emailRegex = /\S+@\S+\.\S+/; + if (!emailRegex.test(email)) { + return angularHelper.rejectedPromise({ + errorMsg: 'Email address is not valid' + }); + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "PostRequestPasswordReset"), { + email: email + }), + 'Request password reset failed for email ' + email); + }, + /** + * @ngdoc method + * @name umbraco.resources.authResource#performValidatePasswordResetCode + * @methodOf umbraco.resources.authResource + * + * @description + * Checks to see if the provided password reset code is valid + * + * ##usage + *
      +         * authResource.performValidatePasswordResetCode(resetCode)
      +         *    .then(function(data) {
      +         *        //Allow reset of password
      +         *    });
      +         * 
      + * @param {integer} userId User Id + * @param {string} resetCode Password reset code + * @returns {Promise} resourcePromise object + * + */ + performValidatePasswordResetCode: function (userId, resetCode) { + + if (!userId) { + return angularHelper.rejectedPromise({ + errorMsg: 'User Id cannot be empty' + }); + } + + if (!resetCode) { + return angularHelper.rejectedPromise({ + errorMsg: 'Reset code cannot be empty' + }); + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "PostValidatePasswordResetCode"), + { + userId: userId, + resetCode: resetCode + }), + 'Password reset code validation failed for userId + ' + userId + ', code' + resetCode); + }, + + /** + * @ngdoc method + * @name umbraco.resources.authResource#performSetPassword + * @methodOf umbraco.resources.authResource + * + * @description + * Checks to see if the provided password reset code is valid and sets the user's password + * + * ##usage + *
      +         * authResource.performSetPassword(userId, password, confirmPassword, resetCode)
      +         *    .then(function(data) {
      +         *        //Password set
      +         *    });
      +         * 
      + * @param {integer} userId User Id + * @param {string} password New password + * @param {string} confirmPassword Confirmation of new password + * @param {string} resetCode Password reset code + * @returns {Promise} resourcePromise object + * + */ + performSetPassword: function (userId, password, confirmPassword, resetCode) { + + if (!userId) { + return angularHelper.rejectedPromise({ + errorMsg: 'User Id cannot be empty' + }); + } + + if (!password) { + return angularHelper.rejectedPromise({ + errorMsg: 'Password cannot be empty' + }); + } + + if (password !== confirmPassword) { + return angularHelper.rejectedPromise({ + errorMsg: 'Password and confirmation do not match' + }); + } + + if (!resetCode) { + return angularHelper.rejectedPromise({ + errorMsg: 'Reset code cannot be empty' + }); + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "authenticationApiBaseUrl", + "PostSetPassword"), + { + userId: userId, + password: password, + resetCode: resetCode + }), + 'Password reset code validation failed for userId + ' + userId); + }, + unlinkLogin: function (loginProvider, providerKey) { if (!loginProvider || !providerKey) { return angularHelper.rejectedPromise({ diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 3fb291619d..27f0165f4d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -212,6 +212,38 @@ angular.module('umbraco.services') }); }, + /** Returns a promise, sends a request to the server to send a password reset email if the email is recognised */ + requestPasswordReset: function (email) { + + return authResource.performRequestPasswordReset(email) + .then(function (data) { + // Note that we don't actually confirm if the email address was matched or not, to avoid + // allowing an attacker to determine which email addresses are valid. + var result = { success: true }; + return result; + }); + }, + + /** Returns a promise, sends a request to the server to validate a password reset code */ + validatePasswordResetCode: function (userId, resetCode) { + + return authResource.performValidatePasswordResetCode(userId, resetCode) + .then(function (data) { + var result = { success: data }; + return result; + }); + }, + + /** Returns a promise, sends a request to the server to validate a password reset code */ + setPassword: function (userId, password, confirmPassword, resetCode) { + + return authResource.performSetPassword(userId, password, confirmPassword, resetCode) + .then(function (data) { + var result = { success: data }; + return result; + }); + }, + /** Logs the user out */ logout: function () { diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/login.less b/src/Umbraco.Web.UI.Client/src/less/pages/login.less index 081a194b7e..ff7fbc243a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less @@ -66,7 +66,7 @@ font-weight: normal; } -.login-overlay .alert.alert-error { +.login-overlay .alert { display: inline-block; padding-right: 6px; padding-left: 6px; @@ -74,6 +74,13 @@ text-align: center; } +.login-overlay .switch-view { + margin-top: 10px; + a { + color: #fff; + } +} + @media (max-width: 565px) { // Remove padding on login-form on smaller devices .login-overlay .form { diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index 5d641fcb6c..69e6779a15 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -93,10 +93,10 @@ app.config(function ($routeProvider) { //ensure auth is *not* required so it will redirect to / resolve: canRoute(false) }) - .when('/logout', { + .when('/logout', { redirectTo: '/login/false', - resolve: doLogout() - }) + resolve: doLogout() + }) .when('/:section', { templateUrl: function (rp) { if (rp.section.toLowerCase() === "default" || rp.section.toLowerCase() === "umbraco" || rp.section === "") diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js index 325af02bbc..d76d66a7c4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js @@ -1,22 +1,38 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LoginController", - function ($scope, $cookies, localizationService, userService, externalLoginInfo) { + function ($scope, $cookies, localizationService, userService, externalLoginInfo, $timeout, $location) { + + var setFieldFocus = function(form, field) { + $timeout(function() { + $("form[name='" + form + "'] input[name='" + field + "']").focus(); + }); + } + + $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; + + $scope.showLogin = function () { + $scope.errorMsg = ""; + $scope.view = "login"; + setFieldFocus("loginForm", "username"); + } + + $scope.showRequestPasswordReset = function () { + $scope.errorMsg = ""; + $scope.view = "request-password-reset"; + $scope.showEmailResetConfirmation = false; + setFieldFocus("requestPasswordResetForm", "email"); + } + + $scope.showSetPassword = function () { + $scope.view = "set-password"; + setFieldFocus("setPasswordForm", "password"); + } - /** - * @ngdoc function - * @name signin - * @methodOf MainController - * @function - * - * @description - * signs the user in - */ var d = new Date(); var konamiGreetings = new Array("Suze Sunday", "Malibu Monday", "Tequila Tuesday", "Whiskey Wednesday", "Negroni Day", "Fernet Friday", "Sancerre Saturday"); var konamiMode = $cookies.konamiLogin; - //var weekday = new Array("Super Sunday", "Manic Monday", "Tremendous Tuesday", "Wonderful Wednesday", "Thunder Thursday", "Friendly Friday", "Shiny Saturday"); if (konamiMode == "1") { $scope.greeting = "Happy " + konamiGreetings[d.getDay()]; } else { @@ -43,18 +59,32 @@ } } + // Set initial view - either set password if reset code provided in querystring + // otherwise login form + var userId = $location.search().userId; + var resetCode = $location.search().resetCode; + if (userId && resetCode) { + userService.validatePasswordResetCode(userId, resetCode) + .then(function () { + $scope.showSetPassword(); + }, function () { + $scope.view = "password-reset-code-expired"; + }); + } else { + $scope.showLogin(); + } + $scope.loginSubmit = function (login, password) { //if the login and password are not empty we need to automatically // validate them - this is because if there are validation errors on the server // then the user has to change both username & password to resubmit which isn't ideal, - // so if they're not empty , we'l just make sure to set them to valid. + // so if they're not empty, we'll just make sure to set them to valid. if (login && password && login.length > 0 && password.length > 0) { $scope.loginForm.username.$setValidity('auth', true); $scope.loginForm.password.$setValidity('auth', true); } - if ($scope.loginForm.$invalid) { return; } @@ -84,4 +114,63 @@ } }); }; + + $scope.requestPasswordResetSubmit = function (email) { + + $scope.errorMsg = ""; + $scope.showEmailResetConfirmation = false; + + if ($scope.requestPasswordResetForm.$invalid) { + return; + } + + userService.requestPasswordReset(email) + .then(function () { + $scope.showEmailResetConfirmation = true; + }, function (reason) { + $scope.errorMsg = reason.errorMsg; + $scope.requestPasswordResetForm.email.$setValidity("auth", false); + }); + + $scope.requestPasswordResetForm.email.$viewChangeListeners.push(function () { + if ($scope.requestPasswordResetForm.email.$invalid) { + $scope.requestPasswordResetForm.email.$setValidity('auth', true); + } + }); + }; + + $scope.setPasswordSubmit = function (password, confirmPassword) { + + $scope.showSetPasswordConfirmation = false; + + if (password && confirmPassword && password.length > 0 && confirmPassword.length > 0) { + $scope.setPasswordForm.password.$setValidity('auth', true); + $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); + } + + if ($scope.setPasswordForm.$invalid) { + return; + } + + userService.setPassword(userId, password, confirmPassword, resetCode) + .then(function () { + $scope.showSetPasswordConfirmation = true; + }, function (reason) { + $scope.errorMsg = reason.errorMsg; + $scope.setPasswordForm.password.$setValidity("auth", false); + $scope.setPasswordForm.confirmPassword.$setValidity("auth", false); + }); + + $scope.setPasswordForm.password.$viewChangeListeners.push(function () { + if ($scope.setPasswordForm.password.$invalid) { + $scope.setPasswordForm.password.$setValidity('auth', true); + } + }); + $scope.setPasswordForm.confirmPassword.$viewChangeListeners.push(function () { + if ($scope.setPasswordForm.confirmPassword.$invalid) { + $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); + } + }); + } + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html index 3ca9bcda10..992b98942e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html @@ -2,54 +2,130 @@

      {{greeting}}

      -

      - Log in below. - Log in below -

      - -
      - -
      - {{error}} + +
      + +

      + Log in below. + Log in below +

      + +
      + +
      + {{error}} +
      + +
      + +
      + + + +
      +
      + +
      +
      +
      Or
      +
      +
      -
      + +
      + +
      -
      +
      + +
      - + +
      +
      {{errorMsg}}
      +
      + + - -
      -
      Or
      -
      -
      -
      -
      - -
      +
      +

      + Please enter your email address. If your account is located an email will be sent to you containing a link from which you can reset your password. +

      -
      - -
      + +
      + +
      - + -
      -
      {{errorMsg}}
      +
      +
      {{errorMsg}}
      +
      + +
      +
      + If your email address has been matched an email with password reset instructions has been sent. +
      +
      + + + +
      + +
      +

      + Please provide a new password. +

      + +
      + +
      + +
      + +
      + +
      + + + +
      +
      {{errorMsg}}
      +
      + +

      + Your new password has been set and you may now use it to log in. +

      + + +
      +
      + +
      +
      + The link you have clicked on is invalid or has expired.
      - - + +
      \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index 118b992ae5..b21894e07a 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -112,6 +112,9 @@ false + + true + diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index b810183d42..95f68b1219 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -290,6 +290,7 @@ Enter your username Enter your password + Confirm your password Name the %0%... Enter a name... Label... @@ -297,6 +298,7 @@ Type to search... Type to filter... Type to add tags (press enter after each tag)... + Enter your email Allow at root @@ -660,6 +662,16 @@ To manage your website, simply open the Umbraco back office and start adding con Log in below Session timed out © 2001 - %0%
      Umbraco.com

      ]]>
      + Forgotten password? + Please enter your email address. If your account is located an email will be sent to you containing a link from which you can reset your password. + If your email address has been matched an email with password reset instructions has been sent. + Return to login form + Please provide a new password. + Your new password has been set and you may now use it to log in. + The link you have clicked on is invalid or has expired. + Umbraco: Reset Password + Your username to login to the Umbraco back-office is: {0} + here. ]]> Dashboard diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 402902f48c..24380a5680 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -291,6 +291,7 @@ Enter your username Enter your password + Confirm your password Name the %0%... Enter a name... Label... @@ -298,6 +299,7 @@ Type to search... Type to filter... Type to add tags (press enter after each tag)... + Enter your email @@ -658,6 +660,16 @@ To manage your website, simply open the Umbraco back office and start adding con Log in below Session timed out © 2001 - %0%
      Umbraco.com

      ]]>
      + Forgotten password? + Please enter your email address. If your account is located an email will be sent to you containing a link from which you can reset your password. + If your email address has been matched an email with password reset instructions has been sent. + Return to login form + Please provide a new password. + Your new password has been set and you may now use it to log in. + The link you have clicked on is invalid or has expired. + Umbraco: Reset Password + Your username to login to the Umbraco back-office is: {0} + here. ]]> Dashboard diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 7f8752fff7..dd66460408 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -74,7 +74,7 @@ - + diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index ca6a69e238..09027c58f4 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -3,41 +3,29 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; -using System.Security.Claims; -using System.ServiceModel.Channels; -using System.Text; using System.Threading.Tasks; using System.Web; -using System.Web.Helpers; using System.Web.Http; -using System.Web.Http.Controllers; -using System.Web.Security; using AutoMapper; using Microsoft.AspNet.Identity; +using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; -using Microsoft.Owin.Security; using Umbraco.Core; using Umbraco.Core.Configuration; -using Umbraco.Core.Models.Membership; +using Umbraco.Core.Logging; +using Umbraco.Core.Security; +using Umbraco.Core.Services; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Models.Mapping; using Umbraco.Web.Mvc; -using Umbraco.Core.Security; using Umbraco.Web.Security; +using Umbraco.Web.Security.Identity; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; -using umbraco.providers; -using Microsoft.AspNet.Identity.Owin; -using Umbraco.Core.Logging; -using Newtonsoft.Json.Linq; -using Umbraco.Core.Models.Identity; -using Umbraco.Web.Security.Identity; using IUser = Umbraco.Core.Models.Membership.IUser; namespace Umbraco.Web.Editors { - /// /// The API controller used for editing content /// @@ -163,9 +151,7 @@ namespace Umbraco.Web.Editors [SetAngularAntiForgeryTokens] public async Task PostLogin(LoginModel loginModel) { - var http = this.TryGetHttpContext(); - if (http.Success == false) - throw new InvalidOperationException("This method requires that an HttpContext be active"); + var http = EnsureHttpContext(); var result = await SignInManager.PasswordSignInAsync( loginModel.Username, loginModel.Password, isPersistent: true, shouldLockout: true); @@ -231,6 +217,106 @@ namespace Umbraco.Web.Editors } } + /// + /// Processes a password reset request. Looks for a match on the provided email address + /// and if found sends an email with a link to reset it + /// + /// + [SetAngularAntiForgeryTokens] + public async Task PostRequestPasswordReset(RequestPasswordResetModel model) + { + // If this feature is switched off in configuration the UI will be amended to not make the request to reset password available. + // So this is just a server-side secondary check. + if (UmbracoConfig.For.UmbracoSettings().Security.AllowPasswordReset == false) + { + throw new HttpResponseException(HttpStatusCode.BadRequest); + } + + var http = EnsureHttpContext(); + + var identityUser = await SignInManager.UserManager.FindByEmailAsync(model.Email); + if (identityUser != null) + { + var user = Services.UserService.GetByEmail(model.Email); + if (user != null && user.IsLockedOut == false) + { + var code = await UserManager.GeneratePasswordResetTokenAsync(identityUser.Id); + var callbackUrl = ConstuctCallbackUrl(http.Request.Url, identityUser.Id, code); + var message = ConstructPasswordResetEmailMessage(user, callbackUrl); + await UserManager.SendEmailAsync(identityUser.Id, + Services.TextService.Localize("login/resetPasswordEmailCopySubject"), + message); + } + } + + return Request.CreateResponse(HttpStatusCode.OK); + } + + private static string ConstuctCallbackUrl(Uri url, int userId, string code) + { + return string.Format("{0}://{1}/umbraco/#/login?userId={2}&resetCode={3}", + url.Scheme, + url.Host + (url.Port == 80 ? string.Empty : ":" + url.Port), + userId, + HttpUtility.UrlEncode(code)); + } + + private string ConstructPasswordResetEmailMessage(IUser user, string callbackUrl) + { + var emailCopy1 = Services.TextService.Localize("login/resetPasswordEmailCopyFormat1"); + var emailCopy2 = Services.TextService.Localize("login/resetPasswordEmailCopyFormat2"); + var message = string.Format("

      " + emailCopy1 + "

      \n\n" + + "

      " + emailCopy2 + "

      ", + user.Username, + callbackUrl); + return message; + } + + /// + /// Processes a password reset request. Looks for a match on the provided email address + /// and if found sends an email with a link to reset it + /// + /// + [SetAngularAntiForgeryTokens] + public async Task PostValidatePasswordResetCode(ValidatePasswordResetCodeModel model) + { + var user = UserManager.FindById(model.UserId); + if (user != null) + { + var result = await UserManager.UserTokenProvider.ValidateAsync("ResetPassword", + model.ResetCode, UserManager, user); + if (result) + { + return Request.CreateResponse(HttpStatusCode.OK); + } + } + + return Request.CreateValidationErrorResponse("Password reset code not valid"); + } + + /// + /// Processes a set password request. Validates the request and sets a new password. + /// + /// + [SetAngularAntiForgeryTokens] + public async Task PostSetPassword(SetPasswordModel model) + { + var result = await UserManager.ResetPasswordAsync(model.UserId, model.ResetCode, model.Password); + if (result.Succeeded) + { + return Request.CreateResponse(HttpStatusCode.OK); + } + + return Request.CreateValidationErrorResponse("Set password failed"); + } + + private HttpContextBase EnsureHttpContext() + { + var attempt = this.TryGetHttpContext(); + if (attempt.Success == false) + throw new InvalidOperationException("This method requires that an HttpContext be active"); + return attempt.Result; + } /// /// Logs the current user out diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 0b7a611fbb..297221dacc 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -361,6 +361,7 @@ namespace Umbraco.Web.Editors }, {"keepUserLoggedIn", UmbracoConfig.For.UmbracoSettings().Security.KeepUserLoggedIn}, {"cssPath", IOHelper.ResolveUrl(SystemDirectories.Css).TrimEnd('/')}, + {"allowPasswordReset", UmbracoConfig.For.UmbracoSettings().Security.AllowPasswordReset}, } }, { diff --git a/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs b/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs index 3f38ae35ec..2dc9841293 100644 --- a/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs @@ -12,6 +12,8 @@ using Umbraco.Web.Editors; namespace Umbraco.Web { + using Umbraco.Core.Configuration; + /// /// HtmlHelper extensions for the back office /// @@ -43,6 +45,9 @@ namespace Umbraco.Web ""serverVarsJs"": """ + uri.GetUrlWithCacheBust("ServerVariables", "BackOffice") + @""", ""externalLoginsUrl"": """ + externalLoginsUrl + @""" }, + ""umbracoSettings"": { + ""allowPasswordReset"": " + (UmbracoConfig.For.UmbracoSettings().Security.AllowPasswordReset ? "true" : "false") + @" + }, ""application"": { ""applicationPath"": """ + html.ViewContext.HttpContext.Request.ApplicationPath + @""", ""version"": """ + version + @""", diff --git a/src/Umbraco.Web/Models/RequestPasswordResetModel.cs b/src/Umbraco.Web/Models/RequestPasswordResetModel.cs new file mode 100644 index 0000000000..650dd8ed11 --- /dev/null +++ b/src/Umbraco.Web/Models/RequestPasswordResetModel.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models +{ + public class RequestPasswordResetModel + { + [Required] + [DataMember(Name = "email", IsRequired = true)] + public string Email { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/SetPasswordModel.cs b/src/Umbraco.Web/Models/SetPasswordModel.cs new file mode 100644 index 0000000000..cc70989d66 --- /dev/null +++ b/src/Umbraco.Web/Models/SetPasswordModel.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models +{ + public class SetPasswordModel + { + [Required] + [DataMember(Name = "userId", IsRequired = true)] + public int UserId { get; set; } + + [Required] + [DataMember(Name = "password", IsRequired = true)] + public string Password { get; set; } + + [Required] + [DataMember(Name = "resetCode", IsRequired = true)] + public string ResetCode { get; set; } + } +} diff --git a/src/Umbraco.Web/Models/ValidatePasswordResetCodeModel.cs b/src/Umbraco.Web/Models/ValidatePasswordResetCodeModel.cs new file mode 100644 index 0000000000..61db6907ef --- /dev/null +++ b/src/Umbraco.Web/Models/ValidatePasswordResetCodeModel.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models +{ + public class ValidatePasswordResetCodeModel + { + [Required] + [DataMember(Name = "userId", IsRequired = true)] + public int UserId { get; set; } + + [Required] + [DataMember(Name = "resetCode", IsRequired = true)] + public string ResetCode { get; set; } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 3aeffcfe16..16b220b232 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -341,6 +341,9 @@ + + + diff --git a/src/umbraco.businesslogic/UmbracoSettings.cs b/src/umbraco.businesslogic/UmbracoSettings.cs index 3d489cfaa4..d73ea22844 100644 --- a/src/umbraco.businesslogic/UmbracoSettings.cs +++ b/src/umbraco.businesslogic/UmbracoSettings.cs @@ -102,6 +102,14 @@ namespace umbraco get { return UmbracoConfig.For.UmbracoSettings().Security.HideDisabledUsersInBackoffice; } } + /// + /// Enable the UI and API to allow back-office users to reset their passwords? Default is true + /// + public static bool AllowPasswordReset + { + get { return UmbracoConfig.For.UmbracoSettings().Security.AllowPasswordReset; } + } + /// /// Gets a value indicating whether the logs will be auto cleaned /// From d424b13ac41e83186760af172a4a18be2663abc3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Apr 2016 18:59:39 +0200 Subject: [PATCH 076/101] Fixes some styling, wording and validation --- .../src/less/pages/login.less | 11 +++-- .../views/common/dialogs/login.controller.js | 21 +++++++-- .../src/views/common/dialogs/login.html | 44 ++++++++++--------- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 4 +- .../umbraco/config/lang/en_us.xml | 4 +- 5 files changed, 52 insertions(+), 32 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/login.less b/src/Umbraco.Web.UI.Client/src/less/pages/login.less index ff7fbc243a..6805783a86 100644 --- a/src/Umbraco.Web.UI.Client/src/less/pages/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/pages/login.less @@ -75,10 +75,7 @@ } .login-overlay .switch-view { - margin-top: 10px; - a { - color: #fff; - } + margin-top: 10px; } @media (max-width: 565px) { @@ -115,3 +112,9 @@ margin: auto; color: @grayLight; } + +.login-overlay .text-error, +.login-overlay .text-info +{ + font-weight:bold; +} diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js index d76d66a7c4..71dbb8902f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js @@ -7,29 +7,44 @@ }); } + function resetInputValidation() { + if ($scope.loginForm) { + $scope.loginForm.username.$setValidity('auth', true); + $scope.loginForm.password.$setValidity('auth', true); + } + if ($scope.requestPasswordResetForm) { + $scope.requestPasswordResetForm.email.$setValidity("auth", true); + } + if ($scope.setPasswordForm) { + $scope.setPasswordForm.password.$setValidity('auth', true); + $scope.setPasswordForm.confirmPassword.$setValidity('auth', true); + } + } + $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset; $scope.showLogin = function () { $scope.errorMsg = ""; + resetInputValidation(); $scope.view = "login"; setFieldFocus("loginForm", "username"); } $scope.showRequestPasswordReset = function () { $scope.errorMsg = ""; + resetInputValidation(); $scope.view = "request-password-reset"; $scope.showEmailResetConfirmation = false; setFieldFocus("requestPasswordResetForm", "email"); } $scope.showSetPassword = function () { + $scope.errorMsg = ""; + resetInputValidation(); $scope.view = "set-password"; setFieldFocus("setPasswordForm", "password"); } - - - var d = new Date(); var konamiGreetings = new Array("Suze Sunday", "Malibu Monday", "Tequila Tuesday", "Whiskey Wednesday", "Negroni Day", "Fernet Friday", "Sancerre Saturday"); var konamiMode = $cookies.konamiLogin; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html index 992b98942e..76b90a8250 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html @@ -1,5 +1,5 @@ 
      -
      +

      {{greeting}}

      @@ -12,7 +12,7 @@
      -
      +
      {{error}}
      @@ -46,15 +46,15 @@
      + +
      +
      {{errorMsg}}
      +
      -
      -
      {{errorMsg}}
      -
      -
      @@ -68,21 +68,21 @@
      - - - +
      -
      {{errorMsg}}
      +
      {{errorMsg}}
      - +
      -
      +
      If your email address has been matched an email with password reset instructions has been sent.
      + +
      @@ -101,17 +101,19 @@
      - - - +
      -
      {{errorMsg}}
      +
      {{errorMsg}}
      -

      - Your new password has been set and you may now use it to log in. +

      +

      + Your new password has been set and you may now use it to log in. +

      + + @@ -119,7 +121,7 @@
      -
      +
      The link you have clicked on is invalid or has expired.
      diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 95f68b1219..cc75ea0038 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -663,8 +663,8 @@ To manage your website, simply open the Umbraco back office and start adding con Session timed out © 2001 - %0%
      Umbraco.com

      ]]>
      Forgotten password? - Please enter your email address. If your account is located an email will be sent to you containing a link from which you can reset your password. - If your email address has been matched an email with password reset instructions has been sent. + An email will be sent to the address specified with a link to reset your password + An email with password reset instructions will be sent to the specified address if it matched our records Return to login form Please provide a new password. Your new password has been set and you may now use it to log in. diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 24380a5680..23c627830c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -661,8 +661,8 @@ To manage your website, simply open the Umbraco back office and start adding con Session timed out © 2001 - %0%
      Umbraco.com

      ]]>
      Forgotten password? - Please enter your email address. If your account is located an email will be sent to you containing a link from which you can reset your password. - If your email address has been matched an email with password reset instructions has been sent. + An email will be sent to the address specified with a link to reset your password + An email with password reset instructions will be sent to the specified address if it matched our records Return to login form Please provide a new password. Your new password has been set and you may now use it to log in. From f279000d00029e311ed2d9c60fa5c35348a8af62 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Apr 2016 19:54:04 +0200 Subject: [PATCH 077/101] removes unecessary methods from user.service.js, ensures correct error msg when resetting password on the server, fixes email copy and allows for non-html emails if the copy is plain text, removes the need for more than one email msg in the lang files and uses the correct way to replace tokens, --- .../Security/BackOfficeUserManager.cs | 3 +- src/Umbraco.Core/Security/EmailService.cs | 5 ++- .../src/common/resources/auth.resource.js | 9 +++-- .../src/common/services/user.service.js | 34 +------------------ .../views/common/dialogs/login.controller.js | 21 +++++++++--- .../src/views/common/dialogs/login.html | 25 +++++++------- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 11 +++--- .../umbraco/config/lang/en_us.xml | 11 +++--- .../Editors/AuthenticationController.cs | 18 +++------- 9 files changed, 58 insertions(+), 79 deletions(-) diff --git a/src/Umbraco.Core/Security/BackOfficeUserManager.cs b/src/Umbraco.Core/Security/BackOfficeUserManager.cs index ba7e615771..e48079c10b 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserManager.cs @@ -118,6 +118,8 @@ namespace Umbraco.Core.Security //custom identity factory for creating the identity object for which we auth against in the back office manager.ClaimsIdentityFactory = new BackOfficeClaimsIdentityFactory(); + manager.EmailService = new EmailService(); + //NOTE: Not implementing these, if people need custom 2 factor auth, they'll need to implement their own UserStore to suport it //// Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user @@ -132,7 +134,6 @@ namespace Umbraco.Core.Security // BodyFormat = "Your security code is: {0}" //}); - manager.EmailService = new EmailService(); //manager.SmsService = new SmsService(); } diff --git a/src/Umbraco.Core/Security/EmailService.cs b/src/Umbraco.Core/Security/EmailService.cs index 0a4ca95ceb..93864aa37c 100644 --- a/src/Umbraco.Core/Security/EmailService.cs +++ b/src/Umbraco.Core/Security/EmailService.cs @@ -14,7 +14,10 @@ namespace Umbraco.Core.Security mailMessage.Body = message.Body; mailMessage.To.Add(message.Destination); mailMessage.Subject = message.Subject; - mailMessage.IsBodyHtml = true; + + //TODO: This check could be nicer but that is the way it is currently + mailMessage.IsBodyHtml = message.Body.IsNullOrWhiteSpace() == false + && message.Body.Contains("<") && message.Body.Contains("/>"); await client.SendMailAsync(mailMessage); } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js index b170df3c97..30585d326d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js @@ -79,7 +79,10 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { errorMsg: 'Email address cannot be empty' }); } - + + //TODO: This validation shouldn't really be done here, the validation on the login dialog + // is pretty hacky which is why this is here, ideally validation on the login dialog would + // be done properly. var emailRegex = /\S+@\S+\.\S+/; if (!emailRegex.test(email)) { return angularHelper.rejectedPromise({ @@ -140,7 +143,7 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { userId: userId, resetCode: resetCode }), - 'Password reset code validation failed for userId + ' + userId + ', code' + resetCode); + 'Password reset code validation failed for userId ' + userId + ', code' + resetCode); }, /** @@ -201,7 +204,7 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { password: password, resetCode: resetCode }), - 'Password reset code validation failed for userId + ' + userId); + 'Password reset code validation failed for userId ' + userId); }, unlinkLogin: function (loginProvider, providerKey) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 27f0165f4d..dffda24f1d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -211,39 +211,7 @@ angular.module('umbraco.services') return result; }); }, - - /** Returns a promise, sends a request to the server to send a password reset email if the email is recognised */ - requestPasswordReset: function (email) { - - return authResource.performRequestPasswordReset(email) - .then(function (data) { - // Note that we don't actually confirm if the email address was matched or not, to avoid - // allowing an attacker to determine which email addresses are valid. - var result = { success: true }; - return result; - }); - }, - - /** Returns a promise, sends a request to the server to validate a password reset code */ - validatePasswordResetCode: function (userId, resetCode) { - - return authResource.performValidatePasswordResetCode(userId, resetCode) - .then(function (data) { - var result = { success: data }; - return result; - }); - }, - - /** Returns a promise, sends a request to the server to validate a password reset code */ - setPassword: function (userId, password, confirmPassword, resetCode) { - - return authResource.performSetPassword(userId, password, confirmPassword, resetCode) - .then(function (data) { - var result = { success: data }; - return result; - }); - }, - + /** Logs the user out */ logout: function () { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js index 71dbb8902f..ff9981eba1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js @@ -1,5 +1,5 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LoginController", - function ($scope, $cookies, localizationService, userService, externalLoginInfo, $timeout, $location) { + function ($scope, $cookies, localizationService, userService, externalLoginInfo, $timeout, $location, authResource) { var setFieldFocus = function(form, field) { $timeout(function() { @@ -8,6 +8,9 @@ } function resetInputValidation() { + $scope.confirmPassword = ""; + $scope.password = ""; + $scope.login = ""; if ($scope.loginForm) { $scope.loginForm.username.$setValidity('auth', true); $scope.loginForm.password.$setValidity('auth', true); @@ -79,7 +82,7 @@ var userId = $location.search().userId; var resetCode = $location.search().resetCode; if (userId && resetCode) { - userService.validatePasswordResetCode(userId, resetCode) + authResource.performValidatePasswordResetCode(userId, resetCode) .then(function () { $scope.showSetPassword(); }, function () { @@ -139,8 +142,10 @@ return; } - userService.requestPasswordReset(email) + authResource.performRequestPasswordReset(email) .then(function () { + //remove the email entered + $scope.email = ""; $scope.showEmailResetConfirmation = true; }, function (reason) { $scope.errorMsg = reason.errorMsg; @@ -167,11 +172,17 @@ return; } - userService.setPassword(userId, password, confirmPassword, resetCode) + authResource.performSetPassword(userId, password, confirmPassword, resetCode) .then(function () { $scope.showSetPasswordConfirmation = true; + $scope.resetComplete = true; }, function (reason) { - $scope.errorMsg = reason.errorMsg; + if (reason.data && reason.data.Message) { + $scope.errorMsg = reason.data.Message; + } + else { + $scope.errorMsg = reason.errorMsg; + } $scope.setPasswordForm.password.$setValidity("auth", false); $scope.setPasswordForm.confirmPassword.$setValidity("auth", false); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html index 76b90a8250..f97588cee0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html @@ -61,7 +61,7 @@

      - Please enter your email address. If your account is located an email will be sent to you containing a link from which you can reset your password. + An email will be sent to the address specified with a link to reset your password

      @@ -75,7 +75,7 @@
      - If your email address has been matched an email with password reset instructions has been sent. + An email with password reset instructions will be sent to the specified address if it matched our records
      @@ -88,34 +88,35 @@
      -

      + +

      Please provide a new password.

      -
      - +
      +
      -
      +
      -
      +
      {{errorMsg}}
      -

      +

      Your new password has been set and you may now use it to log in.
      -

      +
      - +
      @@ -125,7 +126,7 @@ The link you have clicked on is invalid or has expired.
      diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index cc75ea0038..a5ac0ed911 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -666,12 +666,13 @@ To manage your website, simply open the Umbraco back office and start adding con An email will be sent to the address specified with a link to reset your password An email with password reset instructions will be sent to the specified address if it matched our records Return to login form - Please provide a new password. - Your new password has been set and you may now use it to log in. - The link you have clicked on is invalid or has expired. + Please provide a new password + Your new password has been set and you may now use it to log in + The link you have clicked on is invalid or has expired Umbraco: Reset Password - Your username to login to the Umbraco back-office is: {0} - here. ]]> + + Your username to login to the Umbraco back-office is: %0%

      Click here to reset your password or copy/paste this URL into your browser:

      %1%

      ]]> +
      Dashboard diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 23c627830c..4384b3577c 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -664,12 +664,13 @@ To manage your website, simply open the Umbraco back office and start adding con An email will be sent to the address specified with a link to reset your password An email with password reset instructions will be sent to the specified address if it matched our records Return to login form - Please provide a new password. - Your new password has been set and you may now use it to log in. - The link you have clicked on is invalid or has expired. + Please provide a new password + Your new password has been set and you may now use it to log in + The link you have clicked on is invalid or has expired Umbraco: Reset Password - Your username to login to the Umbraco back-office is: {0} - here. ]]> + + Your username to login to the Umbraco back-office is: %0%

      Click here to reset your password or copy/paste this URL into your browser:

      %1%

      ]]> +
      Dashboard diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 09027c58f4..50deec6360 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -242,7 +242,7 @@ namespace Umbraco.Web.Editors { var code = await UserManager.GeneratePasswordResetTokenAsync(identityUser.Id); var callbackUrl = ConstuctCallbackUrl(http.Request.Url, identityUser.Id, code); - var message = ConstructPasswordResetEmailMessage(user, callbackUrl); + var message = Services.TextService.Localize("resetPasswordEmailCopyFormat", new[] {identityUser.UserName, callbackUrl}); await UserManager.SendEmailAsync(identityUser.Id, Services.TextService.Localize("login/resetPasswordEmailCopySubject"), message); @@ -259,18 +259,7 @@ namespace Umbraco.Web.Editors url.Host + (url.Port == 80 ? string.Empty : ":" + url.Port), userId, HttpUtility.UrlEncode(code)); - } - - private string ConstructPasswordResetEmailMessage(IUser user, string callbackUrl) - { - var emailCopy1 = Services.TextService.Localize("login/resetPasswordEmailCopyFormat1"); - var emailCopy2 = Services.TextService.Localize("login/resetPasswordEmailCopyFormat2"); - var message = string.Format("

      " + emailCopy1 + "

      \n\n" + - "

      " + emailCopy2 + "

      ", - user.Username, - callbackUrl); - return message; - } + } /// /// Processes a password reset request. Looks for a match on the provided email address @@ -307,7 +296,8 @@ namespace Umbraco.Web.Editors return Request.CreateResponse(HttpStatusCode.OK); } - return Request.CreateValidationErrorResponse("Set password failed"); + return Request.CreateValidationErrorResponse( + result.Errors.Any() ? result.Errors.First() : "Set password failed"); } private HttpContextBase EnsureHttpContext() From e610a5ef54c2a67146c1b6b58281af164da57d9e Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 13 Apr 2016 13:51:12 +0200 Subject: [PATCH 078/101] Changes the password reset link to be a real link (not an angular deep link), this means there is less logging of the reset code in a query string and less visibility of it, this also means that the validation of the code happens instantly. The premise for this is the same as how we deal with external authentication requests and uses ViewData/TempData with redirects. Fixes the models to have the correct attributes to be able to directly json serialize them. --- .../src/common/resources/auth.resource.js | 2 +- .../views/common/dialogs/login.controller.js | 33 +++++---- .../src/views/common/dialogs/login.html | 5 +- .../umbraco/Views/Default.cshtml | 4 +- .../Editors/AuthenticationController.cs | 51 ++++++-------- .../Editors/BackOfficeController.cs | 67 ++++++++++++++----- .../HtmlHelperBackOfficeExtensions.cs | 38 ++++++++++- .../Models/RequestPasswordResetModel.cs | 2 + src/Umbraco.Web/Models/SetPasswordModel.cs | 1 + .../Models/ValidatePasswordResetCodeModel.cs | 1 + src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 11 files changed, 132 insertions(+), 74 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js index 30585d326d..20ebaa10c0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/auth.resource.js @@ -170,7 +170,7 @@ function authResource($q, $http, umbRequestHelper, angularHelper) { */ performSetPassword: function (userId, password, confirmPassword, resetCode) { - if (!userId) { + if (userId === undefined || userId === null) { return angularHelper.rejectedPromise({ errorMsg: 'User Id cannot be empty' }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js index ff9981eba1..1b1b3bb402 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js @@ -1,5 +1,5 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LoginController", - function ($scope, $cookies, localizationService, userService, externalLoginInfo, $timeout, $location, authResource) { + function ($scope, $cookies, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource) { var setFieldFocus = function(form, field) { $timeout(function() { @@ -63,6 +63,7 @@ $scope.externalLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLoginsUrl; $scope.externalLoginProviders = externalLoginInfo.providers; $scope.externalLoginInfo = externalLoginInfo; + $scope.resetPasswordCodeInfo = resetPasswordCodeInfo; $scope.activateKonamiMode = function () { if ($cookies.konamiLogin == "1") { @@ -77,21 +78,6 @@ } } - // Set initial view - either set password if reset code provided in querystring - // otherwise login form - var userId = $location.search().userId; - var resetCode = $location.search().resetCode; - if (userId && resetCode) { - authResource.performValidatePasswordResetCode(userId, resetCode) - .then(function () { - $scope.showSetPassword(); - }, function () { - $scope.view = "password-reset-code-expired"; - }); - } else { - $scope.showLogin(); - } - $scope.loginSubmit = function (login, password) { //if the login and password are not empty we need to automatically @@ -172,7 +158,7 @@ return; } - authResource.performSetPassword(userId, password, confirmPassword, resetCode) + authResource.performSetPassword($scope.resetPasswordCodeInfo.resetCodeModel.userId, password, confirmPassword, $scope.resetPasswordCodeInfo.resetCodeModel.resetCode) .then(function () { $scope.showSetPasswordConfirmation = true; $scope.resetComplete = true; @@ -199,4 +185,17 @@ }); } + + //Now, show the correct panel: + + if ($scope.resetPasswordCodeInfo.resetCodeModel) { + $scope.showSetPassword(); + } + else if ($scope.resetPasswordCodeInfo.errors.length > 0) { + $scope.view = "password-reset-code-expired"; + } + else { + $scope.showLogin(); + } + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html index f97588cee0..12f25f42a5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html @@ -122,9 +122,10 @@
      -
      - The link you have clicked on is invalid or has expired. +
      + {{error}}
      + diff --git a/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml b/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml index a63fd3c670..07063934b3 100644 --- a/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml +++ b/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml @@ -72,8 +72,8 @@ diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 50deec6360..c03ff624a4 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Threading.Tasks; using System.Web; using System.Web.Http; +using System.Web.Mvc; using AutoMapper; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; @@ -99,7 +100,7 @@ namespace Umbraco.Web.Editors /// Checks if the current user's cookie is valid and if so returns OK or a 400 (BadRequest) ///
      /// - [HttpGet] + [System.Web.Http.HttpGet] public bool IsAuthenticated() { var attempt = UmbracoContext.Security.AuthorizeRequest(); @@ -231,9 +232,6 @@ namespace Umbraco.Web.Editors { throw new HttpResponseException(HttpStatusCode.BadRequest); } - - var http = EnsureHttpContext(); - var identityUser = await SignInManager.UserManager.FindByEmailAsync(model.Email); if (identityUser != null) { @@ -241,7 +239,7 @@ namespace Umbraco.Web.Editors if (user != null && user.IsLockedOut == false) { var code = await UserManager.GeneratePasswordResetTokenAsync(identityUser.Id); - var callbackUrl = ConstuctCallbackUrl(http.Request.Url, identityUser.Id, code); + var callbackUrl = ConstuctCallbackUrl(identityUser.Id, code); var message = Services.TextService.Localize("resetPasswordEmailCopyFormat", new[] {identityUser.UserName, callbackUrl}); await UserManager.SendEmailAsync(identityUser.Id, Services.TextService.Localize("login/resetPasswordEmailCopySubject"), @@ -252,37 +250,28 @@ namespace Umbraco.Web.Editors return Request.CreateResponse(HttpStatusCode.OK); } - private static string ConstuctCallbackUrl(Uri url, int userId, string code) + private string ConstuctCallbackUrl(int userId, string code) { - return string.Format("{0}://{1}/umbraco/#/login?userId={2}&resetCode={3}", - url.Scheme, - url.Host + (url.Port == 80 ? string.Empty : ":" + url.Port), - userId, - HttpUtility.UrlEncode(code)); - } + //get an mvc helper to get the url + var http = EnsureHttpContext(); + var urlHelper = new UrlHelper(http.Request.RequestContext); - /// - /// Processes a password reset request. Looks for a match on the provided email address - /// and if found sends an email with a link to reset it - /// - /// - [SetAngularAntiForgeryTokens] - public async Task PostValidatePasswordResetCode(ValidatePasswordResetCodeModel model) - { - var user = UserManager.FindById(model.UserId); - if (user != null) - { - var result = await UserManager.UserTokenProvider.ValidateAsync("ResetPassword", - model.ResetCode, UserManager, user); - if (result) + var action = urlHelper.Action("ValidatePasswordResetCode", "BackOffice", + new { - return Request.CreateResponse(HttpStatusCode.OK); - } - } + area = GlobalSettings.UmbracoMvcArea, + u = userId, + r = code + }); - return Request.CreateValidationErrorResponse("Password reset code not valid"); - } + //TODO: Virtual path? + return string.Format("{0}://{1}{2}", + http.Request.Url.Scheme, + http.Request.Url.Host + (http.Request.Url.Port == 80 ? string.Empty : ":" + http.Request.Url.Port), + action); + } + /// /// Processes a set password request. Validates the request and sets a new password. /// diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 297221dacc..b5cea7a945 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -4,6 +4,8 @@ using System.Configuration; using System.Globalization; using System.IO; using System.Linq; +using System.Net; +using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -26,6 +28,7 @@ using Umbraco.Core.Manifest; using Umbraco.Core.Models; using Umbraco.Core.Models.Identity; using Umbraco.Core.Security; +using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Umbraco.Web.PropertyEditors; @@ -34,6 +37,7 @@ using Umbraco.Web.Trees; using Umbraco.Web.UI.JavaScript; using Umbraco.Web.WebApi.Filters; using Umbraco.Web.WebServices; +using Umbraco.Core.Services; using Action = umbraco.BusinessLogic.Actions.Action; using Constants = Umbraco.Core.Constants; @@ -49,6 +53,10 @@ namespace Umbraco.Web.Editors private BackOfficeUserManager _userManager; private BackOfficeSignInManager _signInManager; + private const string TokenExternalSignInError = "ExternalSignInError"; + private const string TokenPasswordResetCode = "PasswordResetCode"; + private static readonly string[] TempDataTokenNames = { TokenExternalSignInError, TokenPasswordResetCode }; + protected BackOfficeSignInManager SignInManager { get { return _signInManager ?? (_signInManager = OwinContext.Get()); } @@ -431,7 +439,25 @@ namespace Umbraco.Web.Editors User.Identity.GetUserId()); } + [HttpGet] + public async Task ValidatePasswordResetCode([Bind(Prefix = "u")]int userId, [Bind(Prefix = "r")]string resetCode) + { + var user = UserManager.FindById(userId); + if (user != null) + { + var result = await UserManager.UserTokenProvider.ValidateAsync("ResetPassword", resetCode, UserManager, user); + if (result) + { + //Add a flag and redirect for it to be displayed + TempData[TokenPasswordResetCode] = new ValidatePasswordResetCodeModel {UserId = userId, ResetCode = resetCode}; + return RedirectToLocal(Url.Action("Default", "BackOffice")); + } + } + //Add error and redirect for it to be displayed + TempData[TokenPasswordResetCode] = new[] { Services.TextService.Localize("login/resetCodeExpired") }; + return RedirectToLocal(Url.Action("Default", "BackOffice")); + } [HttpGet] public async Task ExternalLinkLoginCallback() @@ -443,7 +469,7 @@ namespace Umbraco.Web.Editors if (loginInfo == null) { //Add error and redirect for it to be displayed - TempData["ExternalSignInError"] = new[] { "An error occurred, could not get external login info" }; + TempData[TokenExternalSignInError] = new[] { "An error occurred, could not get external login info" }; return RedirectToLocal(Url.Action("Default", "BackOffice")); } @@ -454,27 +480,32 @@ namespace Umbraco.Web.Editors } //Add errors and redirect for it to be displayed - TempData["ExternalSignInError"] = result.Errors; + TempData[TokenExternalSignInError] = result.Errors; return RedirectToLocal(Url.Action("Default", "BackOffice")); } /// - /// Used by Default and AuthorizeUpgrade to render as per normal if there's no external login info, otherwise - /// process the external login info. + /// Used by Default and AuthorizeUpgrade to render as per normal if there's no external login info, + /// otherwise process the external login info. /// - /// - private async Task RenderDefaultOrProcessExternalLoginAsync(Func defaultResponse, Func externalSignInResponse) + /// + private async Task RenderDefaultOrProcessExternalLoginAsync( + Func defaultResponse, + Func externalSignInResponse) { if (defaultResponse == null) throw new ArgumentNullException("defaultResponse"); if (externalSignInResponse == null) throw new ArgumentNullException("externalSignInResponse"); ViewBag.UmbracoPath = GlobalSettings.UmbracoMvcArea; - //check if there's errors in the TempData, assign to view bag and render the view - if (TempData["ExternalSignInError"] != null) - { - ViewBag.ExternalSignInError = TempData["ExternalSignInError"]; - return defaultResponse(); + //check if there is the TempData with the any token name specified, if so, assign to view bag and render the view + foreach (var tempDataTokenName in TempDataTokenNames) + { + if (TempData[tempDataTokenName] != null) + { + ViewData[tempDataTokenName] = TempData[tempDataTokenName]; + return defaultResponse(); + } } //First check if there's external login info, if there's not proceed as normal @@ -512,7 +543,7 @@ namespace Umbraco.Web.Editors { if (await AutoLinkAndSignInExternalAccount(loginInfo) == false) { - ViewBag.ExternalSignInError = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not been linked to to an account" }; + ViewData[TokenExternalSignInError] = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not been linked to to an account" }; } //Remove the cookie otherwise this message will keep appearing @@ -547,7 +578,7 @@ namespace Umbraco.Web.Editors //we are allowing auto-linking/creating of local accounts if (loginInfo.Email.IsNullOrWhiteSpace()) { - ViewBag.ExternalSignInError = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not provided an email address, the account cannot be linked." }; + ViewData[TokenExternalSignInError] = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not provided an email address, the account cannot be linked." }; } else { @@ -556,7 +587,7 @@ namespace Umbraco.Web.Editors var foundByEmail = Services.UserService.GetByEmail(loginInfo.Email); if (foundByEmail != null) { - ViewBag.ExternalSignInError = new[] { "A user with this email address already exists locally. You will need to login locally to Umbraco and link this external provider: " + loginInfo.Login.LoginProvider }; + ViewData[TokenExternalSignInError] = new[] { "A user with this email address already exists locally. You will need to login locally to Umbraco and link this external provider: " + loginInfo.Login.LoginProvider }; } else { @@ -564,7 +595,7 @@ namespace Umbraco.Web.Editors var userType = Services.UserService.GetUserTypeByAlias(defaultUserType); if (userType == null) { - ViewBag.ExternalSignInError = new[] { "Could not auto-link this account, the specified User Type does not exist: " + defaultUserType }; + ViewData[TokenExternalSignInError] = new[] { "Could not auto-link this account, the specified User Type does not exist: " + defaultUserType }; } else { @@ -592,21 +623,21 @@ namespace Umbraco.Web.Editors if (userCreationResult.Succeeded == false) { - ViewBag.ExternalSignInError = userCreationResult.Errors; + ViewData[TokenExternalSignInError] = userCreationResult.Errors; } else { var linkResult = await UserManager.AddLoginAsync(autoLinkUser.Id, loginInfo.Login); if (linkResult.Succeeded == false) { - ViewBag.ExternalSignInError = linkResult.Errors; + ViewData[TokenExternalSignInError] = linkResult.Errors; //If this fails, we should really delete the user since it will be in an inconsistent state! var deleteResult = await UserManager.DeleteAsync(autoLinkUser); if (deleteResult.Succeeded == false) { //DOH! ... this isn't good, combine all errors to be shown - ViewBag.ExternalSignInError = linkResult.Errors.Concat(deleteResult.Errors); + ViewData[TokenExternalSignInError] = linkResult.Errors.Concat(deleteResult.Errors); } } else diff --git a/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs b/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs index 2dc9841293..71fc3be8f1 100644 --- a/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperBackOfficeExtensions.cs @@ -9,6 +9,7 @@ using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Web.Editors; +using Umbraco.Web.Models; namespace Umbraco.Web { @@ -61,12 +62,12 @@ namespace Umbraco.Web } /// - /// Used to render the script that will pass in the angular externalLoginInfo service on page load + /// Used to render the script that will pass in the angular "externalLoginInfo" service/value on page load /// /// /// /// - public static IHtmlString AngularExternalLoginInfoValuesScript(this HtmlHelper html, IEnumerable externalLoginErrors) + public static IHtmlString AngularValueExternalLoginInfoScript(this HtmlHelper html, IEnumerable externalLoginErrors) { var loginProviders = html.ViewContext.HttpContext.GetOwinContext().Authentication.GetExternalAuthenticationTypes() .Where(p => p.Properties.ContainsKey("UmbracoBackOffice")) @@ -98,5 +99,38 @@ namespace Umbraco.Web return html.Raw(sb.ToString()); } + + /// + /// Used to render the script that will pass in the angular "resetPasswordCodeInfo" service/value on page load + /// + /// + /// + /// + public static IHtmlString AngularValueResetPasswordCodeInfoScript(this HtmlHelper html, object val) + { + var sb = new StringBuilder(); + sb.AppendLine(); + sb.AppendLine(@"var errors = [];"); + + var errors = val as IEnumerable; + if (errors != null) + { + foreach (var error in errors) + { + sb.AppendFormat(@"errors.push(""{0}"");", error).AppendLine(); + } + } + + var resetCodeModel = val as ValidatePasswordResetCodeModel; + + + sb.AppendLine(@"app.value(""resetPasswordCodeInfo"", {"); + sb.AppendLine(@"errors: errors,"); + sb.Append(@"resetCodeModel: "); + sb.AppendLine(JsonConvert.SerializeObject(resetCodeModel)); + sb.AppendLine(@"});"); + + return html.Raw(sb.ToString()); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/RequestPasswordResetModel.cs b/src/Umbraco.Web/Models/RequestPasswordResetModel.cs index 650dd8ed11..0ea173bfd6 100644 --- a/src/Umbraco.Web/Models/RequestPasswordResetModel.cs +++ b/src/Umbraco.Web/Models/RequestPasswordResetModel.cs @@ -3,6 +3,8 @@ using System.Runtime.Serialization; namespace Umbraco.Web.Models { + + [DataContract(Name = "requestPasswordReset", Namespace = "")] public class RequestPasswordResetModel { [Required] diff --git a/src/Umbraco.Web/Models/SetPasswordModel.cs b/src/Umbraco.Web/Models/SetPasswordModel.cs index cc70989d66..02d0e4f901 100644 --- a/src/Umbraco.Web/Models/SetPasswordModel.cs +++ b/src/Umbraco.Web/Models/SetPasswordModel.cs @@ -3,6 +3,7 @@ using System.Runtime.Serialization; namespace Umbraco.Web.Models { + [DataContract(Name = "setPassword", Namespace = "")] public class SetPasswordModel { [Required] diff --git a/src/Umbraco.Web/Models/ValidatePasswordResetCodeModel.cs b/src/Umbraco.Web/Models/ValidatePasswordResetCodeModel.cs index 61db6907ef..cba92eeff7 100644 --- a/src/Umbraco.Web/Models/ValidatePasswordResetCodeModel.cs +++ b/src/Umbraco.Web/Models/ValidatePasswordResetCodeModel.cs @@ -3,6 +3,7 @@ using System.Runtime.Serialization; namespace Umbraco.Web.Models { + [DataContract(Name = "validatePasswordReset", Namespace = "")] public class ValidatePasswordResetCodeModel { [Required] diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 16b220b232..48005b1209 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -342,9 +342,9 @@ - + From ce0ecd81147b7fb624919dedb49aa618370f2b90 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 13 Apr 2016 14:35:40 +0200 Subject: [PATCH 079/101] Ensures that the emails sent our are in the culture of the user, fixes issue of logging in after resetting password and then logging out again --- .../src/views/common/dialogs/login.controller.js | 4 ++++ src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 2 +- src/Umbraco.Web/Editors/AuthenticationController.cs | 12 ++++++++++-- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js index 1b1b3bb402..c38c55fcfb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.controller.js @@ -162,6 +162,10 @@ .then(function () { $scope.showSetPasswordConfirmation = true; $scope.resetComplete = true; + + //reset the values in the resetPasswordCodeInfo angular so if someone logs out the change password isn't shown again + resetPasswordCodeInfo.resetCodeModel = null; + }, function (reason) { if (reason.data && reason.data.Message) { $scope.errorMsg = reason.data.Message; diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index a5ac0ed911..93a570efe2 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -667,7 +667,7 @@ To manage your website, simply open the Umbraco back office and start adding con An email with password reset instructions will be sent to the specified address if it matched our records Return to login form Please provide a new password - Your new password has been set and you may now use it to log in + You Password has been updated! The link you have clicked on is invalid or has expired Umbraco: Reset Password diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 4384b3577c..6bcca5db86 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -665,7 +665,7 @@ To manage your website, simply open the Umbraco back office and start adding con An email with password reset instructions will be sent to the specified address if it matched our records Return to login form Please provide a new password - Your new password has been set and you may now use it to log in + You Password has been updated! The link you have clicked on is invalid or has expired Umbraco: Reset Password diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index c03ff624a4..1cfd0312c2 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -14,6 +14,7 @@ using Microsoft.Owin; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Models; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Web.Models; @@ -240,9 +241,16 @@ namespace Umbraco.Web.Editors { var code = await UserManager.GeneratePasswordResetTokenAsync(identityUser.Id); var callbackUrl = ConstuctCallbackUrl(identityUser.Id, code); - var message = Services.TextService.Localize("resetPasswordEmailCopyFormat", new[] {identityUser.UserName, callbackUrl}); + + var message = Services.TextService.Localize("resetPasswordEmailCopyFormat", + //Ensure the culture of the found user is used for the email! + UserExtensions.GetUserCulture(identityUser.Culture, Services.TextService), + new[] {identityUser.UserName, callbackUrl}); + await UserManager.SendEmailAsync(identityUser.Id, - Services.TextService.Localize("login/resetPasswordEmailCopySubject"), + Services.TextService.Localize("login/resetPasswordEmailCopySubject", + //Ensure the culture of the found user is used for the email! + UserExtensions.GetUserCulture(identityUser.Culture, Services.TextService)), message); } } From 10bb16316d8b1c5ca2150d2e2e1133a1947f20d6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 13 Apr 2016 14:49:37 +0200 Subject: [PATCH 080/101] Updates sorting code to support decimals - need to test on MySql --- .../Repositories/VersionableRepositoryBase.cs | 10 ++++++---- .../Persistence/SqlSyntax/ISqlSyntaxProvider.cs | 1 + .../Persistence/SqlSyntax/MySqlSyntaxProvider.cs | 3 ++- .../Persistence/SqlSyntax/SqlSyntaxProviderBase.cs | 1 + 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 2d55a3dd05..0215ba27bb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -274,12 +274,14 @@ namespace Umbraco.Core.Persistence.Repositories var sortedInt = string.Format(SqlSyntax.ConvertIntegerToOrderableString, "dataInt"); var sortedDate = string.Format(SqlSyntax.ConvertDateToOrderableString, "dataDate"); var sortedString = string.Format(SqlSyntax.IsNull, "dataNvarchar", "''"); + var sortedDecimal = string.Format(SqlSyntax.ConvertDecimalToOrderableString, "dataInt"); var orderBySql = string.Format(@"ORDER BY ( SELECT CASE - WHEN dataInt Is Not Null THEN {0} - WHEN dataDate Is Not Null THEN {1} - ELSE {2} + WHEN dataInt Is Not Null THEN {0} + WHEN dataDecimal Is Not Null THEN {1} + WHEN dataDate Is Not Null THEN {2} + ELSE {3} END FROM cmsContent c INNER JOIN cmsContentVersion cv ON cv.ContentId = c.nodeId AND VersionDate = ( @@ -290,7 +292,7 @@ namespace Umbraco.Core.Persistence.Repositories INNER JOIN cmsPropertyData cpd ON cpd.contentNodeId = c.nodeId AND cpd.versionId = cv.VersionId INNER JOIN cmsPropertyType cpt ON cpt.Id = cpd.propertytypeId - WHERE c.nodeId = umbracoNode.Id and cpt.Alias = @0)", sortedInt, sortedDate, sortedString); + WHERE c.nodeId = umbracoNode.Id and cpt.Alias = @0)", sortedInt, sortedDecimal, sortedDate, sortedString); sortedSql.Append(orderBySql, orderBy); if (orderDirection == Direction.Descending) diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index e663c8e64b..c70b6a571a 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -73,6 +73,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax string IsNull { get; } string ConvertIntegerToOrderableString { get; } string ConvertDateToOrderableString { get; } + string ConvertDecimalToOrderableString { get; } IEnumerable GetTablesInSchema(Database db); IEnumerable GetColumnsInSchema(Database db); diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index d02a5fb8dc..d4585f5aa1 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -363,7 +363,8 @@ ORDER BY TABLE_NAME, INDEX_NAME", public override string IsNull { get { return "IFNULL({0},{1})"; } } public override string ConvertIntegerToOrderableString { get { return "LPAD({0}, 8, '0')"; } } public override string ConvertDateToOrderableString { get { return "DATE_FORMAT({0}, '%Y%m%d')"; } } - + public override string ConvertDecimalToOrderableString { get { return "LPAD({0}, 25, '0')"; } } + public override bool? SupportsCaseInsensitiveQueries(Database db) { bool? supportsCaseInsensitiveQueries = null; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index 35c133ce6e..b7b58d929f 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -540,5 +540,6 @@ namespace Umbraco.Core.Persistence.SqlSyntax public virtual string IsNull { get { return "ISNULL({0},{1})"; } } public virtual string ConvertIntegerToOrderableString { get { return "RIGHT('00000000' + CAST({0} AS varchar(8)),8)"; } } public virtual string ConvertDateToOrderableString { get { return "CONVERT(varchar, {0}, 102)"; } } + public virtual string ConvertDecimalToOrderableString { get { return "RIGHT('0000000000000000000000000' + CAST({0} AS varchar(25)),25)"; } } } } \ No newline at end of file From c9c391e477290b172232fc60e71f6bbeb444a785 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 13 Apr 2016 15:05:23 +0200 Subject: [PATCH 081/101] fixed type, decimal sorting now works --- .../Persistence/Repositories/VersionableRepositoryBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 0215ba27bb..934555ac6e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -274,7 +274,7 @@ namespace Umbraco.Core.Persistence.Repositories var sortedInt = string.Format(SqlSyntax.ConvertIntegerToOrderableString, "dataInt"); var sortedDate = string.Format(SqlSyntax.ConvertDateToOrderableString, "dataDate"); var sortedString = string.Format(SqlSyntax.IsNull, "dataNvarchar", "''"); - var sortedDecimal = string.Format(SqlSyntax.ConvertDecimalToOrderableString, "dataInt"); + var sortedDecimal = string.Format(SqlSyntax.ConvertDecimalToOrderableString, "dataDecimal"); var orderBySql = string.Format(@"ORDER BY ( SELECT CASE From c3b4a9128ad41f3f174a7e955ffb7a09d32a9182 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 13 Apr 2016 15:47:21 +0200 Subject: [PATCH 082/101] ensures all GetPagedDescendant methods are mocked - fixes tests. --- .../UmbracoExamine/IndexInitializer.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index aea5243058..9990c58a43 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -49,7 +49,8 @@ namespace Umbraco.Tests.UmbracoExamine } if (mediaService == null) { - long totalRecs; + long longTotalRecs; + int intTotalRecs; var allRecs = dataService.MediaService.GetLatestMediaByXpath("//node") .Root @@ -74,7 +75,15 @@ namespace Umbraco.Tests.UmbracoExamine mediaService = Mock.Of( x => x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out totalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()) + It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()) + == + allRecs + && x.GetPagedDescendants( + It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny()) + == + allRecs + && x.GetPagedDescendants( + It.IsAny(), It.IsAny(), It.IsAny(), out intTotalRecs, It.IsAny(), It.IsAny(), It.IsAny()) == allRecs); From 7886b7814d45ceaea975cd82054c9840baeead32 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 13 Apr 2016 15:57:27 +0200 Subject: [PATCH 083/101] U4-8312 - models generation false error (#1222) --- .../src/views/documenttypes/edit.controller.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js index 8132f50b58..0b304aaef4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js @@ -145,7 +145,8 @@ contentTypeHelper.generateModels().then(function (result) { - if (result.success) { + // generateModels() returns the dashboard content + if (!result.lastError) { //re-check model status contentTypeHelper.checkModelsBuilderStatus().then(function(statusResult) { From b8d8c9e59a2ad16a48b5a57467ffd0f6bbc16c5b Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 13 Apr 2016 16:41:45 +0200 Subject: [PATCH 084/101] fixes merge - but the sorting on custom field will not currently work, need to fix that now. --- .../Repositories/ContentRepository.cs | 4 +- .../Repositories/MediaRepository.cs | 2 +- .../Repositories/MemberRepository.cs | 2 +- .../Repositories/VersionableRepositoryBase.cs | 82 ++++++++++++++----- src/Umbraco.Core/Services/ContentService.cs | 41 ---------- src/Umbraco.Core/Services/IContentService.cs | 14 +--- src/Umbraco.Core/Services/IMediaService.cs | 14 +--- src/Umbraco.Core/Services/MediaService.cs | 44 +--------- .../UmbracoExamine/IndexInitializer.cs | 4 - .../Editors/AuthenticationController.cs | 3 + 10 files changed, 72 insertions(+), 138 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 7222217606..fb0121f8dd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -252,7 +252,7 @@ namespace Umbraco.Core.Persistence.Repositories // 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, //TODO: Think this needs a true here); + MapQueryDtos, "Path", Direction.Ascending, true); var xmlItems = (from descendant in descendants let xml = serializer(descendant) @@ -789,7 +789,7 @@ namespace Umbraco.Core.Persistence.Repositories return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, MapQueryDtos, - orderBy, orderDirection, + orderBy, orderDirection, orderBySystemField, filterSql); } diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 4be1e2e4e5..62257f08e2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -499,7 +499,7 @@ namespace Umbraco.Core.Persistence.Repositories : Sql().Append("AND (umbracoNode." + SqlSyntax.GetQuotedColumnName("text") + " LIKE @0)", "%" + filter + "%"); return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - MapQueryDtos, orderBy, orderDirection, + MapQueryDtos, orderBy, orderDirection, orderBySystemField, filterSql); } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index c1afbee590..51fac1149c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -631,7 +631,7 @@ namespace Umbraco.Core.Persistence.Repositories "OR (cmsMember.LoginName LIKE @0))", "%" + filter + "%"); return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - MapQueryDtos, orderBy, orderDirection, TODO: Do we need a orderBySystemField here?, + MapQueryDtos, orderBy, orderDirection, orderBySystemField, filterSql); } diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 53a3884431..b32336e9b2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -255,7 +255,7 @@ namespace Umbraco.Core.Persistence.Repositories } - private Sql PrepareSqlForPagedResults(Sql sql, Sql filterSql, string orderBy, Direction orderDirection) + private Sql PrepareSqlForPagedResults(Sql sql, Sql filterSql, string orderBy, Direction orderDirection, bool orderBySystemField) { if (filterSql == null && string.IsNullOrEmpty(orderBy)) return sql; @@ -271,29 +271,67 @@ namespace Umbraco.Core.Persistence.Repositories // apply sort if (string.IsNullOrEmpty(orderBy) == false) { - // get the database field eg "[table].[column]" - var dbfield = GetDatabaseFieldNameForOrderBy(orderBy); + if (orderBySystemField) + { + // get the database field eg "[table].[column]" + var dbfield = GetDatabaseFieldNameForOrderBy(orderBy); - // for SqlServer pagination to work, the "order by" field needs to be the alias eg if - // the select statement has "umbracoNode.text AS NodeDto__Text" then the order field needs - // to be "NodeDto__Text" and NOT "umbracoNode.text". - // not sure about SqlCE nor MySql, so better do it too. initially thought about patching - // NPoco but that would be expensive and not 100% possible, so better give NPoco proper - // queries to begin with. - // thought about maintaining a map of columns-to-aliases in the sql context but that would - // be expensive and most of the time, useless. so instead we parse the SQL looking for the - // alias. somewhat expensive too but nothing's free. + // for SqlServer pagination to work, the "order by" field needs to be the alias eg if + // the select statement has "umbracoNode.text AS NodeDto__Text" then the order field needs + // to be "NodeDto__Text" and NOT "umbracoNode.text". + // not sure about SqlCE nor MySql, so better do it too. initially thought about patching + // NPoco but that would be expensive and not 100% possible, so better give NPoco proper + // queries to begin with. + // thought about maintaining a map of columns-to-aliases in the sql context but that would + // be expensive and most of the time, useless. so instead we parse the SQL looking for the + // alias. somewhat expensive too but nothing's free. - var matches = VersionableRepositoryBaseAliasRegex.For(SqlSyntax).Matches(sql.SQL); - var match = matches.Cast().FirstOrDefault(m => m.Groups[1].Value == dbfield); - if (match != null) - dbfield = match.Groups[2].Value; + var matches = VersionableRepositoryBaseAliasRegex.For(SqlSyntax).Matches(sql.SQL); + var match = matches.Cast().FirstOrDefault(m => m.Groups[1].Value == dbfield); + if (match != null) + dbfield = match.Groups[2].Value; - var orderByParams = new object[] { dbfield }; - if (orderDirection == Direction.Ascending) - psql.OrderBy(orderByParams); + var orderByParams = new object[] {dbfield}; + if (orderDirection == Direction.Ascending) + psql.OrderBy(orderByParams); + else + psql.OrderByDescending(orderByParams); + } else - psql.OrderByDescending(orderByParams); + { + // Sorting by a custom field, so set-up sub-query for ORDER BY clause to pull through valie + // from most recent content version for the given order by field + var sortedInt = string.Format(SqlSyntax.ConvertIntegerToOrderableString, "dataInt"); + var sortedDate = string.Format(SqlSyntax.ConvertDateToOrderableString, "dataDate"); + var sortedString = string.Format(SqlSyntax.IsNull, "dataNvarchar", "''"); + var sortedDecimal = string.Format(SqlSyntax.ConvertDecimalToOrderableString, "dataDecimal"); + + var orderBySql = string.Format(@"ORDER BY ( + SELECT CASE + WHEN dataInt Is Not Null THEN {0} + WHEN dataDecimal Is Not Null THEN {1} + WHEN dataDate Is Not Null THEN {2} + ELSE {3} + END + FROM cmsContent c + INNER JOIN cmsContentVersion cv ON cv.ContentId = c.nodeId AND VersionDate = ( + SELECT Max(VersionDate) + FROM cmsContentVersion + WHERE ContentId = c.nodeId + ) + INNER JOIN cmsPropertyData cpd ON cpd.contentNodeId = c.nodeId + AND cpd.versionId = cv.VersionId + INNER JOIN cmsPropertyType cpt ON cpt.Id = cpd.propertytypeId + WHERE c.nodeId = umbracoNode.Id and cpt.Alias = @0)", sortedInt, sortedDecimal, sortedDate, sortedString); + + throw new NotImplementedException("FIX ME!"); + + //sortedSql.Append(orderBySql, orderBy); + //if (orderDirection == Direction.Descending) + //{ + // sortedSql.Append(" DESC"); + //} + } } return psql; @@ -301,7 +339,7 @@ namespace Umbraco.Core.Persistence.Repositories protected IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, Func, IEnumerable> mapper, - string orderBy, Direction orderDirection, + string orderBy, Direction orderDirection, bool orderBySystemField, Sql filterSql = null) { if (orderBy == null) throw new ArgumentNullException(nameof(orderBy)); @@ -313,7 +351,7 @@ namespace Umbraco.Core.Persistence.Repositories var sqlNodeIds = translator.Translate(); // sort and filter - sqlNodeIds = PrepareSqlForPagedResults(sqlNodeIds, filterSql, orderBy, orderDirection); + sqlNodeIds = PrepareSqlForPagedResults(sqlNodeIds, filterSql, orderBy, orderDirection, orderBySystemField); // get a page of DTOs and the total count var pagedResult = Database.Page(pageIndex + 1, pageSize, sqlNodeIds); diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 0cf7263246..f213a584be 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -487,27 +487,6 @@ namespace Umbraco.Core.Services } } - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, - string orderBy, Direction orderDirection, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - //if the id is System Root, then just get all - if (id != Constants.System.Root) - { - repository.Query.Where(x => x.ParentId == id); - } - long total; - var contents = repository.GetPagedResultsByQuery(repository.Query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); - totalChildren = Convert.ToInt32(total); - return contents; - } - } - /// /// Gets a collection of objects by Parent Id /// @@ -557,26 +536,6 @@ namespace Umbraco.Core.Services } } - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork())) - { - //if the id is System Root, then just get all - if (id != Constants.System.Root) - { - repository.Query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); - } - long total; - var contents = repository.GetPagedResultsByQuery(repository.Query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); - totalChildren = Convert.ToInt32(total); - return contents; - } - } - /// /// Gets a collection of objects by Parent Id /// diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 8c58f57a83..625f917ffe 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -215,12 +215,7 @@ namespace Umbraco.Core.Services /// Id of the Parent to retrieve Children from /// An Enumerable list of objects IEnumerable GetChildren(int id); - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); - + /// /// Gets a collection of objects by Parent Id /// @@ -249,12 +244,7 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, bool orderBySystemField, string filter); - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); - + /// /// Gets a collection of objects by Parent Id /// diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index 6ff8f75402..645bcfd1e0 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -117,12 +117,7 @@ namespace Umbraco.Core.Services /// Id of the Parent to retrieve Children from /// An Enumerable list of objects IEnumerable GetChildren(int id); - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); - + /// /// Gets a collection of objects by Parent Id /// @@ -151,12 +146,7 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, bool orderBySystemField, string filter); - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); - + /// /// Gets a collection of objects by Parent Id /// diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 18b7b7f9ec..2a459174c0 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -395,26 +395,6 @@ namespace Umbraco.Core.Services } } - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedChildren(int id, int pageIndex, int pageSize, out int totalChildren, - string orderBy, Direction orderDirection, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - var query = repository.Query; - query.Where(x => x.ParentId == id); - - long total; - var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); - - totalChildren = Convert.ToInt32(total); - return medias; - } - } - /// /// Gets a collection of objects by Parent Id /// @@ -458,29 +438,7 @@ namespace Umbraco.Core.Services return medias; } - } - - [Obsolete("Use the overload with 'long' parameter types instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var repository = RepositoryFactory.CreateMediaRepository(UowProvider.GetUnitOfWork())) - { - - var query = repository.Query; - //if the id is -1, then just get all - if (id != -1) - { - query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); - } - long total; - var contents = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out total, orderBy, orderDirection, filter); - totalChildren = Convert.ToInt32(total); - return contents; - } - } + } /// /// Gets a collection of objects by Parent Id diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index d0b2f98229..5dbb1ee29f 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -82,10 +82,6 @@ namespace Umbraco.Tests.UmbracoExamine && x.GetPagedDescendants( It.IsAny(), It.IsAny(), It.IsAny(), out longTotalRecs, It.IsAny(), It.IsAny(), It.IsAny()) == - allRecs - && x.GetPagedDescendants( - It.IsAny(), It.IsAny(), It.IsAny(), out intTotalRecs, It.IsAny(), It.IsAny(), It.IsAny()) - == allRecs); } diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 71c0a79b8f..0b9b555287 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -4,7 +4,9 @@ using System.Net; using System.Net.Http; using System.Collections.Generic; using System.Threading.Tasks; +using System.Web; using System.Web.Http; +using System.Web.Mvc; using AutoMapper; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; @@ -21,6 +23,7 @@ using Umbraco.Web.Security.Identity; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; namespace Umbraco.Web.Editors { From 38f6a7a8b69846e23988b0097dc484f8ac6f2a35 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 13 Apr 2016 16:45:01 +0200 Subject: [PATCH 085/101] fixed typo --- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 93a570efe2..08521f4580 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -667,7 +667,7 @@ To manage your website, simply open the Umbraco back office and start adding con An email with password reset instructions will be sent to the specified address if it matched our records Return to login form Please provide a new password - You Password has been updated! + Your Password has been updated The link you have clicked on is invalid or has expired Umbraco: Reset Password From a015032442bfacbd68a59b1d6ec6ae7d1052f050 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 13 Apr 2016 16:45:47 +0200 Subject: [PATCH 086/101] fixed typo --- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 6bcca5db86..6b78e66e7f 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -665,7 +665,7 @@ To manage your website, simply open the Umbraco back office and start adding con An email with password reset instructions will be sent to the specified address if it matched our records Return to login form Please provide a new password - You Password has been updated! + Your Password has been updated The link you have clicked on is invalid or has expired Umbraco: Reset Password From 9e3941e8e1337e7c65f53ce32506a4c0eb51c93b Mon Sep 17 00:00:00 2001 From: Aleksey Fedotov <0xc0dec@gmail.com> Date: Wed, 13 Apr 2016 22:14:21 +0500 Subject: [PATCH 087/101] Fixed logger context class --- src/Umbraco.Web/Scheduling/KeepAlive.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Scheduling/KeepAlive.cs b/src/Umbraco.Web/Scheduling/KeepAlive.cs index 0557e56e57..380ae85401 100644 --- a/src/Umbraco.Web/Scheduling/KeepAlive.cs +++ b/src/Umbraco.Web/Scheduling/KeepAlive.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web.Scheduling // ensure we do not run if not main domain, but do NOT lock it if (_appContext.MainDom.IsMainDom == false) { - LogHelper.Debug("Does not run if not MainDom."); + LogHelper.Debug("Does not run if not MainDom."); return false; // do NOT repeat, going down } From bf70e208adfc2ffce7bc86f86e9f2e8aac4f8111 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 14 Apr 2016 09:34:53 +0200 Subject: [PATCH 088/101] U4-8325 XSLT macro rendering logs too much in Info mode (after upgrade 7.1.4 => 7.4.0) --- src/Umbraco.Web/umbraco.presentation/macro.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/umbraco.presentation/macro.cs b/src/Umbraco.Web/umbraco.presentation/macro.cs index 66ba6fea15..77027749e2 100644 --- a/src/Umbraco.Web/umbraco.presentation/macro.cs +++ b/src/Umbraco.Web/umbraco.presentation/macro.cs @@ -1589,7 +1589,7 @@ namespace umbraco //Trace out to profiling... doesn't actually profile, just for informational output. if (excludeProfiling == false) { - using (ApplicationContext.Current.ProfilingLogger.TraceDuration(string.Format("{0}", message))) + using (ApplicationContext.Current.ProfilingLogger.DebugDuration(string.Format("{0}", message))) { } } From fcaa4ad8d686dfd4c90be09d10bc6b136dc3e5fa Mon Sep 17 00:00:00 2001 From: Per Ploug Date: Thu, 14 Apr 2016 11:36:24 +0200 Subject: [PATCH 089/101] JsDocs comments on listviewhelper (U4-7185) --- .../common/services/listviewhelper.service.js | 239 ++++++++++++++++-- 1 file changed, 221 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js index e211218ce1..c21ca4e313 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/listviewhelper.service.js @@ -1,3 +1,47 @@ +/** + @ngdoc service + * @name umbraco.services.listViewHelper + * + * + * @description + * Service for performing operations against items in the list view UI. Used by the built-in internal listviews + * as well as custom listview. + * + * A custom listview is always used inside a wrapper listview, so there are a number of inherited values on its + * scope by default: + * + * **$scope.selection**: Array containing all items currently selected in the listview + * + * **$scope.items**: Array containing all items currently displayed in the listview + * + * **$scope.folders**: Array containing all folders in the current listview (only for media) + * + * **$scope.options**: configuration object containing information such as pagesize, permissions, order direction etc. + * + * **$scope.model.config.layouts**: array of available layouts to apply to the listview (grid, list or custom layout) + * + * ##Usage## + * To use, inject listViewHelper into custom listview controller, listviewhelper expects you + * to pass in the full collection of items in the listview in several of its methods + * this collection is inherited from the parent controller and is available on $scope.selection + * + *
      + *      angular.module("umbraco").controller("my.listVieweditor". function($scope, listViewHelper){
      + *
      + *          //current items in the listview
      + *          var items = $scope.items;
      + *
      + *          //current selection
      + *          var selection = $scope.selection;
      + *
      + *          //deselect an item , $scope.selection is inherited, item is picked from inherited $scope.items
      + *          listViewHelper.deselectItem(item, $scope.selection);
      + *
      + *          //test if all items are selected, $scope.items + $scope.selection are inherited
      + *          listViewhelper.isSelectedAll($scope.items, $scope.selection);
      + *      });
      + * 
      + */ (function () { 'use strict'; @@ -6,6 +50,19 @@ var firstSelectedIndex = 0; var localStorageKey = "umblistViewLayout"; + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#getLayout + * @methodOf umbraco.services.listViewHelper + * + * @description + * Method for internal use, based on the collection of layouts passed, the method selects either + * any previous layout from local storage, or picks the first allowed layout + * + * @param {Number} nodeId The id of the current node displayed in the content editor + * @param {Array} availableLayouts Array of all allowed layouts, available from $scope.model.config.layouts + */ + function getLayout(nodeId, availableLayouts) { var storedLayouts = []; @@ -28,6 +85,19 @@ } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#setLayout + * @methodOf umbraco.services.listViewHelper + * + * @description + * Changes the current layout used by the listview to the layout passed in. Stores selection in localstorage + * + * @param {Number} nodeID Id of the current node displayed in the content editor + * @param {Object} selectedLayout Layout selected as the layout to set as the current layout + * @param {Array} availableLayouts Array of all allowed layouts, available from $scope.model.config.layouts + */ + function setLayout(nodeId, selectedLayout, availableLayouts) { var activeLayout = {}; @@ -54,6 +124,18 @@ } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#saveLayoutInLocalStorage + * @methodOf umbraco.services.listViewHelper + * + * @description + * Stores a given layout as the current default selection in local storage + * + * @param {Number} nodeId Id of the current node displayed in the content editor + * @param {Object} selectedLayout Layout selected as the layout to set as the current layout + */ + function saveLayoutInLocalStorage(nodeId, selectedLayout) { var layoutFound = false; var storedLayouts = []; @@ -84,6 +166,17 @@ } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#getFirstAllowedLayout + * @methodOf umbraco.services.listViewHelper + * + * @description + * Returns currently selected layout, or alternatively the first layout in the available layouts collection + * + * @param {Array} layouts Array of all allowed layouts, available from $scope.model.config.layouts + */ + function getFirstAllowedLayout(layouts) { var firstAllowedLayout = {}; @@ -99,6 +192,23 @@ return firstAllowedLayout; } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#selectHandler + * @methodOf umbraco.services.listViewHelper + * + * @description + * Helper method for working with item selection via a checkbox, internally it uses selectItem and deselectItem. + * Working with this method, requires its triggered via a checkbox which can then pass in its triggered $event + * When the checkbox is clicked, this method will toggle selection of the associated item so it matches the state of the checkbox + * + * @param {Object} selectedItem Item being selected or deselected by the checkbox + * @param {Number} selectedIndex Index of item being selected/deselected, usually passed as $index + * @param {Array} items All items in the current listview, available as $scope.items + * @param {Array} selection All selected items in the current listview, available as $scope.selection + * @param {Event} $event Event triggered by the checkbox being checked to select / deselect an item + */ + function selectHandler(selectedItem, selectedIndex, items, selection, $event) { var start = 0; @@ -143,6 +253,18 @@ } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#selectItem + * @methodOf umbraco.services.listViewHelper + * + * @description + * Selects a given item to the listview selection array, requires you pass in the inherited $scope.selection collection + * + * @param {Object} item Item to select + * @param {Array} selection Listview selection, available as $scope.selection + */ + function selectItem(item, selection) { var isSelected = false; for (var i = 0; selection.length > i; i++) { @@ -157,6 +279,18 @@ } } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#deselectItem + * @methodOf umbraco.services.listViewHelper + * + * @description + * Deselects a given item from the listviews selection array, requires you pass in the inherited $scope.selection collection + * + * @param {Object} item Item to deselect + * @param {Array} selection Listview selection, available as $scope.selection + */ + function deselectItem(item, selection) { for (var i = 0; selection.length > i; i++) { var selectedItem = selection[i]; @@ -167,6 +301,20 @@ } } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#clearSelection + * @methodOf umbraco.services.listViewHelper + * + * @description + * Removes a given number of items and folders from the listviews selection array + * Folders can only be passed in if the listview is used in the media section which has a concept of folders. + * + * @param {Array} items Items to remove, can be null + * @param {Array} folders Folders to remove, can be null + * @param {Array} selection Listview selection, available as $scope.selection + */ + function clearSelection(items, folders, selection) { var i = 0; @@ -180,7 +328,7 @@ } } - if (angular.isArray(items)) { + if(angular.isArray(folders)) { for (i = 0; folders.length > i; i++) { var folder = folders[i]; folder.selected = false; @@ -188,6 +336,20 @@ } } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#selectAllItems + * @methodOf umbraco.services.listViewHelper + * + * @description + * Helper method for toggling the select state on all items in the active listview + * Can only be used from a checkbox as a checkbox $event is required to pass in. + * + * @param {Array} items Items to toggle selection on, should be $scope.items + * @param {Array} selection Listview selection, available as $scope.selection + * @param {$event} $event Event passed from the checkbox being toggled + */ + function selectAllItems(items, selection, $event) { var checkbox = $event.target; @@ -219,6 +381,20 @@ } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#isSelectedAll + * @methodOf umbraco.services.listViewHelper + * + * @description + * Method to determine if all items on the current page in the list has been selected + * Given the current items in the view, and the current selection, it will return true/false + * + * @param {Array} items Items to test if all are selected, should be $scope.items + * @param {Array} selection Listview selection, available as $scope.selection + * @returns {Boolean} boolean indicate if all items in the listview have been selected + */ + function isSelectedAll(items, selection) { var numberOfSelectedItem = 0; @@ -242,10 +418,35 @@ } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#setSortingDirection + * @methodOf umbraco.services.listViewHelper + * + * @description + * *Internal* method for changing sort order icon + * @param {String} col Column alias to order after + * @param {String} direction Order direction `asc` or `desc` + * @param {Object} options object passed from the parent listview available as $scope.options + */ + function setSortingDirection(col, direction, options) { return options.orderBy.toUpperCase() === col.toUpperCase() && options.orderDirection === direction; } + /** + * @ngdoc method + * @name umbraco.services.listViewHelper#setSorting + * @methodOf umbraco.services.listViewHelper + * + * @description + * Method for setting the field on which the listview will order its items after. + * + * @param {String} field Field alias to order after + * @param {Boolean} allow Determines if the user is allowed to set this field, normally true + * @param {Object} options Options object passed from the parent listview available as $scope.options + */ + function setSorting(field, allow, options) { if (allow) { options.orderBy = field; @@ -257,12 +458,12 @@ } } } - + //This takes in a dictionary of Ids with Permissions and determines // the intersect of all permissions to return an object representing the // listview button permissions function getButtonPermissions(unmergedPermissions, currentIdsWithPermissions) { - + if (currentIdsWithPermissions == null) { currentIdsWithPermissions = {}; } @@ -286,26 +487,28 @@ canCopy: _.contains(intersectPermissions, 'O'), //Magic Char = O canCreate: _.contains(intersectPermissions, 'C'), //Magic Char = C canDelete: _.contains(intersectPermissions, 'D'), //Magic Char = D - canMove: _.contains(intersectPermissions, 'M'), //Magic Char = M + canMove: _.contains(intersectPermissions, 'M'), //Magic Char = M canPublish: _.contains(intersectPermissions, 'U'), //Magic Char = U - canUnpublish: _.contains(intersectPermissions, 'U'), //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish) + canUnpublish: _.contains(intersectPermissions, 'U'), //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish) }; } var service = { - getLayout: getLayout, - getFirstAllowedLayout: getFirstAllowedLayout, - setLayout: setLayout, - saveLayoutInLocalStorage: saveLayoutInLocalStorage, - selectHandler: selectHandler, - selectItem: selectItem, - deselectItem: deselectItem, - clearSelection: clearSelection, - selectAllItems: selectAllItems, - isSelectedAll: isSelectedAll, - setSortingDirection: setSortingDirection, - setSorting: setSorting, - getButtonPermissions: getButtonPermissions + + getLayout: getLayout, + getFirstAllowedLayout: getFirstAllowedLayout, + setLayout: setLayout, + saveLayoutInLocalStorage: saveLayoutInLocalStorage, + selectHandler: selectHandler, + selectItem: selectItem, + deselectItem: deselectItem, + clearSelection: clearSelection, + selectAllItems: selectAllItems, + isSelectedAll: isSelectedAll, + setSortingDirection: setSortingDirection, + setSorting: setSorting, + getButtonPermissions: getButtonPermissions + }; return service; From f149ca9c763135f3a7aab1e4b6dc2d56d5ff4455 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 20 Apr 2016 12:50:52 +0200 Subject: [PATCH 090/101] Fixes SqlCe and MySQL sorting on custom properties for list view, this also makes the query work much better and there is no longer a requirement for a sub-query in the order by statement. --- .../Repositories/VersionableRepositoryBase.cs | 43 ++++++------- .../SqlSyntax/ISqlSyntaxProvider.cs | 3 +- .../SqlSyntax/MySqlSyntaxProvider.cs | 5 +- .../SqlSyntax/SqlCeSyntaxProvider.cs | 1 - .../SqlSyntax/SqlSyntaxProviderBase.cs | 7 +-- .../Persistence/UmbracoDatabase.cs | 19 ++++-- .../Repositories/ContentRepositoryTest.cs | 60 +++++++++++++++++-- 7 files changed, 99 insertions(+), 39 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index b32336e9b2..62d7259065 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -303,34 +303,37 @@ namespace Umbraco.Core.Persistence.Repositories // from most recent content version for the given order by field var sortedInt = string.Format(SqlSyntax.ConvertIntegerToOrderableString, "dataInt"); var sortedDate = string.Format(SqlSyntax.ConvertDateToOrderableString, "dataDate"); - var sortedString = string.Format(SqlSyntax.IsNull, "dataNvarchar", "''"); + var sortedString = string.Format("COALESCE({0},'')", "dataNvarchar"); var sortedDecimal = string.Format(SqlSyntax.ConvertDecimalToOrderableString, "dataDecimal"); - var orderBySql = string.Format(@"ORDER BY ( + var innerJoinTempTable = string.Format(@"INNER JOIN ( SELECT CASE - WHEN dataInt Is Not Null THEN {0} + WHEN dataInt Is Not Null THEN {0} WHEN dataDecimal Is Not Null THEN {1} WHEN dataDate Is Not Null THEN {2} ELSE {3} - END - FROM cmsContent c - INNER JOIN cmsContentVersion cv ON cv.ContentId = c.nodeId AND VersionDate = ( - SELECT Max(VersionDate) - FROM cmsContentVersion - WHERE ContentId = c.nodeId - ) - INNER JOIN cmsPropertyData cpd ON cpd.contentNodeId = c.nodeId - AND cpd.versionId = cv.VersionId - INNER JOIN cmsPropertyType cpt ON cpt.Id = cpd.propertytypeId - WHERE c.nodeId = umbracoNode.Id and cpt.Alias = @0)", sortedInt, sortedDecimal, sortedDate, sortedString); + 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 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); - throw new NotImplementedException("FIX ME!"); + //insert this just above the LEFT OUTER JOIN + var newSql = psql.SQL.Insert(psql.SQL.IndexOf("LEFT OUTER JOIN"), innerJoinTempTable); + var newArgs = psql.Arguments.ToList(); + newArgs.Add(orderBy); - //sortedSql.Append(orderBySql, orderBy); - //if (orderDirection == Direction.Descending) - //{ - // sortedSql.Append(" DESC"); - //} + psql = new Sql(psql.SqlContext, newSql, newArgs.ToArray()); + + psql.OrderBy("CustomPropData.CustomPropVal"); + //psql.Append(innerJoinTempTable, orderBy); + if (orderDirection == Direction.Descending) + { + psql.Append(" DESC"); + } } } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index 33197dced8..fc26bcd7c9 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -70,8 +70,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax bool SupportsClustered(); bool SupportsIdentityInsert(); bool? SupportsCaseInsensitiveQueries(Database db); - - string IsNull { get; } + string ConvertIntegerToOrderableString { get; } string ConvertDateToOrderableString { get; } string ConvertDecimalToOrderableString { get; } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index f9a193d731..462f601323 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -361,10 +361,9 @@ ORDER BY TABLE_NAME, INDEX_NAME", public override string DropIndex { get { return "DROP INDEX {0} ON {1}"; } } public override string RenameColumn { get { return "ALTER TABLE {0} CHANGE {1} {2}"; } } - public override string IsNull { get { return "IFNULL({0},{1})"; } } - public override string ConvertIntegerToOrderableString { get { return "LPAD({0}, 8, '0')"; } } + public override string ConvertIntegerToOrderableString { get { return "LPAD(FORMAT({0}, 0), 8, '0')"; } } public override string ConvertDateToOrderableString { get { return "DATE_FORMAT({0}, '%Y%m%d')"; } } - public override string ConvertDecimalToOrderableString { get { return "LPAD({0}, 25, '0')"; } } + public override string ConvertDecimalToOrderableString { get { return "LPAD(FORMAT({0}, 9), 20, '0')"; } } public override bool? SupportsCaseInsensitiveQueries(Database db) { diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index 41b88abec9..4a3eb2e4a8 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -209,7 +209,6 @@ ORDER BY TABLE_NAME, INDEX_NAME"); public override string DropIndex { get { return "DROP INDEX {1}.{0}"; } } - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index 36aad20b29..a992cdd231 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -538,9 +538,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax public virtual string DeleteConstraint { get { return "ALTER TABLE {0} DROP CONSTRAINT {1}"; } } public virtual string CreateForeignKeyConstraint { get { return "ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4}){5}{6}"; } } - public virtual string IsNull { get { return "ISNULL({0},{1})"; } } - public virtual string ConvertIntegerToOrderableString { get { return "RIGHT('00000000' + CAST({0} AS varchar(8)),8)"; } } - public virtual string ConvertDateToOrderableString { get { return "CONVERT(varchar, {0}, 102)"; } } - public virtual string ConvertDecimalToOrderableString { get { return "RIGHT('0000000000000000000000000' + CAST({0} AS varchar(25)),25)"; } } + public virtual string ConvertIntegerToOrderableString { get { return "REPLACE(STR({0}, 8), SPACE(1), '0')"; } } + public virtual string ConvertDateToOrderableString { get { return "CONVERT(nvarchar, {0}, 102)"; } } + public virtual string ConvertDecimalToOrderableString { get { return "REPLACE(STR({0}, 20, 9), SPACE(1), '0')"; } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs index eaf637df4b..451fbf36d7 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Linq; +using System.Text; using NPoco; using StackExchange.Profiling; using Umbraco.Core.Logging; @@ -115,15 +116,25 @@ namespace Umbraco.Core.Persistence // if no timeout is specified, and the connection has a longer timeout, use it if (OneTimeCommandTimeout == 0 && CommandTimeout == 0 && cmd.Connection.ConnectionTimeout > 30) cmd.CommandTimeout = cmd.Connection.ConnectionTimeout; + + if (EnableSqlTrace) + { + var sb = new StringBuilder(); + sb.Append(cmd.CommandText); + foreach (DbParameter p in cmd.Parameters) + { + sb.Append(" - "); + sb.Append(p.Value); + } + + _logger.Debug(sb.ToString()); + } + base.OnExecutingCommand(cmd); } protected override void OnExecutedCommand(DbCommand cmd) { - if (EnableSqlTrace) - { - _logger.Debug(cmd.CommandText); - } if (_enableCount) { SqlCount++; diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index 65c5187120..a6a26c203d 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using System.Xml.Linq; using Moq; using NUnit.Framework; @@ -556,6 +557,44 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual("[alias2]", matches[1].Groups[2].Value); } + [Test] + public void Can_Perform_GetPagedResultsByQuery_Sorting_On_Custom_Property() + { + // Arrange + var provider = new NPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = repository.Query.Where(x => x.Name.Contains("Text")); + long totalRecords; + + //var origRegex = NPoco.PagingHelper.rxOrderBy; + try + { + //NPoco.PagingHelper.rxOrderBy = new Regex("(?!.*(?:\\s+FROM[\\s\\(]+))ORDER\\s+BY\\s+([\\w\\.\\[\\]\\(\\) \"`,'@\\s\\+\\=]+)(?!.*\\))", RegexOptions.IgnoreCase | RegexOptions.Compiled); + DatabaseContext.Database.EnableSqlTrace = true; + DatabaseContext.Database.EnableSqlCount(); + + var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "title", Direction.Ascending, false); + + Assert.AreEqual(3, totalRecords); + Assert.AreEqual(2, result.Count()); + + result = repository.GetPagedResultsByQuery(query, 1, 2, out totalRecords, "title", Direction.Ascending, false); + + Assert.AreEqual(1, result.Count()); + } + finally + { + //NPoco.PagingHelper.rxOrderBy = origRegex; + DatabaseContext.Database.EnableSqlTrace = false; + DatabaseContext.Database.DisableSqlCount(); + } + } + } + [Test] public void Can_Perform_GetPagedResultsByQuery_ForFirstPage_On_ContentRepository() { @@ -568,12 +607,23 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = repository.Query.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true); - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); + try + { + DatabaseContext.Database.EnableSqlTrace = true; + DatabaseContext.Database.EnableSqlCount(); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); + } + finally + { + DatabaseContext.Database.EnableSqlTrace = false; + DatabaseContext.Database.DisableSqlCount(); + } } } From 289d5f5741768cd156a1b6a2dfbcfc7191f8ad78 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 20 Apr 2016 12:51:42 +0200 Subject: [PATCH 091/101] removes the need for the custom regex for the order by since we no longer have an order by sub-query --- .../Persistence/Repositories/ContentRepositoryTest.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index a6a26c203d..f9244c3987 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -569,11 +569,9 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = repository.Query.Where(x => x.Name.Contains("Text")); long totalRecords; - - //var origRegex = NPoco.PagingHelper.rxOrderBy; + try { - //NPoco.PagingHelper.rxOrderBy = new Regex("(?!.*(?:\\s+FROM[\\s\\(]+))ORDER\\s+BY\\s+([\\w\\.\\[\\]\\(\\) \"`,'@\\s\\+\\=]+)(?!.*\\))", RegexOptions.IgnoreCase | RegexOptions.Compiled); DatabaseContext.Database.EnableSqlTrace = true; DatabaseContext.Database.EnableSqlCount(); @@ -587,8 +585,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(1, result.Count()); } finally - { - //NPoco.PagingHelper.rxOrderBy = origRegex; + { DatabaseContext.Database.EnableSqlTrace = false; DatabaseContext.Database.DisableSqlCount(); } From 612412683527dad1cdc3dbd4df61969b4695d7b0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 20 Apr 2016 15:50:38 +0200 Subject: [PATCH 092/101] Fixes the custom field sorting, no longer has a sub query for order by, now supports both mysql and sqlce, adds a unit test (should add more though) --- .../Repositories/ContentRepository.cs | 1 + .../Repositories/VersionableRepositoryBase.cs | 48 +++++++++------- .../SqlSyntax/ISqlSyntaxProvider.cs | 3 +- .../SqlSyntax/MySqlSyntaxProvider.cs | 5 +- .../SqlSyntax/SqlCeSyntaxProvider.cs | 1 - .../SqlSyntax/SqlSyntaxProviderBase.cs | 7 +-- .../Persistence/UmbracoDatabase.cs | 19 +++++-- .../Repositories/ContentRepositoryTest.cs | 57 +++++++++++++++++-- 8 files changed, 101 insertions(+), 40 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 270b209553..5f96cad114 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -105,6 +105,7 @@ namespace Umbraco.Core.Persistence.Repositories #region Overrides of PetaPocoRepositoryBase + protected override Sql GetBaseQuery(bool isCount) { var sqlx = string.Format("LEFT OUTER JOIN {0} {1} ON ({1}.{2}={0}.{2} AND {1}.{3}=1)", diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 934555ac6e..4b2befb3e3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -248,6 +248,7 @@ namespace Umbraco.Core.Persistence.Repositories private Sql GetSortedSqlForPagedResults(Sql sql, Direction orderDirection, string orderBy, bool orderBySystemField) { + //copy to var so that the original isn't changed var sortedSql = new Sql(sql.SQL, sql.Arguments); @@ -273,35 +274,40 @@ namespace Umbraco.Core.Persistence.Repositories // from most recent content version for the given order by field var sortedInt = string.Format(SqlSyntax.ConvertIntegerToOrderableString, "dataInt"); var sortedDate = string.Format(SqlSyntax.ConvertDateToOrderableString, "dataDate"); - var sortedString = string.Format(SqlSyntax.IsNull, "dataNvarchar", "''"); + var sortedString = string.Format("COALESCE({0},'')", "dataNvarchar"); var sortedDecimal = string.Format(SqlSyntax.ConvertDecimalToOrderableString, "dataDecimal"); - var orderBySql = string.Format(@"ORDER BY ( - SELECT CASE - WHEN dataInt Is Not Null THEN {0} - WHEN dataDecimal Is Not Null THEN {1} - WHEN dataDate Is Not Null THEN {2} - ELSE {3} - END - FROM cmsContent c - INNER JOIN cmsContentVersion cv ON cv.ContentId = c.nodeId AND VersionDate = ( - SELECT Max(VersionDate) - FROM cmsContentVersion - WHERE ContentId = c.nodeId - ) - INNER JOIN cmsPropertyData cpd ON cpd.contentNodeId = c.nodeId - AND cpd.versionId = cv.VersionId - INNER JOIN cmsPropertyType cpt ON cpt.Id = cpd.propertytypeId - WHERE c.nodeId = umbracoNode.Id and cpt.Alias = @0)", sortedInt, sortedDecimal, sortedDate, sortedString); + var innerJoinTempTable = string.Format(@"INNER JOIN ( + SELECT CASE + WHEN dataInt Is Not Null THEN {0} + WHEN dataDecimal Is Not Null THEN {1} + WHEN dataDate Is Not Null THEN {2} + ELSE {3} + 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 cmsPropertyType cpt ON cpt.Id = cpd.propertytypeId + WHERE cpt.Alias = @{4} AND cd.newest = 1) AS CustomPropData + ON CustomPropData.CustomPropValContentId = umbracoNode.id + ", sortedInt, sortedDecimal, sortedDate, sortedString, sortedSql.Arguments.Length); - sortedSql.Append(orderBySql, orderBy); + //insert this just above the LEFT OUTER JOIN + var newSql = sortedSql.SQL.Insert(sortedSql.SQL.IndexOf("LEFT OUTER JOIN"), innerJoinTempTable); + var newArgs = sortedSql.Arguments.ToList(); + newArgs.Add(orderBy); + + sortedSql = new Sql(newSql, newArgs.ToArray()); + + sortedSql.OrderBy("CustomPropData.CustomPropVal"); if (orderDirection == Direction.Descending) { sortedSql.Append(" DESC"); - } + } } - + return sortedSql; + } /// diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index c70b6a571a..e61b23c8ec 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -69,8 +69,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax bool SupportsClustered(); bool SupportsIdentityInsert(); bool? SupportsCaseInsensitiveQueries(Database db); - - string IsNull { get; } + string ConvertIntegerToOrderableString { get; } string ConvertDateToOrderableString { get; } string ConvertDecimalToOrderableString { get; } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index d4585f5aa1..7167a5af95 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -360,10 +360,9 @@ ORDER BY TABLE_NAME, INDEX_NAME", public override string DropIndex { get { return "DROP INDEX {0} ON {1}"; } } public override string RenameColumn { get { return "ALTER TABLE {0} CHANGE {1} {2}"; } } - public override string IsNull { get { return "IFNULL({0},{1})"; } } - public override string ConvertIntegerToOrderableString { get { return "LPAD({0}, 8, '0')"; } } + public override string ConvertIntegerToOrderableString { get { return "LPAD(FORMAT({0}, 0), 8, '0')"; } } public override string ConvertDateToOrderableString { get { return "DATE_FORMAT({0}, '%Y%m%d')"; } } - public override string ConvertDecimalToOrderableString { get { return "LPAD({0}, 25, '0')"; } } + public override string ConvertDecimalToOrderableString { get { return "LPAD(FORMAT({0}, 9), 20, '0')"; } } public override bool? SupportsCaseInsensitiveQueries(Database db) { diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index 1c6e1bba52..23e0a1bb87 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -208,7 +208,6 @@ ORDER BY TABLE_NAME, INDEX_NAME"); public override string DropIndex { get { return "DROP INDEX {1}.{0}"; } } - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index b7b58d929f..50417761aa 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -537,9 +537,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax public virtual string DeleteConstraint { get { return "ALTER TABLE {0} DROP CONSTRAINT {1}"; } } public virtual string CreateForeignKeyConstraint { get { return "ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4}){5}{6}"; } } - public virtual string IsNull { get { return "ISNULL({0},{1})"; } } - public virtual string ConvertIntegerToOrderableString { get { return "RIGHT('00000000' + CAST({0} AS varchar(8)),8)"; } } - public virtual string ConvertDateToOrderableString { get { return "CONVERT(varchar, {0}, 102)"; } } - public virtual string ConvertDecimalToOrderableString { get { return "RIGHT('0000000000000000000000000' + CAST({0} AS varchar(25)),25)"; } } + public virtual string ConvertIntegerToOrderableString { get { return "REPLACE(STR({0}, 8), SPACE(1), '0')"; } } + public virtual string ConvertDateToOrderableString { get { return "CONVERT(nvarchar, {0}, 102)"; } } + public virtual string ConvertDecimalToOrderableString { get { return "REPLACE(STR({0}, 20, 9), SPACE(1), '0')"; } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs index 8fc1150409..a0490348f3 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Linq; +using System.Text; using StackExchange.Profiling; using Umbraco.Core.Logging; @@ -128,15 +129,25 @@ namespace Umbraco.Core.Persistence // if no timeout is specified, and the connection has a longer timeout, use it if (OneTimeCommandTimeout == 0 && CommandTimeout == 0 && cmd.Connection.ConnectionTimeout > 30) cmd.CommandTimeout = cmd.Connection.ConnectionTimeout; + + if (EnableSqlTrace) + { + var sb = new StringBuilder(); + sb.Append(cmd.CommandText); + foreach (DbParameter p in cmd.Parameters) + { + sb.Append(" - "); + sb.Append(p.Value); + } + + _logger.Debug(sb.ToString()); + } + base.OnExecutingCommand(cmd); } public override void OnExecutedCommand(IDbCommand cmd) { - if (EnableSqlTrace) - { - _logger.Debug(cmd.CommandText); - } if (_enableCount) { SqlCount++; diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index 2cdc40d6f6..d29b073df7 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using System.Xml.Linq; using Moq; using NUnit.Framework; @@ -543,6 +544,41 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Can_Perform_GetPagedResultsByQuery_Sorting_On_Custom_Property() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Name.Contains("Text")); + long totalRecords; + + try + { + DatabaseContext.Database.EnableSqlTrace = true; + DatabaseContext.Database.EnableSqlCount(); + + var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "title", Direction.Ascending, false); + + Assert.AreEqual(3, totalRecords); + Assert.AreEqual(2, result.Count()); + + result = repository.GetPagedResultsByQuery(query, 1, 2, out totalRecords, "title", Direction.Ascending, false); + + Assert.AreEqual(1, result.Count()); + } + finally + { + DatabaseContext.Database.EnableSqlTrace = false; + DatabaseContext.Database.DisableSqlCount(); + } + } + } + [Test] public void Can_Perform_GetPagedResultsByQuery_ForFirstPage_On_ContentRepository() { @@ -555,12 +591,23 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var query = Query.Builder.Where(x => x.Level == 2); long totalRecords; - var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true); - // Assert - Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); - Assert.That(result.Count(), Is.EqualTo(1)); - Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); + try + { + DatabaseContext.Database.EnableSqlTrace = true; + DatabaseContext.Database.EnableSqlCount(); + var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Text Page 1")); + } + finally + { + DatabaseContext.Database.EnableSqlTrace = false; + DatabaseContext.Database.DisableSqlCount(); + } } } From b04e070757f2e3ccadaf18270200b298d8274ceb Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 20 Apr 2016 13:13:34 +0200 Subject: [PATCH 093/101] Fixing tests - Plugin and TypeFinder --- .../Plugins/PluginManagerTests.cs | 6 ++--- src/Umbraco.Tests/Plugins/TypeFinderTests.cs | 23 +++++++++++++++---- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs index 975259fe32..b8f5e305a6 100644 --- a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs +++ b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs @@ -263,7 +263,7 @@ namespace Umbraco.Tests.Plugins public void Resolves_Assigned_Mappers() { var foundTypes1 = _manager.ResolveAssignedMapperTypes(); - Assert.AreEqual(28, foundTypes1.Count()); + Assert.AreEqual(29, foundTypes1.Count()); // 29 classes in the solution implement BaseMapper } [Test] @@ -277,14 +277,14 @@ namespace Umbraco.Tests.Plugins public void Resolves_Actions() { var actions = _manager.ResolveActions(); - Assert.AreEqual(37, actions.Count()); + Assert.AreEqual(36, actions.Count()); // 36 classes in the solution implement IAction } [Test] public void Resolves_Trees() { var trees = _manager.ResolveTrees(); - Assert.AreEqual(39, trees.Count()); + Assert.AreEqual(8, trees.Count()); // 8 classes in the solution implement BaseTree } [Test] diff --git a/src/Umbraco.Tests/Plugins/TypeFinderTests.cs b/src/Umbraco.Tests/Plugins/TypeFinderTests.cs index be2a462cfe..d2161bdfe5 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.Web; using Umbraco.Web.Models.Trees; using Umbraco.Web.Trees; @@ -73,17 +74,29 @@ namespace Umbraco.Tests.Plugins var typesFound = TypeFinder.FindClassesOfType(_assemblies); var originalTypesFound = TypeFinderOriginal.FindClassesOfType(_assemblies); - Assert.AreEqual(originalTypesFound.Count(), typesFound.Count()); - Assert.AreEqual(9, typesFound.Count()); - Assert.AreEqual(9, originalTypesFound.Count()); + foreach (var type in typesFound) + Console.WriteLine(type); + Console.WriteLine(); + foreach (var type in originalTypesFound) + Console.WriteLine(type); + + // 6 classes in _assemblies implement IApplicationEventHandler + Assert.AreEqual(6, typesFound.Count()); + + // however, + // Umbraco.Core.Profiling.WebProfiler is internal and is not returned by TypeFinderOriginal, + // that's a known issue of the legacy type finder, so we have to tweak the count here. + Assert.AreEqual(5, originalTypesFound.Count()); } [Test] public void Find_Classes_With_Attribute() { var typesFound = TypeFinder.FindClassesWithAttribute(_assemblies); - //TODO: Fix this with the correct count - Assert.AreEqual(1, typesFound.Count()); + Assert.AreEqual(0, typesFound.Count()); // 0 classes in _assemblies are marked with [Tree] + + typesFound = TypeFinder.FindClassesWithAttribute(new[] { typeof (UmbracoContext).Assembly }); + Assert.AreEqual(23, typesFound.Count()); // 23 classes in Umbraco.Web are marked with [Tree] } [Ignore] From 7e8b4d13a72e3f7216482ea8628050214c26bf49 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 20 Apr 2016 14:06:13 +0200 Subject: [PATCH 094/101] Fixing tests - ValueListPreValueEditor --- .../MultiValuePropertyEditorTests.cs | 27 +++++++++++++++++++ .../ValueListPreValueEditor.cs | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs index 42b9d174df..0ba844ca6b 100644 --- a/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs @@ -1,10 +1,21 @@ 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.Web.PropertyEditors; namespace Umbraco.Tests.PropertyEditors @@ -88,6 +99,22 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void DropDownPreValueEditor_Format_Data_For_Editor() { + // editor wants ApplicationContext.Current.Services.TextService + // (that should be fixed with proper injection) + var logger = Mock.Of(); + var textService = new Mock(); + textService.Setup(x => x.Localize(It.IsAny(), It.IsAny(), It.IsAny>())).Returns("blah"); + var appContext = new ApplicationContext( + new DatabaseContext(Mock.Of(), logger, new SqlSyntaxProviders(Enumerable.Empty())), + new ServiceContext( + localizedTextService: textService.Object + ), + Mock.Of(), + new ProfilingLogger(logger, Mock.Of())) + { + IsReady = true + }; + ApplicationContext.Current = appContext; var defaultVals = new Dictionary(); var persisted = new PreValueCollection(new Dictionary diff --git a/src/Umbraco.Web/PropertyEditors/ValueListPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ValueListPreValueEditor.cs index 41c45efb98..2c6357de3d 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueListPreValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueListPreValueEditor.cs @@ -46,7 +46,7 @@ namespace Umbraco.Web.PropertyEditors //It's also important to note that by default the dropdown angular controller is expecting the // config options to come in with a property called 'items' Key = "items", - Name = ApplicationContext.Current.Services.TextService.Localize("editdatatype/addPrevalue"), + Name = ApplicationContext.Current.Services.TextService.Localize("editdatatype/addPrevalue"), // todo: inject View = "multivalues" } }; From bf51be950f1cb49e09d99b12cec16970dd370877 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 20 Apr 2016 14:09:40 +0200 Subject: [PATCH 095/101] Fixing tests - CoreBootManager --- .../Persistence/Migrations/MigrationStartupHandlerTests.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs b/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs index 3ebd40577b..0b39985028 100644 --- a/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs +++ b/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs @@ -113,6 +113,12 @@ namespace Umbraco.Tests.Persistence.Migrations private readonly string _prodName; private readonly Args _changed; + // need that one else it breaks IoC + public TestMigrationHandler() + { + _changed = new Args(); + } + public TestMigrationHandler(Args changed) { _changed = changed; From 41f6012eb38e7ebc222f91391f928219138a48d5 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 20 Apr 2016 15:00:52 +0200 Subject: [PATCH 096/101] Fixing tests - ServiceContext --- src/Umbraco.Core/Services/ServiceContext.cs | 398 +++--------------- .../Services/ThreadSafetyServiceTest.cs | 2 +- .../TestHelpers/BaseDatabaseFactoryTest.cs | 2 +- .../TestHelpers/BaseUmbracoApplicationTest.cs | 2 +- .../TestHelpers/ServiceContextHelper.cs | 134 ++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 6 files changed, 195 insertions(+), 344 deletions(-) create mode 100644 src/Umbraco.Tests/TestHelpers/ServiceContextHelper.cs diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index a00e7307b8..0017df01e9 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -1,55 +1,43 @@ using System; -using System.Collections.Generic; -using log4net; -using Umbraco.Core.Logging; -using System.IO; -using System.Linq; -using Umbraco.Core.IO; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Publishing; -using Umbraco.Core.Events; -using Umbraco.Core.Strings; namespace Umbraco.Core.Services { /// - /// The Umbraco ServiceContext, which provides access to the following services: - /// , , , - /// , and . + /// Represents the Umbraco Service context, which provides access to all services. /// public class ServiceContext { - private Lazy _migrationEntryService; - private Lazy _publicAccessService; - private Lazy _taskService; - private Lazy _domainService; - private Lazy _auditService; - private Lazy _localizedTextService; - private Lazy _tagService; - private Lazy _contentService; - private Lazy _userService; - private Lazy _memberService; - private Lazy _mediaService; - private Lazy _contentTypeService; - private Lazy _dataTypeService; - private Lazy _fileService; - private Lazy _localizationService; - private Lazy _packagingService; - private Lazy _serverRegistrationService; - private Lazy _entityService; - private Lazy _relationService; - private Lazy _treeService; - private Lazy _sectionService; - private Lazy _macroService; - private Lazy _memberTypeService; - private Lazy _memberGroupService; - private Lazy _notificationService; - private Lazy _externalLoginService; + private readonly Lazy _migrationEntryService; + private readonly Lazy _publicAccessService; + private readonly Lazy _taskService; + private readonly Lazy _domainService; + private readonly Lazy _auditService; + private readonly Lazy _localizedTextService; + private readonly Lazy _tagService; + private readonly Lazy _contentService; + private readonly Lazy _userService; + private readonly Lazy _memberService; + private readonly Lazy _mediaService; + private readonly Lazy _contentTypeService; + private readonly Lazy _dataTypeService; + private readonly Lazy _fileService; + private readonly Lazy _localizationService; + private readonly Lazy _packagingService; + private readonly Lazy _serverRegistrationService; + private readonly Lazy _entityService; + private readonly Lazy _relationService; + private readonly Lazy _treeService; + private readonly Lazy _sectionService; + private readonly Lazy _macroService; + private readonly Lazy _memberTypeService; + private readonly Lazy _memberGroupService; + private readonly Lazy _notificationService; + private readonly Lazy _externalLoginService; /// - /// Constructor used for IoC + /// Initializes a new instance of the class with lazy services. /// + /// Used by IoC. Note that LightInject will favor lazy args when picking a constructor. public ServiceContext(Lazy migrationEntryService, Lazy publicAccessService, Lazy taskService, Lazy domainService, Lazy auditService, Lazy localizedTextService, Lazy tagService, Lazy contentService, Lazy userService, Lazy memberService, Lazy mediaService, Lazy contentTypeService, Lazy dataTypeService, Lazy fileService, Lazy localizationService, Lazy packagingService, Lazy serverRegistrationService, Lazy entityService, Lazy relationService, Lazy treeService, Lazy sectionService, Lazy macroService, Lazy memberTypeService, Lazy memberGroupService, Lazy notificationService, Lazy externalLoginService) { _migrationEntryService = migrationEntryService; @@ -81,34 +69,9 @@ namespace Umbraco.Core.Services } /// - /// public ctor - will generally just be used for unit testing all items are optional and if not specified, the defaults will be used + /// Initializes a new instance of the class with services. /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// + /// Used in tests. All items are optional and remain null if not specified. public ServiceContext( IContentService contentService = null, IMediaService mediaService = null, @@ -165,378 +128,131 @@ namespace Umbraco.Core.Services if (publicAccessService != null) _publicAccessService = new Lazy(() => publicAccessService); } - /// - /// Creates a service context with a RepositoryFactory which is used to construct Services - /// - /// - /// - /// - /// - /// - /// - /// - /// - public ServiceContext( - RepositoryFactory repositoryFactory, - IDatabaseUnitOfWorkProvider dbUnitOfWorkProvider, - IUnitOfWorkProvider fileUnitOfWorkProvider, - IPublishingStrategy publishingStrategy, - CacheHelper cache, - ILogger logger, - IEventMessagesFactory eventMessagesFactory, - IEnumerable urlSegmentProviders) - { - if (repositoryFactory == null) throw new ArgumentNullException("repositoryFactory"); - if (dbUnitOfWorkProvider == null) throw new ArgumentNullException("dbUnitOfWorkProvider"); - if (fileUnitOfWorkProvider == null) throw new ArgumentNullException("fileUnitOfWorkProvider"); - if (publishingStrategy == null) throw new ArgumentNullException("publishingStrategy"); - if (cache == null) throw new ArgumentNullException("cache"); - if (logger == null) throw new ArgumentNullException("logger"); - if (eventMessagesFactory == null) throw new ArgumentNullException("eventMessagesFactory"); - - BuildServiceCache(dbUnitOfWorkProvider, fileUnitOfWorkProvider, publishingStrategy, cache, - repositoryFactory, - logger, eventMessagesFactory, - urlSegmentProviders); - } - - /// - /// Builds the various services - /// - private void BuildServiceCache( - IDatabaseUnitOfWorkProvider dbUnitOfWorkProvider, - IUnitOfWorkProvider fileUnitOfWorkProvider, - IPublishingStrategy publishingStrategy, - CacheHelper cache, - RepositoryFactory repositoryFactory, - ILogger logger, - IEventMessagesFactory eventMessagesFactory, - IEnumerable urlSegmentProviders) - { - var provider = dbUnitOfWorkProvider; - var fileProvider = fileUnitOfWorkProvider; - - if (_migrationEntryService == null) - _migrationEntryService = new Lazy(() => new MigrationEntryService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_externalLoginService == null) - _externalLoginService = new Lazy(() => new ExternalLoginService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_publicAccessService == null) - _publicAccessService = new Lazy(() => new PublicAccessService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_taskService == null) - _taskService = new Lazy(() => new TaskService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_domainService == null) - _domainService = new Lazy(() => new DomainService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_auditService == null) - _auditService = new Lazy(() => new AuditService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_localizedTextService == null) - { - - _localizedTextService = new Lazy(() => new LocalizedTextService( - new Lazy(() => - { - var mainLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Umbraco + "/config/lang/")); - var appPlugins = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins)); - var configLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Config + "/lang/")); - - var pluginLangFolders = appPlugins.Exists == false - ? Enumerable.Empty() - : appPlugins.GetDirectories() - .SelectMany(x => x.GetDirectories("Lang")) - .SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly)) - .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 5) - .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false)); - - //user defined langs that overwrite the default, these should not be used by plugin creators - var userLangFolders = configLangFolder.Exists == false - ? Enumerable.Empty() - : configLangFolder - .GetFiles("*.user.xml", SearchOption.TopDirectoryOnly) - .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 10) - .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, true)); - - return new LocalizedTextServiceFileSources( - logger, - cache.RuntimeCache, - mainLangFolder, - pluginLangFolders.Concat(userLangFolders)); - - }), - logger)); - } - - - if (_notificationService == null) - _notificationService = new Lazy(() => new NotificationService(provider, _userService.Value, _contentService.Value, repositoryFactory, logger)); - - if (_serverRegistrationService == null) - _serverRegistrationService = new Lazy(() => new ServerRegistrationService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_userService == null) - _userService = new Lazy(() => new UserService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_memberService == null) - _memberService = new Lazy(() => new MemberService(provider, repositoryFactory, logger, eventMessagesFactory, _memberGroupService.Value, _dataTypeService.Value)); - - if (_contentService == null) - _contentService = new Lazy(() => new ContentService(provider, repositoryFactory, logger, eventMessagesFactory, publishingStrategy, _dataTypeService.Value, _userService.Value, urlSegmentProviders)); - - if (_mediaService == null) - _mediaService = new Lazy(() => new MediaService(provider, repositoryFactory, logger, eventMessagesFactory, _dataTypeService.Value, _userService.Value, urlSegmentProviders)); - - if (_contentTypeService == null) - _contentTypeService = new Lazy(() => new ContentTypeService(provider, repositoryFactory, logger, eventMessagesFactory, _contentService.Value, _mediaService.Value)); - - if (_dataTypeService == null) - _dataTypeService = new Lazy(() => new DataTypeService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_fileService == null) - _fileService = new Lazy(() => new FileService(fileProvider, provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_localizationService == null) - _localizationService = new Lazy(() => new LocalizationService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_entityService == null) - _entityService = new Lazy(() => new EntityService( - provider, repositoryFactory, logger, eventMessagesFactory, - _contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value, _memberService.Value, _memberTypeService.Value, - //TODO: Consider making this an isolated cache instead of using the global one - cache.RuntimeCache)); - - if (_packagingService == null) - _packagingService = new Lazy(() => new PackagingService(logger, _contentService.Value, _contentTypeService.Value, _mediaService.Value, _macroService.Value, _dataTypeService.Value, _fileService.Value, _localizationService.Value, _entityService.Value, _userService.Value, repositoryFactory, provider, urlSegmentProviders)); - - if (_relationService == null) - _relationService = new Lazy(() => new RelationService(provider, repositoryFactory, logger, eventMessagesFactory, _entityService.Value)); - - if (_treeService == null) - _treeService = new Lazy(() => new EmptyApplicationTreeService()); - - if (_sectionService == null) - _sectionService = new Lazy(() => new EmptySectionService()); - - if (_macroService == null) - _macroService = new Lazy(() => new MacroService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_memberTypeService == null) - _memberTypeService = new Lazy(() => new MemberTypeService(provider, repositoryFactory, logger, eventMessagesFactory, _memberService.Value)); - - if (_tagService == null) - _tagService = new Lazy(() => new TagService(provider, repositoryFactory, logger, eventMessagesFactory)); - - if (_memberGroupService == null) - _memberGroupService = new Lazy(() => new MemberGroupService(provider, repositoryFactory, logger, eventMessagesFactory)); - - } - /// /// Gets the /// - public IMigrationEntryService MigrationEntryService - { - get { return _migrationEntryService.Value; } - } + public IMigrationEntryService MigrationEntryService => _migrationEntryService.Value; /// /// Gets the /// - public IPublicAccessService PublicAccessService - { - get { return _publicAccessService.Value; } - } + public IPublicAccessService PublicAccessService => _publicAccessService.Value; /// /// Gets the /// - public ITaskService TaskService - { - get { return _taskService.Value; } - } + public ITaskService TaskService => _taskService.Value; /// /// Gets the /// - public IDomainService DomainService - { - get { return _domainService.Value; } - } + public IDomainService DomainService => _domainService.Value; /// /// Gets the /// - public IAuditService AuditService - { - get { return _auditService.Value; } - } + public IAuditService AuditService => _auditService.Value; /// /// Gets the /// - public ILocalizedTextService TextService - { - get { return _localizedTextService.Value; } - } + public ILocalizedTextService TextService => _localizedTextService.Value; /// /// Gets the /// - public INotificationService NotificationService - { - get { return _notificationService.Value; } - } + public INotificationService NotificationService => _notificationService.Value; /// /// Gets the /// - public IServerRegistrationService ServerRegistrationService - { - get { return _serverRegistrationService.Value; } - } + public IServerRegistrationService ServerRegistrationService => _serverRegistrationService.Value; /// /// Gets the /// - public ITagService TagService - { - get { return _tagService.Value; } - } + public ITagService TagService => _tagService.Value; /// /// Gets the /// - public IMacroService MacroService - { - get { return _macroService.Value; } - } + public IMacroService MacroService => _macroService.Value; /// /// Gets the /// - public IEntityService EntityService - { - get { return _entityService.Value; } - } + public IEntityService EntityService => _entityService.Value; /// /// Gets the /// - public IRelationService RelationService - { - get { return _relationService.Value; } - } + public IRelationService RelationService => _relationService.Value; /// /// Gets the /// - public IContentService ContentService - { - get { return _contentService.Value; } - } + public IContentService ContentService => _contentService.Value; /// /// Gets the /// - public IContentTypeService ContentTypeService - { - get { return _contentTypeService.Value; } - } + public IContentTypeService ContentTypeService => _contentTypeService.Value; /// /// Gets the /// - public IDataTypeService DataTypeService - { - get { return _dataTypeService.Value; } - } + public IDataTypeService DataTypeService => _dataTypeService.Value; /// /// Gets the /// - public IFileService FileService - { - get { return _fileService.Value; } - } + public IFileService FileService => _fileService.Value; /// /// Gets the /// - public ILocalizationService LocalizationService - { - get { return _localizationService.Value; } - } + public ILocalizationService LocalizationService => _localizationService.Value; /// /// Gets the /// - public IMediaService MediaService - { - get { return _mediaService.Value; } - } + public IMediaService MediaService => _mediaService.Value; /// /// Gets the /// - public IPackagingService PackagingService - { - get { return _packagingService.Value; } - } + public IPackagingService PackagingService => _packagingService.Value; /// /// Gets the /// - public IUserService UserService - { - get { return _userService.Value; } - } + public IUserService UserService => _userService.Value; /// /// Gets the /// - public IMemberService MemberService - { - get { return _memberService.Value; } - } + public IMemberService MemberService => _memberService.Value; /// /// Gets the /// - public ISectionService SectionService - { - get { return _sectionService.Value; } - } + public ISectionService SectionService => _sectionService.Value; /// /// Gets the /// - public IApplicationTreeService ApplicationTreeService - { - get { return _treeService.Value; } - } + public IApplicationTreeService ApplicationTreeService => _treeService.Value; /// /// Gets the MemberTypeService /// - public IMemberTypeService MemberTypeService - { - get { return _memberTypeService.Value; } - } + public IMemberTypeService MemberTypeService => _memberTypeService.Value; /// /// Gets the MemberGroupService /// - public IMemberGroupService MemberGroupService - { - get { return _memberGroupService.Value; } - } + public IMemberGroupService MemberGroupService => _memberGroupService.Value; - public IExternalLoginService ExternalLoginService - { - get { return _externalLoginService.Value; } - } + public IExternalLoginService ExternalLoginService => _externalLoginService.Value; } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs index b4560b072b..e25892224c 100644 --- a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs +++ b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs @@ -49,7 +49,7 @@ namespace Umbraco.Tests.Services var repositoryFactory = new RepositoryFactory(SqlSyntax, Container); _uowProvider = new PerThreadNPocoUnitOfWorkProvider(_dbFactory); var evtMsgs = new TransientMessagesFactory(); - ApplicationContext.Services = new ServiceContext( + ApplicationContext.Services = ServiceContextHelper.GetServiceContext( repositoryFactory, _uowProvider, new FileUnitOfWorkProvider(), diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index abc3c0ee40..2276f73a04 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -97,7 +97,7 @@ namespace Umbraco.Tests.TestHelpers //assign the db context new DatabaseContext(dbFactory, Logger, SqlSyntax, "System.Data.SqlServerCe.4.0"), //assign the service context - new ServiceContext( + ServiceContextHelper.GetServiceContext( Container.GetInstance(), new NPocoUnitOfWorkProvider(dbFactory), new FileUnitOfWorkProvider(), diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index 24a5c5f34d..49def25762 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -214,7 +214,7 @@ namespace Umbraco.Tests.TestHelpers new DatabaseContext(new DefaultDatabaseFactory(Core.Configuration.GlobalSettings.UmbracoConnectionName, Logger), Logger, SqlSyntax, "System.Data.SqlServerCe.4.0"), //assign the service context - new ServiceContext( + ServiceContextHelper.GetServiceContext( Container.GetInstance(), new NPocoUnitOfWorkProvider(Logger), new FileUnitOfWorkProvider(), diff --git a/src/Umbraco.Tests/TestHelpers/ServiceContextHelper.cs b/src/Umbraco.Tests/TestHelpers/ServiceContextHelper.cs new file mode 100644 index 0000000000..5c4485871e --- /dev/null +++ b/src/Umbraco.Tests/TestHelpers/ServiceContextHelper.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Umbraco.Core; +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; +using Umbraco.Web.Services; + +namespace Umbraco.Tests.TestHelpers +{ + class ServiceContextHelper + { + public static ServiceContext GetServiceContext(RepositoryFactory repositoryFactory, + IDatabaseUnitOfWorkProvider dbUnitOfWorkProvider, + IUnitOfWorkProvider fileUnitOfWorkProvider, + IPublishingStrategy publishingStrategy, + CacheHelper cache, + ILogger logger, + IEventMessagesFactory eventMessagesFactory, + IEnumerable urlSegmentProviders) + { + if (repositoryFactory == null) throw new ArgumentNullException(nameof(repositoryFactory)); + if (dbUnitOfWorkProvider == null) throw new ArgumentNullException(nameof(dbUnitOfWorkProvider)); + if (fileUnitOfWorkProvider == null) throw new ArgumentNullException(nameof(fileUnitOfWorkProvider)); + if (publishingStrategy == null) throw new ArgumentNullException(nameof(publishingStrategy)); + if (cache == null) throw new ArgumentNullException(nameof(cache)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + if (eventMessagesFactory == null) throw new ArgumentNullException(nameof(eventMessagesFactory)); + + var provider = dbUnitOfWorkProvider; + var fileProvider = fileUnitOfWorkProvider; + + var migrationEntryService = new Lazy(() => new MigrationEntryService(provider, repositoryFactory, logger, eventMessagesFactory)); + var externalLoginService = new Lazy(() => new ExternalLoginService(provider, repositoryFactory, logger, eventMessagesFactory)); + var publicAccessService = new Lazy(() => new PublicAccessService(provider, repositoryFactory, logger, eventMessagesFactory)); + var taskService = new Lazy(() => new TaskService(provider, repositoryFactory, logger, eventMessagesFactory)); + var domainService = new Lazy(() => new DomainService(provider, repositoryFactory, logger, eventMessagesFactory)); + var auditService = new Lazy(() => new AuditService(provider, repositoryFactory, logger, eventMessagesFactory)); + + var localizedTextService = new Lazy(() => new LocalizedTextService( + new Lazy(() => + { + var mainLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Umbraco + "/config/lang/")); + var appPlugins = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins)); + var configLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Config + "/lang/")); + + var pluginLangFolders = appPlugins.Exists == false + ? Enumerable.Empty() + : appPlugins.GetDirectories() + .SelectMany(x => x.GetDirectories("Lang")) + .SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly)) + .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 5) + .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false)); + + //user defined langs that overwrite the default, these should not be used by plugin creators + var userLangFolders = configLangFolder.Exists == false + ? Enumerable.Empty() + : configLangFolder + .GetFiles("*.user.xml", SearchOption.TopDirectoryOnly) + .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 10) + .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, true)); + + return new LocalizedTextServiceFileSources( + logger, + cache.RuntimeCache, + mainLangFolder, + pluginLangFolders.Concat(userLangFolders)); + + }), + logger)); + + var userService = new Lazy(() => new UserService(provider, repositoryFactory, logger, eventMessagesFactory)); + var dataTypeService = new Lazy(() => new DataTypeService(provider, repositoryFactory, logger, eventMessagesFactory)); + var contentService = new Lazy(() => new ContentService(provider, repositoryFactory, logger, eventMessagesFactory, publishingStrategy, dataTypeService.Value, userService.Value, urlSegmentProviders)); + var notificationService = new Lazy(() => new NotificationService(provider, userService.Value, contentService.Value, repositoryFactory, logger)); + var serverRegistrationService = new Lazy(() => new ServerRegistrationService(provider, repositoryFactory, logger, eventMessagesFactory)); + var memberGroupService = new Lazy(() => new MemberGroupService(provider, repositoryFactory, logger, eventMessagesFactory)); + var memberService = new Lazy(() => new MemberService(provider, repositoryFactory, logger, eventMessagesFactory, memberGroupService.Value, dataTypeService.Value)); + var mediaService = new Lazy(() => new MediaService(provider, repositoryFactory, logger, eventMessagesFactory, dataTypeService.Value, userService.Value, urlSegmentProviders)); + var contentTypeService = new Lazy(() => new ContentTypeService(provider, repositoryFactory, logger, eventMessagesFactory, contentService.Value, mediaService.Value)); + var fileService = new Lazy(() => new FileService(fileProvider, provider, repositoryFactory, logger, eventMessagesFactory)); + var localizationService = new Lazy(() => new LocalizationService(provider, repositoryFactory, logger, eventMessagesFactory)); + + var memberTypeService = new Lazy(() => new MemberTypeService(provider, repositoryFactory, logger, eventMessagesFactory, memberService.Value)); + var entityService = new Lazy(() => new EntityService( + provider, repositoryFactory, logger, eventMessagesFactory, + contentService.Value, contentTypeService.Value, mediaService.Value, dataTypeService.Value, memberService.Value, memberTypeService.Value, + //TODO: Consider making this an isolated cache instead of using the global one + cache.RuntimeCache)); + + var macroService = new Lazy(() => new MacroService(provider, repositoryFactory, logger, eventMessagesFactory)); + var packagingService = new Lazy(() => new PackagingService(logger, contentService.Value, contentTypeService.Value, mediaService.Value, macroService.Value, dataTypeService.Value, fileService.Value, localizationService.Value, entityService.Value, userService.Value, repositoryFactory, provider, urlSegmentProviders)); + var relationService = new Lazy(() => new RelationService(provider, repositoryFactory, logger, eventMessagesFactory, entityService.Value)); + var treeService = new Lazy(() => new ApplicationTreeService(logger, cache)); + var tagService = new Lazy(() => new TagService(provider, repositoryFactory, logger, eventMessagesFactory)); + var sectionService = new Lazy(() => new SectionService(userService.Value, treeService.Value, provider, cache)); + + return new ServiceContext( + migrationEntryService, + publicAccessService, + taskService, + domainService, + auditService, + localizedTextService, + tagService, + contentService, + userService, + memberService, + mediaService, + contentTypeService, + dataTypeService, + fileService, + localizationService, + packagingService, + serverRegistrationService, + entityService, + relationService, + treeService, + sectionService, + macroService, + memberTypeService, + memberGroupService, + notificationService, + externalLoginService); + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index fb3e318538..4766505912 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -181,6 +181,7 @@ + From 4e702c7c79d0f0a660fdd81ecf1648376b628438 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 20 Apr 2016 15:09:41 +0200 Subject: [PATCH 097/101] Fixing tests - kill KegacyShortStringHelper --- .../Strings/LegacyShortStringHelper.cs | 531 ------------------ src/Umbraco.Core/Umbraco.Core.csproj | 1 - .../Routing/RenderRouteHandlerTests.cs | 2 +- .../Strings/CmsHelperCasingTests.cs | 13 - .../Strings/LegacyShortStringHelperTests.cs | 358 ------------ .../Strings/LegacyStringExtensionsTests.cs | 181 ------ src/Umbraco.Tests/Umbraco.Tests.csproj | 2 - 7 files changed, 1 insertion(+), 1087 deletions(-) delete mode 100644 src/Umbraco.Core/Strings/LegacyShortStringHelper.cs delete mode 100644 src/Umbraco.Tests/Strings/LegacyShortStringHelperTests.cs delete mode 100644 src/Umbraco.Tests/Strings/LegacyStringExtensionsTests.cs diff --git a/src/Umbraco.Core/Strings/LegacyShortStringHelper.cs b/src/Umbraco.Core/Strings/LegacyShortStringHelper.cs deleted file mode 100644 index 901c9ee299..0000000000 --- a/src/Umbraco.Core/Strings/LegacyShortStringHelper.cs +++ /dev/null @@ -1,531 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Xml; -using Umbraco.Core.Configuration; - -namespace Umbraco.Core.Strings -{ - /// - /// Legacy implementation of string functions for short strings such as aliases or url segments. - /// - /// - /// Not necessarily optimized to work on large bodies of text. - /// Can expose surprising or bogus behavior. - /// Uses invariant culture everywhere. - /// - internal class LegacyShortStringHelper : IShortStringHelper - { - #region Ctor and vars - - /// - /// Freezes the helper so it can prevents its configuration from being modified. - /// - /// Will be called by ShortStringHelperResolver when resolution freezes. - public void Freeze() - { - // we have nothing to protect. - } - - const string UmbracoValidAliasCharacters = "_-abcdefghijklmnopqrstuvwxyz1234567890"; - const string UmbracoInvalidFirstCharacters = "0123456789"; - - #endregion - - #region Short string services JavaScript - - const string SssjsValidCharacters = "_-abcdefghijklmnopqrstuvwxyz1234567890"; - const string SssjsInvalidFirstCharacters = "0123456789"; - - private const string SssjsFormat = @" -var UMBRACO_FORCE_SAFE_ALIAS = {0}; -var UMBRACO_FORCE_SAFE_ALIAS_VALIDCHARS = '{1}'; -var UMBRACO_FORCE_SAFE_ALIAS_INVALID_FIRST_CHARS = '{2}'; - -function safeAlias(alias) {{ - if (UMBRACO_FORCE_SAFE_ALIAS) {{ - var safeAlias = ''; - var aliasLength = alias.length; - for (var i = 0; i < aliasLength; i++) {{ - currentChar = alias.substring(i, i + 1); - if (UMBRACO_FORCE_SAFE_ALIAS_VALIDCHARS.indexOf(currentChar.toLowerCase()) > -1) {{ - // check for camel (if previous character is a space, we'll upper case the current one - if (safeAlias == '' && UMBRACO_FORCE_SAFE_ALIAS_INVALID_FIRST_CHARS.indexOf(currentChar.toLowerCase()) > 0) {{ - currentChar = ''; - }} else {{ - // first char should always be lowercase (camel style) - if (safeAlias.length == 0) - currentChar = currentChar.toLowerCase(); - - if (i < aliasLength - 1 && safeAlias != '' && alias.substring(i - 1, i) == ' ') - currentChar = currentChar.toUpperCase(); - - safeAlias += currentChar; - }} - }} - }} - - alias = safeAlias; - }} - return alias; -}} - -function getSafeAlias(input, value, immediate, callback) {{ - callback(safeAlias(value)); -}} - -function validateSafeAlias(input, value, immediate, callback) {{ - callback(value == safeAlias(value)); -}} - -// legacy backward compatibility requires that one -function isValidAlias(alias) {{ - return alias == safeAlias(alias); -}} -"; - - /// - /// Gets the JavaScript code defining client-side short string services. - /// - public string GetShortStringServicesJavaScript(string controllerPath) - { - return string.Format(SssjsFormat, - UmbracoConfig.For.UmbracoSettings().Content.ForceSafeAliases ? "true" : "false", SssjsValidCharacters, SssjsInvalidFirstCharacters); - } - - #endregion - - #region IShortStringHelper CleanFor... - - /// - /// Cleans a string to produce a string that can safely be used in an alias. - /// - /// The text to filter. - /// The safe alias. - /// The string will be cleaned in the context of invariant culture. - public string CleanStringForSafeAlias(string text) - { - // ported from StringExtensions.ToSafeAlias() - - const string validAliasCharacters = UmbracoValidAliasCharacters; - const string invalidFirstCharacters = UmbracoInvalidFirstCharacters; - var safeString = new StringBuilder(); - int aliasLength = text.Length; - for (var i = 0; i < aliasLength; i++) - { - var currentChar = text.Substring(i, 1); - if (validAliasCharacters.Contains(currentChar.ToLowerInvariant())) - { - // check for camel (if previous character is a space, we'll upper case the current one - if (safeString.Length == 0 && invalidFirstCharacters.Contains(currentChar.ToLowerInvariant())) - { - //currentChar = ""; - } - else - { - if (i < aliasLength - 1 && i > 0 && text.Substring(i - 1, 1) == " ") - currentChar = currentChar.ToUpperInvariant(); - - safeString.Append(currentChar); - } - } - } - - return safeString.ToString(); - } - - /// - /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used in an alias. - /// - /// The text to filter. - /// The culture. - /// The safe alias. - /// Legacy does not support culture contexts. - public string CleanStringForSafeAlias(string text, CultureInfo culture) - { - return CleanStringForSafeAlias(text); - } - - /// - /// Cleans a string to produce a string that can safely be used in an url segment, in the context of the invariant culture. - /// - /// The text to filter. - /// The safe url segment. - public string CleanStringForUrlSegment(string text) - { - return LegacyFormatUrl(text); - } - - /// - /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used in an url segment. - /// - /// The text to filter. - /// The culture. - /// The safe url segment. - /// Legacy does not support culture contexts. - public string CleanStringForUrlSegment(string text, CultureInfo culture) - { - return CleanStringForUrlSegment(text); - } - - /// - /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a filename, - /// both internally (on disk) and externally (as a url). - /// - /// The text to filter. - /// The safe filename. - /// Legacy says this was used to "overcome an issue when Umbraco is used in IE in an intranet environment" but that issue is not documented. - public string CleanStringForSafeFileName(string text) - { - var filePath = text; - - // ported from Core.IO.IOHelper.SafeFileName() - - if (String.IsNullOrEmpty(filePath)) - return String.Empty; - - if (!String.IsNullOrWhiteSpace(filePath)) - { - foreach (var character in Path.GetInvalidFileNameChars()) - { - filePath = filePath.Replace(character, '-'); - } - } - else - { - filePath = String.Empty; - } - - //Break up the file in name and extension before applying the UrlReplaceCharacters - var fileNamePart = filePath.Substring(0, filePath.LastIndexOf('.')); - var ext = filePath.Substring(filePath.LastIndexOf('.')); - - //Because the file usually is downloadable as well we check characters against 'UmbracoSettings.UrlReplaceCharacters' - foreach (var n in UmbracoConfig.For.UmbracoSettings().RequestHandler.CharCollection) - { - if (n.Char.IsNullOrWhiteSpace() == false) - fileNamePart = fileNamePart.Replace(n.Char, n.Replacement); - } - - filePath = string.Concat(fileNamePart, ext); - - // Adapted from: http://stackoverflow.com/a/4827510/5018 - // Combined both Reserved Characters and Character Data - // from http://en.wikipedia.org/wiki/Percent-encoding - var stringBuilder = new StringBuilder(); - - const string reservedCharacters = "!*'();:@&=+$,/?%#[]-~{}\"<>\\^`| "; - - foreach (var character in filePath) - { - if (reservedCharacters.IndexOf(character) == -1) - stringBuilder.Append(character); - else - stringBuilder.Append("-"); - } - - // Remove repeating dashes - // From: http://stackoverflow.com/questions/5111967/regex-to-remove-a-specific-repeated-character - var reducedString = Regex.Replace(stringBuilder.ToString(), "-+", "-"); - - return reducedString; - } - - /// - /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a filename, - /// both internally (on disk) and externally (as a url). - /// - /// The text to filter. - /// The culture. - /// The safe filename. - /// Legacy does not support culture contexts. - public string CleanStringForSafeFileName(string text, CultureInfo culture) - { - return CleanStringForSafeFileName(text); - } - - #endregion - - #region CleanString - - // legacy does not implement these - - public string CleanString(string text, CleanStringType stringType) - { - return text; - } - - public string CleanString(string text, CleanStringType stringType, char separator) - { - return text; - } - - public string CleanString(string text, CleanStringType stringType, CultureInfo culture) - { - return text; - } - - public string CleanString(string text, CleanStringType stringType, char separator, CultureInfo culture) - { - return text; - } - - #endregion - - #region SplitPascalCasing - - /// - /// Splits a pascal-cased string by inserting a separator in between each term. - /// - /// The text to split. - /// The separator. - /// The splitted string. - /// Probably only supports Ascii strings. - public string SplitPascalCasing(string text, char separator) - { - // ported from StringExtensions.SplitPascalCasing() - - var replacement = "$1" + separator; - var result = Regex.Replace(text, "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))", replacement); - return result; - } - - #endregion - - #region Legacy - - /// - /// Cleans a string to produce a string that can safely be used in an alias. - /// - /// The text to filter. - /// The safe alias. - /// The string will be cleaned in the context of invariant culture. - public string LegacyCleanStringForUmbracoAlias(string text) - { - // ported from StringExtensions.ToUmbracoAlias() - // kept here for reference, not used anymore - - if (string.IsNullOrEmpty(text)) return string.Empty; - - //convert case first - //var tmp = text.ConvertCase(caseType); - // note: always Camel anyway - var tmp = LegacyConvertStringCase(text, CleanStringType.CamelCase); - - //remove non-alphanumeric chars - var result = Regex.Replace(tmp, @"[^a-zA-Z0-9\s\.-]+", "", RegexOptions.Compiled); - - // note: spaces are always removed anyway - //if (removeSpaces) - // result = result.Replace(" ", ""); - - return result; - } - - /// - /// Filters a string to convert case, and more. - /// - /// the text to filter. - /// The string case type. - /// The filtered text. - /// - /// This is the legacy method, so we can't really change it, although it has issues (see unit tests). - /// It does more than "converting the case", and also remove spaces, etc. - /// - public string LegacyConvertStringCase(string phrase, CleanStringType cases) - { - // ported from StringExtensions.ConvertCase - - cases &= CleanStringType.CaseMask; - - var splittedPhrase = Regex.Split(phrase, @"[^a-zA-Z0-9\']", RegexOptions.Compiled); - - if (cases == CleanStringType.Unchanged) - return string.Join("", splittedPhrase); - - //var splittedPhrase = phrase.Split(' ', '-', '.'); - var sb = new StringBuilder(); - - foreach (var splittedPhraseChars in splittedPhrase.Select(s => s.ToCharArray())) - { - if (splittedPhraseChars.Length > 0) - { - splittedPhraseChars[0] = ((new String(splittedPhraseChars[0], 1)).ToUpperInvariant().ToCharArray())[0]; - } - sb.Append(new String(splittedPhraseChars)); - } - - var result = sb.ToString(); - - if (cases == CleanStringType.CamelCase) - { - if (result.Length > 1) - { - var pattern = new Regex("^([A-Z]*)([A-Z].*)$", RegexOptions.Singleline | RegexOptions.Compiled); - var match = pattern.Match(result); - if (match.Success) - { - result = match.Groups[1].Value.ToLowerInvariant() + match.Groups[2].Value; - - return result.Substring(0, 1).ToLowerInvariant() + result.Substring(1); - } - - return result; - } - - return result.ToLowerInvariant(); - } - - return result; - } - - /// - /// Converts string to a URL alias. - /// - /// The value. - /// The char replacements. - /// if set to true replace double dashes. - /// if set to true strip non ASCII. - /// if set to true URL encode. - /// - /// - /// This ensures that ONLY ascii chars are allowed and of those ascii chars, only digits and lowercase chars, all - /// punctuation, etc... are stripped out, however this method allows you to pass in string's to replace with the - /// specified replacement character before the string is converted to ascii and it has invalid characters stripped out. - /// This allows you to replace strings like & , etc.. with your replacement character before the automatic - /// reduction. - /// - public string LegacyToUrlAlias(string value, IDictionary charReplacements, bool replaceDoubleDashes, bool stripNonAscii, bool urlEncode) - { - // to lower case invariant - // replace chars one by one using charReplacements - // (opt) convert to ASCII then remove anything that's not ASCII - // trim - and _ then (opt) remove double - - // (opt) url-encode - - // charReplacement is actually *string* replacement ie it can replace " " by a non-breaking space - // so it's kind of a pre-filter actually... - // we need pre-filters, and post-filters, within each token... - // not so... we may want to replace   with a space BEFORE cutting into tokens... - - //first to lower case - value = value.ToLowerInvariant(); - - //then replacement chars - value = charReplacements.Aggregate(value, (current, kvp) => current.Replace(kvp.Key, kvp.Value)); - - //then convert to only ascii, this will remove the rest of any invalid chars - if (stripNonAscii) - { - value = Encoding.ASCII.GetString( - Encoding.Convert( - Encoding.UTF8, - Encoding.GetEncoding( - Encoding.ASCII.EncodingName, - new EncoderReplacementFallback(String.Empty), - new DecoderExceptionFallback()), - Encoding.UTF8.GetBytes(value))); - - //remove all characters that do not fall into the following categories (apart from the replacement val) - var validCodeRanges = - //digits - Enumerable.Range(48, 10).Concat( - //lowercase chars - Enumerable.Range(97, 26)); - - var sb = new StringBuilder(); - foreach (var c in value.Where(c => charReplacements.Values.Contains(c.ToString(CultureInfo.InvariantCulture)) || validCodeRanges.Contains(c))) - { - sb.Append(c); - } - - value = sb.ToString(); - } - - //trim dashes from end - value = value.Trim('-', '_'); - - //replace double occurances of - or _ - value = replaceDoubleDashes ? Regex.Replace(value, @"([-_]){2,}", "$1", RegexOptions.Compiled) : value; - - //url encode result - return urlEncode ? System.Web.HttpUtility.UrlEncode(value) : value; - } - - /// - /// Cleans a string to produce a string that can safely be used in an url segment. - /// - /// The text to filter. - /// The safe url segment. - /// - /// Uses UmbracoSettings.UrlReplaceCharacters - /// and UmbracoSettings.RemoveDoubleDashesFromUrlReplacing. - /// - public string LegacyFormatUrl(string url) - { - var newUrl = url.ToLowerInvariant(); - foreach (var n in UmbracoConfig.For.UmbracoSettings().RequestHandler.CharCollection) - { - if (n.Char != "") - newUrl = newUrl.Replace(n.Char, n.Replacement); - } - - // check for double dashes - if (UmbracoConfig.For.UmbracoSettings().RequestHandler.RemoveDoubleDashes) - { - newUrl = Regex.Replace(newUrl, @"[-]{2,}", "-"); - } - - return newUrl; - } - - #endregion - - #region ReplaceMany - - /// - /// Returns a new string in which all occurences of specified strings are replaced by other specified strings. - /// - /// The string to filter. - /// The replacements definition. - /// The filtered string. - public string ReplaceMany(string text, IDictionary replacements) - { - // Have done various tests, implementing my own "super fast" state machine to handle - // replacement of many items, or via regexes, but on short strings and not too - // many replacements (which prob. is going to be our case) nothing can beat this... - // (at least with safe and checked code -- we don't want unsafe/unchecked here) - - // Note that it will do chained-replacements ie replaced items can be replaced - // in turn by another replacement (ie the order of replacements is important) - - return replacements.Aggregate(text, (current, kvp) => current.Replace(kvp.Key, kvp.Value)); - } - - /// - /// Returns a new string in which all occurences of specified characters are replaced by a specified character. - /// - /// The string to filter. - /// The characters to replace. - /// The replacement character. - /// The filtered string. - public string ReplaceMany(string text, char[] chars, char replacement) - { - // be safe - if (text == null) - throw new ArgumentNullException("text"); - if (chars == null) - throw new ArgumentNullException("chars"); - - // see note above - - return chars.Aggregate(text, (current, c) => current.Replace(c, replacement)); - } - - #endregion - } -} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index e699a351a5..57c9263ea7 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1300,7 +1300,6 @@ - diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index 8faa22ae25..4b750b6894 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -46,7 +46,7 @@ namespace Umbraco.Tests.Routing UmbracoApiControllerResolver.Current = new UmbracoApiControllerResolver( new ActivatorServiceProvider(), Logger, PluginManager.Current.ResolveUmbracoApiControllers()); - ShortStringHelperResolver.Current = new ShortStringHelperResolver(new LegacyShortStringHelper()); + ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper(SettingsForTests.GetDefault())); base.FreezeResolution(); } diff --git a/src/Umbraco.Tests/Strings/CmsHelperCasingTests.cs b/src/Umbraco.Tests/Strings/CmsHelperCasingTests.cs index 77682661b2..dc39650baa 100644 --- a/src/Umbraco.Tests/Strings/CmsHelperCasingTests.cs +++ b/src/Umbraco.Tests/Strings/CmsHelperCasingTests.cs @@ -30,19 +30,6 @@ namespace Umbraco.Tests.Strings Assert.AreEqual(expected, output); } - [TestCase("thisIsTheEnd", "This Is The End")] - [TestCase("th", "Th")] - [TestCase("t", "t")] - [TestCase("thisis", "Thisis")] - [TestCase("ThisIsTheEnd", "This Is The End")] - [TestCase("WhoIsNumber6InTheVillage", "Who Is Number6In The Village")] // we're happy to reproduce the issue - public void CompatibleLegacyReplacement(string input, string expected) - { - var helper = new LegacyShortStringHelper(); - var output = input.Length < 2 ? input : helper.SplitPascalCasing(input, ' ').ToFirstUpperInvariant(); - Assert.AreEqual(expected, output); - } - [TestCase("thisIsTheEnd", "This Is The End")] [TestCase("th", "Th")] [TestCase("t", "t")] diff --git a/src/Umbraco.Tests/Strings/LegacyShortStringHelperTests.cs b/src/Umbraco.Tests/Strings/LegacyShortStringHelperTests.cs deleted file mode 100644 index 073b7add49..0000000000 --- a/src/Umbraco.Tests/Strings/LegacyShortStringHelperTests.cs +++ /dev/null @@ -1,358 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using Umbraco.Core.Strings; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.Strings -{ - [TestFixture] - public class LegacyShortStringHelperTests - { - private LegacyShortStringHelper _helper; - - [SetUp] - public void Setup() - { - var config = SettingsForTests.GetDefault(); - SettingsForTests.ConfigureSettings(config); - _helper = new LegacyShortStringHelper(); - } - - [TearDown] - public void TearDown() - { - } - - - #region Cases - [TestCase("foo", "foo")] - [TestCase(" foo ", "Foo")] - [TestCase("Foo", "Foo")] - [TestCase("FoO", "FoO")] - [TestCase("FoO bar", "FoOBar")] - [TestCase("FoO bar NIL", "FoOBarNIL")] - [TestCase("FoO 33bar 22NIL", "FoO33bar22NIL")] - [TestCase("FoO 33bar 22NI", "FoO33bar22NI")] - [TestCase("0foo", "foo")] - [TestCase("2foo bar", "fooBar")] - [TestCase("9FOO", "FOO")] - [TestCase("foo-BAR", "foo-BAR")] - [TestCase("foo-BA-dang", "foo-BA-dang")] - [TestCase("foo_BAR", "foo_BAR")] - [TestCase("foo'BAR", "fooBAR")] - [TestCase("sauté dans l'espace", "sauteDansLespace", IgnoreReason = "non-supported non-ascii chars")] - [TestCase("foo\"\"bar", "foobar")] - [TestCase("-foo-", "-foo-")] - [TestCase("_foo_", "_foo_")] - [TestCase("spécial", "special", IgnoreReason = "non-supported non-ascii chars")] - [TestCase("brô dëk ", "broDek", IgnoreReason = "non-supported non-ascii chars")] - [TestCase("1235brô dëk ", "broDek", IgnoreReason = "non-supported non-ascii chars")] - [TestCase("汉#字*/漢?字", "")] - [TestCase("aa DB cd EFG X KLMN OP qrst", "aaDBCdEFGXKLMNOPQrst")] - [TestCase("AA db cd EFG X KLMN OP qrst", "AADbCdEFGXKLMNOPQrst")] - [TestCase("AAA db cd EFG X KLMN OP qrst", "AAADbCdEFGXKLMNOPQrst")] - [TestCase("4 ways selector", "WaysSelector")] - [TestCase("WhatIfWeDoItAgain", "WhatIfWeDoItAgain")] - [TestCase("whatIfWeDoItAgain", "whatIfWeDoItAgain")] - [TestCase("WhatIfWEDOITAgain", "WhatIfWEDOITAgain")] - [TestCase("WhatIfWe doItAgain", "WhatIfWeDoItAgain")] - #endregion - public void CleanStringForSafeAlias(string input, string expected) - { - // NOTE legacy CleanStringForSafeAlias has issues w/some cases - // -> ignore test cases - // also, some aliases are strange... how can "-foo-" be a valid alias? - var output = _helper.CleanStringForSafeAlias(input); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("Tab 1", "tab1")] - [TestCase("Home - Page", "homePage")] - [TestCase("Home.Page", "homePage")] - [TestCase("Shannon's Document Type", "shannonsDocumentType")] // look, lowercase s and the end of shannons - [TestCase("!BADDLY nam-ed Document Type", "baddlyNamEdDocumentType")] - [TestCase("i %Want!thisTo end up In Proper@case", "iWantThisToEndUpInProperCase")] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "raksmorgasKeKe", IgnoreReason = "non-supported non-ascii chars")] - [TestCase("TRii", "tRii")] - [TestCase("**TRii", "tRii")] - [TestCase("trII", "trII")] - [TestCase("**trII", "trII")] - [TestCase("trIIX", "trIIX")] - [TestCase("**trIIX", "trIIX")] - #endregion - public void LegacyCleanStringForUmbracoAlias(string input, string expected) - { - // NOTE ToUmbracoAlias has issues w/non-ascii, and a few other things - // -> ignore test cases - // also all those tests should, in theory, fail because removeSpaces is false by default - var output = _helper.LegacyCleanStringForUmbracoAlias(input); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("Home Page", "home-page")] - [TestCase("Shannon's Home Page!", "shannons-home-page!")] - [TestCase("#Someones's Twitter $h1z%n", "someoness-twitter-$h1zn")] - [TestCase("Räksmörgås", "raeksmoergaas")] - [TestCase("'em guys-over there, are#goin' a \"little\"bit crazy eh!! :)", "em-guys-over-there,-aregoin-a-littlebit-crazy-eh!!-)")] - [TestCase("汉#字*/漢?字", "汉字star漢字")] - [TestCase("Réalösk fix bran#lo'sk", "realosk-fix-bran-lo-sk", IgnoreReason = "cannot handle it")] - #endregion - public void LegacyFormatUrl(string input, string expected) - { - // NOTE CleanStringForUrlSegment has issues with a few cases - // -> ignore test cases - // also some results are a bit strange... - var output = _helper.LegacyFormatUrl(input); - Assert.AreEqual(expected, output); - - // NOTE: not testing the overload with culture - // in legacy, they are the same - } - - #region Cases - [TestCase("Home Page", "home-page", true, true, false)] - [TestCase("Shannon's Home Page!", "shannons-home-page", true, true, false)] - [TestCase("#Someones's Twitter $h1z%n", "someoness-twitter-h1zn", true, true, false)] - [TestCase("Räksmörgås", "rksmrgs", true, true, false)] - [TestCase("'em guys-over there, are#goin' a \"little\"bit crazy eh!! :)", "em-guys-over-there-aregoin-a-littlebit-crazy-eh", true, true, false)] - [TestCase("汉#字*/漢?字", "", true, true, false)] - [TestCase("汉#字*/漢?字", "汉字漢字", true, false, false)] - [TestCase("汉#字*/漢?字", "%e6%b1%89%e5%ad%97%e6%bc%a2%e5%ad%97", true, false, true)] - [TestCase("Réalösk fix bran#lo'sk", "realosk-fix-bran-lo-sk", true, true, false, IgnoreReason = "cannot handle it")] - #endregion - public void LegacyToUrlAlias(string input, string expected, bool replaceDoubleDashes, bool stripNonAscii, bool urlEncode) - { - var replacements = new Dictionary - { - {" ", "-"}, - {"\"", ""}, - {""", ""}, - {"@", ""}, - {"%", ""}, - {".", ""}, - {";", ""}, - {"/", ""}, - {":", ""}, - {"#", ""}, - {"+", ""}, - {"*", ""}, - {"&", ""}, - {"?", ""} - }; - - // NOTE CleanStringForUrlSegment has issues with a few cases - // -> ignore test cases - // also some results are a bit strange... - var output = _helper.LegacyToUrlAlias(input, replacements, replaceDoubleDashes, stripNonAscii, urlEncode); - Assert.AreEqual(expected, output); - - // NOTE: not testing the overload with culture - // in legacy, they are the same - } - - #region Cases - [TestCase("Tab 1", "tab1", CleanStringType.CamelCase)] - [TestCase("Home - Page", "homePage", CleanStringType.CamelCase)] - [TestCase("Shannon's document type", "shannon'sDocumentType", CleanStringType.CamelCase)] - [TestCase("This is the FIRSTTIME of TheDay.", "ThisistheFIRSTTIMEofTheDay", CleanStringType.Unchanged)] - [TestCase("Sépàyô lüx.", "Sepayolux", CleanStringType.Unchanged, IgnoreReason = "non-supported non-ascii chars")] - [TestCase("This is the FIRSTTIME of TheDay.", "ThisIsTheFIRSTTIMEOfTheDay", CleanStringType.PascalCase)] - [TestCase("This is the FIRSTTIME of TheDay.", "thisIsTheFIRSTTIMEOfTheDay", CleanStringType.CamelCase)] - #endregion - public void LegacyConvertStringCase(string input, string expected, CleanStringType caseType) - { - // NOTE LegacyConvertStringCase has issues with a few cases - // -> ignore test cases - // also it removes symbols, etc... except the quote? - var output = _helper.LegacyConvertStringCase(input, caseType); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("ThisIsTheEndMyFriend", "This Is The End My Friend")] - [TestCase("ThisIsTHEEndMyFriend", "This Is THE End My Friend")] - [TestCase("THISIsTHEEndMyFriend", "THIS Is THE End My Friend")] - [TestCase("This33I33sThe33EndMyFriend", "This33 I33s The33 End My Friend", IgnoreReason = "fails")] - [TestCase("ThisIsTHEEndMyFriendX", "This Is THE End My Friend X")] - [TestCase("ThisIsTHEEndMyFriendXYZ", "This Is THE End My Friend XYZ")] - [TestCase("ThisIsTHEEndMyFriendXYZt", "This Is THE End My Friend XY Zt")] - [TestCase("UneÉlévationÀPartir", "Une Élévation À Partir", IgnoreReason = "non-supported non-ascii chars")] - #endregion - public void SplitPascalCasing(string input, string expected) - { - // NOTE legacy SplitPascalCasing has issues w/some cases - // -> ignore test cases - var output = _helper.SplitPascalCasing(input, ' '); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("foo", "foo")] - [TestCase(" foo ", "foo")] - [TestCase("Foo", "foo")] - [TestCase("FoO", "foO")] - [TestCase("FoO bar", "foOBar")] - [TestCase("FoO bar NIL", "foOBarNil")] - [TestCase("FoO 33bar 22NIL", "foO33bar22Nil")] - [TestCase("FoO 33bar 22NI", "foO33bar22NI")] - [TestCase("0foo", "foo")] - [TestCase("2foo bar", "fooBar")] - [TestCase("9FOO", "foo")] - [TestCase("foo-BAR", "fooBar")] - [TestCase("foo-BA-dang", "fooBADang")] - [TestCase("foo_BAR", "fooBar")] - [TestCase("foo'BAR", "fooBar")] - [TestCase("sauté dans l'espace", "sauteDansLEspace")] - [TestCase("foo\"\"bar", "fooBar")] - [TestCase("-foo-", "foo")] - [TestCase("_foo_", "foo")] - [TestCase("spécial", "special")] - [TestCase("brô dëk ", "broDek")] - [TestCase("1235brô dëk ", "broDek")] - [TestCase("汉#字*/漢?字", "")] - [TestCase("aa DB cd EFG X KLMN OP qrst", "aaDBCdEfgXKlmnOPQrst")] - [TestCase("AA db cd EFG X KLMN OP qrst", "aaDbCdEfgXKlmnOPQrst")] - [TestCase("AAA db cd EFG X KLMN OP qrst", "aaaDbCdEfgXKlmnOPQrst")] - #endregion - public void CleanStringToAscii(string input, string expected) - { - var output = _helper.CleanString(input, CleanStringType.Ascii | CleanStringType.CamelCase); - // legacy does nothing - Assert.AreEqual(input, output); - } - - #region Cases - [TestCase("1235brô dëK tzARlan ban123!pOo", "brodeKtzARlanban123pOo", CleanStringType.Unchanged)] - [TestCase(" 1235brô dëK tzARlan ban123!pOo ", "brodeKtzARlanban123pOo", CleanStringType.Unchanged)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "BroDeKTzARLanBan123POo", CleanStringType.PascalCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "broDeKTzARLanBan123POo", CleanStringType.CamelCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "BRODEKTZARLANBAN123POO", CleanStringType.UpperCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "brodektzarlanban123poo", CleanStringType.LowerCase)] - [TestCase("aa DB cd EFG X KLMN OP qrst", "aaDBCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] - [TestCase("aaa DB cd EFG X KLMN OP qrst", "aaaDBCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] - [TestCase("aa DB cd EFG X KLMN OP qrst", "AaDBCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] - [TestCase("aaa DB cd EFG X KLMN OP qrst", "AaaDBCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] - [TestCase("AA db cd EFG X KLMN OP qrst", "aaDbCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] - [TestCase("AAA db cd EFG X KLMN OP qrst", "aaaDbCdEfgXKlmnOPQrst", CleanStringType.CamelCase)] - [TestCase("AA db cd EFG X KLMN OP qrst", "AADbCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] - [TestCase("AAA db cd EFG X KLMN OP qrst", "AaaDbCdEfgXKlmnOPQrst", CleanStringType.PascalCase)] - [TestCase("We store some HTML in the DB for performance", "WeStoreSomeHtmlInTheDBForPerformance", CleanStringType.PascalCase)] - [TestCase("We store some HTML in the DB for performance", "weStoreSomeHtmlInTheDBForPerformance", CleanStringType.CamelCase)] - [TestCase("X is true", "XIsTrue", CleanStringType.PascalCase)] - [TestCase("X is true", "xIsTrue", CleanStringType.CamelCase)] - [TestCase("IO are slow", "IOAreSlow", CleanStringType.PascalCase)] - [TestCase("IO are slow", "ioAreSlow", CleanStringType.CamelCase)] - [TestCase("RAM is fast", "RamIsFast", CleanStringType.PascalCase)] - [TestCase("RAM is fast", "ramIsFast", CleanStringType.CamelCase)] - [TestCase("Tab 1", "tab1", CleanStringType.CamelCase)] - [TestCase("Home - Page", "homePage", CleanStringType.CamelCase)] - [TestCase("Shannon's Document Type", "shannonsDocumentType", CleanStringType.CamelCase)] - [TestCase("!BADDLY nam-ed Document Type", "baddlyNamEdDocumentType", CleanStringType.CamelCase)] - [TestCase(" !BADDLY nam-ed Document Type", "BADDLYnamedDocumentType", CleanStringType.Unchanged)] - [TestCase("!BADDLY nam-ed Document Type", "BaddlyNamEdDocumentType", CleanStringType.PascalCase)] - [TestCase("i %Want!thisTo end up In Proper@case", "IWantThisToEndUpInProperCase", CleanStringType.PascalCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "raksmorgasKeKe", CleanStringType.CamelCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "RaksmorgasKeKe", CleanStringType.PascalCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "RaksmorgaskeKe", CleanStringType.Unchanged)] - [TestCase("TRii", "TRii", CleanStringType.Unchanged)] - [TestCase("**TRii", "TRii", CleanStringType.Unchanged)] - [TestCase("TRii", "trIi", CleanStringType.CamelCase)] - [TestCase("**TRii", "trIi", CleanStringType.CamelCase)] - [TestCase("TRii", "TRIi", CleanStringType.PascalCase)] - [TestCase("**TRii", "TRIi", CleanStringType.PascalCase)] - [TestCase("trII", "trII", CleanStringType.Unchanged)] - [TestCase("**trII", "trII", CleanStringType.Unchanged)] - [TestCase("trII", "trII", CleanStringType.CamelCase)] - [TestCase("**trII", "trII", CleanStringType.CamelCase)] - [TestCase("trII", "TrII", CleanStringType.PascalCase)] - [TestCase("**trII", "TrII", CleanStringType.PascalCase)] - [TestCase("trIIX", "trIix", CleanStringType.CamelCase)] - [TestCase("**trIIX", "trIix", CleanStringType.CamelCase)] - [TestCase("trIIX", "TrIix", CleanStringType.PascalCase)] - [TestCase("**trIIX", "TrIix", CleanStringType.PascalCase)] - #endregion - public void CleanStringToAsciiWithCase(string input, string expected, CleanStringType caseType) - { - var output = _helper.CleanString(input, caseType | CleanStringType.Ascii); - // legacy does nothing - Assert.AreEqual(input, output); - } - - #region Cases - [TestCase("1235brô dëK tzARlan ban123!pOo", "bro de K tz AR lan ban123 p Oo", ' ', CleanStringType.Unchanged)] - [TestCase(" 1235brô dëK tzARlan ban123!pOo ", "bro de K tz AR lan ban123 p Oo", ' ', CleanStringType.Unchanged)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "Bro De K Tz AR Lan Ban123 P Oo", ' ', CleanStringType.PascalCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "Bro De K Tz AR Lan Ban123 P Oo", ' ', CleanStringType.PascalCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "bro De K Tz AR Lan Ban123 P Oo", ' ', CleanStringType.CamelCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "bro-De-K-Tz-AR-Lan-Ban123-P-Oo", '-', CleanStringType.CamelCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "BRO-DE-K-TZ-AR-LAN-BAN123-P-OO", '-', CleanStringType.UpperCase)] - [TestCase("1235brô dëK tzARlan ban123!pOo", "bro-de-k-tz-ar-lan-ban123-p-oo", '-', CleanStringType.LowerCase)] - [TestCase("Tab 1", "tab 1", ' ', CleanStringType.CamelCase)] - [TestCase("Home - Page", "home Page", ' ', CleanStringType.CamelCase)] - [TestCase("Shannon's Document Type", "shannons Document Type", ' ', CleanStringType.CamelCase)] - [TestCase("!BADDLY nam-ed Document Type", "baddly Nam Ed Document Type", ' ', CleanStringType.CamelCase)] - [TestCase(" !BADDLY nam-ed Document Type", "BADDLY nam ed Document Type", ' ', CleanStringType.Unchanged)] - [TestCase("!BADDLY nam-ed Document Type", "Baddly Nam Ed Document Type", ' ', CleanStringType.PascalCase)] - [TestCase("i %Want!thisTo end up In Proper@case", "I Want This To End Up In Proper Case", ' ', CleanStringType.PascalCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "raksmorgas Ke Ke", ' ', CleanStringType.CamelCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "Raksmorgas Ke Ke", ' ', CleanStringType.PascalCase)] - [TestCase("Räksmörgås %%$£¤¤¤§ kéKé", "Raksmorgas ke Ke", ' ', CleanStringType.Unchanged)] - #endregion - public void CleanStringToAsciiWithCaseAndSeparator(string input, string expected, char separator, CleanStringType caseType) - { - var output = _helper.CleanString(input, caseType | CleanStringType.Ascii, separator); - // legacy does nothing - Assert.AreEqual(input, output); - } - - [Test] // can't do cases with an IDictionary - public void ReplaceManyWithCharMap() - { - const string input = "télévisiön tzvâr ßup   pof"; - const string expected = "television tzvar ssup pof"; - IDictionary replacements = new Dictionary - { - { "é", "e" }, - { "ö", "o" }, - { "â", "a" }, - { "ß", "ss" }, - { " ", " " }, - }; - var output = _helper.ReplaceMany(input, replacements); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("val$id!ate|this|str'ing", "$!'", '-', "val-id-ate|this|str-ing")] - [TestCase("val$id!ate|this|str'ing", "$!'", '*', "val*id*ate|this|str*ing")] - #endregion - public void ReplaceManyByOneChar(string input, string toReplace, char replacement, string expected) - { - var output = _helper.ReplaceMany(input, toReplace.ToArray(), replacement); - Assert.AreEqual(expected, output); - } - - #region Cases - [TestCase("foo.txt", "foo.txt")] - [TestCase("foo", "foo", IgnoreReason = "fails when no extension")] - [TestCase(".txt", ".txt")] - [TestCase("nag*dog/poo:xit.txt", "nag-dog-poo-xit.txt")] - [TestCase("the dog is in the house.txt", "the-dog-is-in-the-house.txt")] - [TestCase("nil.nil.nil.txt", "nilnilnil.txt")] // because of chars map - [TestCase("taradabum", "taradabum", IgnoreReason = "fails when no extension")] - [TestCase("tara$$da:b/u - { - {" ", "-"}, - {"\"", ""}, - {""", ""}, - {"@", ""}, - {"%", ""}, - {".", ""}, - {";", ""}, - {"/", ""}, - {":", ""}, - {"#", ""}, - {"+", ""}, - {"*", ""}, - {"&", ""}, - {"?", ""} - }; - - var name1 = "Home Page"; - var name2 = "Shannon's Home Page!"; - var name3 = "#Someones's Twitter $h1z%n"; - var name4 = "Räksmörgås"; - var name5 = "'em guys-over there, are#goin' a \"little\"bit crazy eh!! :)"; - var name6 = "汉#字*/漢?字"; - - var url1 = name1.ToUrlSegment(); - var url2 = name2.ToUrlSegment(); - var url3 = name3.ToUrlSegment(); - var url4 = name4.ToUrlSegment(); - var url5 = name5.ToUrlSegment(); - var url6 = name6.ToUrlSegment(); - var url7 = name6.ToUrlSegment(); - var url8 = name6.ToUrlSegment(); - - Assert.AreEqual("home-page", url1); - Assert.AreEqual("shannons-home-page", url2); - Assert.AreEqual("someoness-twitter-h1zn", url3); - Assert.AreEqual("rksmrgs", url4); - Assert.AreEqual("em-guys-over-there-aregoin-a-littlebit-crazy-eh", url5); - Assert.AreEqual("", url6); - Assert.AreEqual("汉字漢字", url7); - Assert.AreEqual("%e6%b1%89%e5%ad%97%e6%bc%a2%e5%ad%97", url8); - - } - - [TestCase] - public void StringExtensions_To_Camel_Case() - { - //Arrange - - var name1 = "Tab 1"; - var name2 = "Home - Page"; - var name3 = "Shannon's document type"; - - //Act - - var camelCase1 = name1.ToCleanString(CleanStringType.CamelCase); - var camelCase2 = name2.ToCleanString(CleanStringType.CamelCase); - var camelCase3 = name3.ToCleanString(CleanStringType.CamelCase); - - //Assert - - Assert.AreEqual("tab1", camelCase1); - Assert.AreEqual("homePage", camelCase2); - Assert.AreEqual("shannon'sDocumentType", camelCase3); - } - - [TestCase] - public void StringExtensions_To_Entity_Alias() - { - //Arrange - - var name1 = "Tab 1"; - var name2 = "Home - Page"; - var name3 = "Shannon's Document Type"; - var name4 = "!BADDLY nam-ed Document Type"; - var name5 = "i %Want!thisTo end up In Proper@case"; - - //Act - - var alias1 = name1.ToSafeAlias(); - var alias2 = name2.ToSafeAlias(); - var alias3 = name3.ToSafeAlias(); - var alias4 = name4.ToSafeAlias(); - var alias5 = name5.ToSafeAlias(/*StringAliasCaseType.PascalCase*/); - - //Assert - - Assert.AreEqual("tab1", alias1); - Assert.AreEqual("homePage", alias2); - Assert.AreEqual("shannonsDocumentType", alias3); - Assert.AreEqual("baddlyNamEdDocumentType", alias4); - - // disable: does not support PascalCase anymore - //Assert.AreEqual("IWantThisToEndUpInProperCase", alias5); - } - - } -} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 4766505912..e04d17d1c8 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -350,9 +350,7 @@ - - From 07b410459f123f206ff78be8e9f1a8eae55722a4 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 20 Apr 2016 17:40:23 +0200 Subject: [PATCH 098/101] Fixing tests - file systems --- .../Services/Importing/PackageImportTests.cs | 5 +++ .../TestHelpers/BaseUmbracoApplicationTest.cs | 35 +++++++++++-------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs b/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs index f506933631..c69efed5d8 100644 --- a/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs +++ b/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs @@ -147,15 +147,20 @@ namespace Umbraco.Tests.Services.Importing var xml = XElement.Parse(strXml); var element = xml.Descendants("Templates").First(); var packagingService = ServiceContext.PackagingService; + var init = ServiceContext.FileService.GetTemplates().Count(); // Act var templates = packagingService.ImportTemplates(element); var numberOfTemplates = (from doc in element.Elements("Template") select doc).Count(); + var allTemplates = ServiceContext.FileService.GetTemplates(); // Assert Assert.That(templates, Is.Not.Null); Assert.That(templates.Any(), Is.True); Assert.That(templates.Count(), Is.EqualTo(numberOfTemplates)); + + Assert.AreEqual(init + numberOfTemplates, allTemplates.Count()); + Assert.IsTrue(allTemplates.All(x => x.Content.Contains("UmbracoTemplatePage"))); } [Test] diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs index 49def25762..8db672f59c 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs @@ -70,7 +70,7 @@ namespace Umbraco.Tests.TestHelpers SetupApplicationContext(); - InitializeMappers(); + InitializeMappers(); FreezeResolution(); @@ -80,7 +80,7 @@ namespace Umbraco.Tests.TestHelpers public override void TearDown() { base.TearDown(); - + //reset settings SettingsForTests.Reset(); UmbracoContext.Current = null; @@ -97,9 +97,10 @@ namespace Umbraco.Tests.TestHelpers protected virtual void ConfigureContainer() { + // oh no! should not use a container in unit tests? var settings = SettingsForTests.GetDefault(); - //register mappers + //register mappers Container.RegisterFrom(); Container.RegisterFrom(); @@ -109,7 +110,7 @@ namespace Umbraco.Tests.TestHelpers //Default Datalayer/Repositories/SQL/Database/etc... Container.RegisterFrom(); - //register basic stuff that might need to be there for some container resolvers to work, we can + //register basic stuff that might need to be there for some container resolvers to work, we can // add more to this in base classes in resolution freezing Container.RegisterSingleton(factory => Logger); Container.Register(factory => CacheHelper); @@ -127,8 +128,12 @@ namespace Umbraco.Tests.TestHelpers Container.RegisterSingleton(factory => Mock.Of(), "PartialViewFileSystem"); Container.RegisterSingleton(factory => Mock.Of(), "PartialViewMacroFileSystem"); Container.RegisterSingleton(factory => Mock.Of(), "StylesheetFileSystem"); - Container.RegisterSingleton(factory => Mock.Of(), "MasterpageFileSystem"); - Container.RegisterSingleton(factory => Mock.Of(), "ViewFileSystem"); + + // need real file systems here as templates content is on-disk only + //Container.RegisterSingleton(factory => Mock.Of(), "MasterpageFileSystem"); + //Container.RegisterSingleton(factory => Mock.Of(), "ViewFileSystem"); + Container.RegisterSingleton(factory => new PhysicalFileSystem("Views", "/views"), "ViewFileSystem"); + Container.RegisterSingleton(factory => new PhysicalFileSystem("MasterPages", "/masterpages"), "MasterpageFileSystem"); } private static readonly object Locker = new object(); @@ -173,7 +178,7 @@ namespace Umbraco.Tests.TestHelpers } /// - /// By default this returns false which means the plugin manager will not be reset so it doesn't need to re-scan + /// By default this returns false which means the plugin manager will not be reset so it doesn't need to re-scan /// all of the assemblies. Inheritors can override this if plugin manager resetting is required, generally needs /// to be set to true if the SetupPluginManager has been overridden. /// @@ -207,7 +212,7 @@ namespace Umbraco.Tests.TestHelpers /// Inheritors can override this if they wish to create a custom application context /// protected virtual void SetupApplicationContext() - { + { var evtMsgs = new TransientMessagesFactory(); ApplicationContext.Current = new ApplicationContext( //assign the db context @@ -215,13 +220,13 @@ namespace Umbraco.Tests.TestHelpers Logger, SqlSyntax, "System.Data.SqlServerCe.4.0"), //assign the service context ServiceContextHelper.GetServiceContext( - Container.GetInstance(), - new NPocoUnitOfWorkProvider(Logger), + Container.GetInstance(), + new NPocoUnitOfWorkProvider(Logger), new FileUnitOfWorkProvider(), - new PublishingStrategy(evtMsgs, Logger), - CacheHelper, - Logger, - evtMsgs, + new PublishingStrategy(evtMsgs, Logger), + CacheHelper, + Logger, + evtMsgs, Enumerable.Empty()), CacheHelper, ProfilingLogger) @@ -273,7 +278,7 @@ namespace Umbraco.Tests.TestHelpers protected ProfilingLogger ProfilingLogger { get; private set; } protected CacheHelper CacheHelper { get; private set; } - //I know tests shouldn't use IoC, but for all these tests inheriting from this class are integration tests + //I know tests shouldn't use IoC, but for all these tests inheriting from this class are integration tests // and the number of these will hopefully start getting greatly reduced now that most things are mockable. internal IServiceContainer Container { get; private set; } From 23817cdce520da60269b71aa844dab00611058f5 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 20 Apr 2016 22:51:01 +0200 Subject: [PATCH 099/101] Fixing tests - MigrationStartupHandler is IDisposable --- .../MigrationStartupHandlerTests.cs | 2 +- .../ClearCsrfCookiesAfterUpgrade.cs | 2 +- ...ediaXmlCacheForDeletedItemsAfterUpgrade.cs | 2 +- .../EnsureListViewDataTypeIsCreated.cs | 2 +- ...upHander.cs => MigrationStartupHandler.cs} | 25 +++++++++++++++++-- .../OverwriteStylesheetFilesFromTempFiles.cs | 2 +- .../PublishAfterUpgradeToVersionSixth.cs | 2 +- .../RebuildMediaXmlCacheAfterUpgrade.cs | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 4 +-- 9 files changed, 32 insertions(+), 11 deletions(-) rename src/Umbraco.Web/Strategies/Migrations/{MigrationStartupHander.cs => MigrationStartupHandler.cs} (78%) diff --git a/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs b/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs index 0b39985028..13bfb7f7af 100644 --- a/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs +++ b/src/Umbraco.Tests/Persistence/Migrations/MigrationStartupHandlerTests.cs @@ -108,7 +108,7 @@ namespace Umbraco.Tests.Persistence.Migrations public int CountExecuted { get; set; } } - public class TestMigrationHandler : MigrationStartupHander + public class TestMigrationHandler : MigrationStartupHandler { private readonly string _prodName; private readonly Args _changed; diff --git a/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs b/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs index 24b706aee9..f40490c2d1 100644 --- a/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs +++ b/src/Umbraco.Web/Strategies/Migrations/ClearCsrfCookiesAfterUpgrade.cs @@ -10,7 +10,7 @@ namespace Umbraco.Web.Strategies.Migrations /// /// After upgrade we clear out the csrf tokens /// - public class ClearCsrfCookiesAfterUpgrade : MigrationStartupHander + public class ClearCsrfCookiesAfterUpgrade : MigrationStartupHandler { protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) { diff --git a/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs b/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs index 6e0580a2c4..dcf56fd03f 100644 --- a/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs +++ b/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs @@ -16,7 +16,7 @@ namespace Umbraco.Web.Strategies.Migrations /// /// * If current is less than or equal to 7.0.0 /// - public class ClearMediaXmlCacheForDeletedItemsAfterUpgrade : MigrationStartupHander + public class ClearMediaXmlCacheForDeletedItemsAfterUpgrade : MigrationStartupHandler { private readonly ISqlSyntaxProvider _sqlSyntax; diff --git a/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs b/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs index 92325ecaa7..706ffe181d 100644 --- a/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs +++ b/src/Umbraco.Web/Strategies/Migrations/EnsureListViewDataTypeIsCreated.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Strategies.Migrations /// /// Creates the built in list view data types /// - public class EnsureDefaultListViewDataTypesCreated : MigrationStartupHander + public class EnsureDefaultListViewDataTypesCreated : MigrationStartupHandler { private readonly ISqlSyntaxProvider _sqlSyntax; diff --git a/src/Umbraco.Web/Strategies/Migrations/MigrationStartupHander.cs b/src/Umbraco.Web/Strategies/Migrations/MigrationStartupHandler.cs similarity index 78% rename from src/Umbraco.Web/Strategies/Migrations/MigrationStartupHander.cs rename to src/Umbraco.Web/Strategies/Migrations/MigrationStartupHandler.cs index a350911580..3b40bbdf5d 100644 --- a/src/Umbraco.Web/Strategies/Migrations/MigrationStartupHander.cs +++ b/src/Umbraco.Web/Strategies/Migrations/MigrationStartupHandler.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Umbraco.Core; using Umbraco.Core.Persistence.Migrations; @@ -7,8 +8,10 @@ namespace Umbraco.Web.Strategies.Migrations /// /// Base class that can be used to run code after the migration runner has executed /// - public abstract class MigrationStartupHander : ApplicationEventHandler + public abstract class MigrationStartupHandler : ApplicationEventHandler, IDisposable { + private bool _disposed; + /// /// Ensure this is run when not configured /// @@ -57,5 +60,23 @@ namespace Umbraco.Web.Strategies.Migrations /// Leaving empty will run for all migration products /// public virtual string[] TargetProductNames { get { return new string[] {}; } } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~MigrationStartupHandler() + { + Dispose(false); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) return; + _disposed = true; + Unsubscribe(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Strategies/Migrations/OverwriteStylesheetFilesFromTempFiles.cs b/src/Umbraco.Web/Strategies/Migrations/OverwriteStylesheetFilesFromTempFiles.cs index 9a8b6a6044..d8e5ded98f 100644 --- a/src/Umbraco.Web/Strategies/Migrations/OverwriteStylesheetFilesFromTempFiles.cs +++ b/src/Umbraco.Web/Strategies/Migrations/OverwriteStylesheetFilesFromTempFiles.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.Strategies.Migrations /// files during the migration since other parts of the migration might fail. So once the migration is complete, we'll then copy over the temp /// files that this migration created over top of the developer's files. We'll also create a backup of their files. ///
      - public sealed class OverwriteStylesheetFilesFromTempFiles : MigrationStartupHander + public sealed class OverwriteStylesheetFilesFromTempFiles : MigrationStartupHandler { protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) { diff --git a/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs b/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs index 66958a2c9d..125b8f7a86 100644 --- a/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs +++ b/src/Umbraco.Web/Strategies/Migrations/PublishAfterUpgradeToVersionSixth.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Strategies.Migrations /// This event ensures that upgrades from (configured) versions lower then 6.0.0 /// have their publish state updated after the database schema has been migrated. ///
      - public class PublishAfterUpgradeToVersionSixth : MigrationStartupHander + public class PublishAfterUpgradeToVersionSixth : MigrationStartupHandler { private readonly ISqlSyntaxProvider _sqlSyntax; diff --git a/src/Umbraco.Web/Strategies/Migrations/RebuildMediaXmlCacheAfterUpgrade.cs b/src/Umbraco.Web/Strategies/Migrations/RebuildMediaXmlCacheAfterUpgrade.cs index 2af3c402aa..63c8d5db16 100644 --- a/src/Umbraco.Web/Strategies/Migrations/RebuildMediaXmlCacheAfterUpgrade.cs +++ b/src/Umbraco.Web/Strategies/Migrations/RebuildMediaXmlCacheAfterUpgrade.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Strategies.Migrations /// /// * If current is less than or equal to 7.0.0 /// - public class RebuildMediaXmlCacheAfterUpgrade : MigrationStartupHander + public class RebuildMediaXmlCacheAfterUpgrade : MigrationStartupHandler { protected override void AfterMigration(MigrationRunner sender, MigrationEventArgs e) { diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c381646598..9109efab24 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -697,7 +697,7 @@ - + @@ -1108,7 +1108,7 @@ ASPXCodeBehind - + From 55ae0d3af6d263c5ae6a5f798058c3eae7e828a8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 21 Apr 2016 11:38:43 +0200 Subject: [PATCH 100/101] Ensure the correct services are injected in to property editors so they are not using singletons --- .../PropertyEditors/ColorListValidatorTest.cs | 7 ++++--- .../EnsureUniqueValuesValidatorTest.cs | 11 ++++++----- .../PropertyEditors/MultiValuePropertyEditorTests.cs | 2 +- .../PropertyEditors/CheckBoxListPropertyEditor.cs | 8 ++++++-- .../PropertyEditors/ColorListPreValueEditor.cs | 3 ++- .../PropertyEditors/ColorPickerPropertyEditor.cs | 8 ++++++-- .../PropertyEditors/DropDownMultiplePropertyEditor.cs | 3 ++- .../DropDownMultipleWithKeysPropertyEditor.cs | 10 +++++++--- .../PropertyEditors/DropDownPropertyEditor.cs | 3 ++- .../PropertyEditors/DropDownWithKeysPropertyEditor.cs | 8 ++++++-- .../PropertyEditors/FileUploadPropertyEditor.cs | 11 +++++++---- .../PropertyEditors/RadioButtonsPropertyEditor.cs | 3 ++- .../PropertyEditors/ValueListPreValueEditor.cs | 6 ++++-- 13 files changed, 55 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Tests/PropertyEditors/ColorListValidatorTest.cs b/src/Umbraco.Tests/PropertyEditors/ColorListValidatorTest.cs index 900b8399e7..a6ef28a40e 100644 --- a/src/Umbraco.Tests/PropertyEditors/ColorListValidatorTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ColorListValidatorTest.cs @@ -3,6 +3,7 @@ using Moq; using NUnit.Framework; using Newtonsoft.Json.Linq; using Umbraco.Core.Logging; +using Umbraco.Core.Services; using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.PropertyEditors @@ -14,7 +15,7 @@ namespace Umbraco.Tests.PropertyEditors public void Only_Tests_On_JArray() { var validator = new ColorListPreValueEditor.ColorListValidator(); - var result = validator.Validate("hello", null, new ColorPickerPropertyEditor(Mock.Of())); + var result = validator.Validate("hello", null, new ColorPickerPropertyEditor(Mock.Of(), Mock.Of())); Assert.AreEqual(0, result.Count()); } @@ -22,7 +23,7 @@ namespace Umbraco.Tests.PropertyEditors public void Only_Tests_On_JArray_Of_Item_JObject() { var validator = new ColorListPreValueEditor.ColorListValidator(); - var result = validator.Validate(new JArray("hello", "world"), null, new ColorPickerPropertyEditor(Mock.Of())); + var result = validator.Validate(new JArray("hello", "world"), null, new ColorPickerPropertyEditor(Mock.Of(), Mock.Of())); Assert.AreEqual(0, result.Count()); } @@ -35,7 +36,7 @@ namespace Umbraco.Tests.PropertyEditors JObject.FromObject(new { value = "zxcvzxcvxzcv" }), JObject.FromObject(new { value = "ABC" }), JObject.FromObject(new { value = "1234567" })), - null, new ColorPickerPropertyEditor(Mock.Of())); + null, new ColorPickerPropertyEditor(Mock.Of(), Mock.Of())); Assert.AreEqual(2, result.Count()); } } diff --git a/src/Umbraco.Tests/PropertyEditors/EnsureUniqueValuesValidatorTest.cs b/src/Umbraco.Tests/PropertyEditors/EnsureUniqueValuesValidatorTest.cs index c24aa65846..bd50d92a94 100644 --- a/src/Umbraco.Tests/PropertyEditors/EnsureUniqueValuesValidatorTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/EnsureUniqueValuesValidatorTest.cs @@ -3,6 +3,7 @@ using Moq; using NUnit.Framework; using Newtonsoft.Json.Linq; using Umbraco.Core.Logging; +using Umbraco.Core.Services; using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.PropertyEditors @@ -14,7 +15,7 @@ namespace Umbraco.Tests.PropertyEditors public void Only_Tests_On_JArray() { var validator = new ValueListPreValueEditor.EnsureUniqueValuesValidator(); - var result = validator.Validate("hello", null, new ColorPickerPropertyEditor(Mock.Of())); + var result = validator.Validate("hello", null, new ColorPickerPropertyEditor(Mock.Of(), Mock.Of())); Assert.AreEqual(0, result.Count()); } @@ -22,7 +23,7 @@ namespace Umbraco.Tests.PropertyEditors public void Only_Tests_On_JArray_Of_Item_JObject() { var validator = new ValueListPreValueEditor.EnsureUniqueValuesValidator(); - var result = validator.Validate(new JArray("hello", "world"), null, new ColorPickerPropertyEditor(Mock.Of())); + var result = validator.Validate(new JArray("hello", "world"), null, new ColorPickerPropertyEditor(Mock.Of(), Mock.Of())); Assert.AreEqual(0, result.Count()); } @@ -30,7 +31,7 @@ namespace Umbraco.Tests.PropertyEditors public void Allows_Unique_Values() { var validator = new ValueListPreValueEditor.EnsureUniqueValuesValidator(); - var result = validator.Validate(new JArray(JObject.FromObject(new { value = "hello" }), JObject.FromObject(new { value = "world" })), null, new ColorPickerPropertyEditor(Mock.Of())); + var result = validator.Validate(new JArray(JObject.FromObject(new { value = "hello" }), JObject.FromObject(new { value = "world" })), null, new ColorPickerPropertyEditor(Mock.Of(), Mock.Of())); Assert.AreEqual(0, result.Count()); } @@ -39,7 +40,7 @@ namespace Umbraco.Tests.PropertyEditors { var validator = new ValueListPreValueEditor.EnsureUniqueValuesValidator(); var result = validator.Validate(new JArray(JObject.FromObject(new { value = "hello" }), JObject.FromObject(new { value = "hello" })), - null, new ColorPickerPropertyEditor(Mock.Of())); + null, new ColorPickerPropertyEditor(Mock.Of(), Mock.Of())); Assert.AreEqual(1, result.Count()); } @@ -52,7 +53,7 @@ namespace Umbraco.Tests.PropertyEditors JObject.FromObject(new { value = "hello" }), JObject.FromObject(new { value = "world" }), JObject.FromObject(new { value = "world" })), - null, new ColorPickerPropertyEditor(Mock.Of())); + null, new ColorPickerPropertyEditor(Mock.Of(), Mock.Of())); Assert.AreEqual(2, result.Count()); } } diff --git a/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs index 0ba844ca6b..6fe7259b92 100644 --- a/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs @@ -124,7 +124,7 @@ namespace Umbraco.Tests.PropertyEditors {"item3", new PreValue(3, "Item 3")} }); - var editor = new ValueListPreValueEditor(); + var editor = new ValueListPreValueEditor(Mock.Of()); var result = editor.ConvertDbToEditor(defaultVals, persisted); diff --git a/src/Umbraco.Web/PropertyEditors/CheckBoxListPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/CheckBoxListPropertyEditor.cs index 2cebd665cd..e1b72f0ad4 100644 --- a/src/Umbraco.Web/PropertyEditors/CheckBoxListPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/CheckBoxListPropertyEditor.cs @@ -1,6 +1,7 @@ using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { @@ -15,11 +16,14 @@ namespace Umbraco.Web.PropertyEditors [PropertyEditor(Constants.PropertyEditors.CheckBoxListAlias, "Checkbox list", "checkboxlist", Icon="icon-bulleted-list", Group="lists")] public class CheckBoxListPropertyEditor : PropertyEditor { + private readonly ILocalizedTextService _textService; + /// /// The constructor will setup the property editor based on the attribute if one is found /// - public CheckBoxListPropertyEditor(ILogger logger) : base(logger) + public CheckBoxListPropertyEditor(ILogger logger, ILocalizedTextService textService) : base(logger) { + _textService = textService; } /// @@ -31,7 +35,7 @@ namespace Umbraco.Web.PropertyEditors /// protected override PreValueEditor CreatePreValueEditor() { - return new ValueListPreValueEditor(); + return new ValueListPreValueEditor(_textService); } /// diff --git a/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs index e7750c4c65..e9e721c108 100644 --- a/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ColorListPreValueEditor.cs @@ -6,13 +6,14 @@ using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { internal class ColorListPreValueEditor : ValueListPreValueEditor { - public ColorListPreValueEditor() + public ColorListPreValueEditor(ILocalizedTextService textService) : base(textService) { var field = Fields.First(); diff --git a/src/Umbraco.Web/PropertyEditors/ColorPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ColorPickerPropertyEditor.cs index 95d2c5f07e..3d2beb64fb 100644 --- a/src/Umbraco.Web/PropertyEditors/ColorPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ColorPickerPropertyEditor.cs @@ -1,17 +1,21 @@ using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { [PropertyEditor(Constants.PropertyEditors.ColorPickerAlias, "Color Picker", "colorpicker", Icon="icon-colorpicker", Group="Pickers")] public class ColorPickerPropertyEditor : PropertyEditor { + private readonly ILocalizedTextService _textService; + /// /// The constructor will setup the property editor based on the attribute if one is found /// - public ColorPickerPropertyEditor(ILogger logger) : base(logger) + public ColorPickerPropertyEditor(ILogger logger, ILocalizedTextService textService) : base(logger) { + _textService = textService; } /// @@ -23,7 +27,7 @@ namespace Umbraco.Web.PropertyEditors /// protected override PreValueEditor CreatePreValueEditor() { - return new ColorListPreValueEditor(); + return new ColorListPreValueEditor(_textService); } } diff --git a/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs index e7cf1bb87b..2728f5c86b 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownMultiplePropertyEditor.cs @@ -2,6 +2,7 @@ using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { @@ -21,7 +22,7 @@ namespace Umbraco.Web.PropertyEditors /// /// The constructor will setup the property editor based on the attribute if one is found /// - public DropDownMultiplePropertyEditor(ILogger logger) : base(logger) + public DropDownMultiplePropertyEditor(ILogger logger, ILocalizedTextService textService) : base(logger, textService) { } diff --git a/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs index 9540c4fdc5..f16d9872ec 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownMultipleWithKeysPropertyEditor.cs @@ -3,6 +3,7 @@ using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { @@ -16,11 +17,14 @@ namespace Umbraco.Web.PropertyEditors [PropertyEditor(Constants.PropertyEditors.DropdownlistMultiplePublishKeysAlias, "Dropdown list multiple, publish keys", "dropdown", Group = "lists", Icon = "icon-bulleted-list")] public class DropDownMultipleWithKeysPropertyEditor : DropDownPropertyEditor { + private readonly ILocalizedTextService _textService; + /// /// The constructor will setup the property editor based on the attribute if one is found /// - public DropDownMultipleWithKeysPropertyEditor(ILogger logger) : base(logger) + public DropDownMultipleWithKeysPropertyEditor(ILogger logger, ILocalizedTextService textService) : base(logger, textService) { + _textService = textService; } protected override PropertyValueEditor CreateValueEditor() @@ -30,7 +34,7 @@ namespace Umbraco.Web.PropertyEditors protected override PreValueEditor CreatePreValueEditor() { - return new DropDownMultiplePreValueEditor(); + return new DropDownMultiplePreValueEditor(_textService); } /// @@ -42,7 +46,7 @@ namespace Umbraco.Web.PropertyEditors /// internal class DropDownMultiplePreValueEditor : ValueListPreValueEditor { - public DropDownMultiplePreValueEditor() + public DropDownMultiplePreValueEditor(ILocalizedTextService textService) : base(textService) { //add the multiple field, we'll make it hidden so it is not seen in the pre-value editor Fields.Add(new PreValueField diff --git a/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs index 3607a8b5fa..4981e88412 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownPropertyEditor.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; using umbraco; using ClientDependency.Core; +using Umbraco.Core.Services; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.PropertyEditors @@ -23,7 +24,7 @@ namespace Umbraco.Web.PropertyEditors /// /// The constructor will setup the property editor based on the attribute if one is found /// - public DropDownPropertyEditor(ILogger logger) : base(logger) + public DropDownPropertyEditor(ILogger logger, ILocalizedTextService textService) : base(logger, textService) { } diff --git a/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs index b4138d170a..2192af8d4f 100644 --- a/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DropDownWithKeysPropertyEditor.cs @@ -1,6 +1,7 @@ using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { @@ -15,11 +16,14 @@ namespace Umbraco.Web.PropertyEditors [PropertyEditor(Constants.PropertyEditors.DropdownlistPublishingKeysAlias, "Dropdown list, publishing keys", "dropdown", ValueType = "INT", Group = "lists", Icon = "icon-indent")] public class DropDownWithKeysPropertyEditor : PropertyEditor { + private readonly ILocalizedTextService _textService; + /// /// The constructor will setup the property editor based on the attribute if one is found /// - public DropDownWithKeysPropertyEditor(ILogger logger) : base(logger) + public DropDownWithKeysPropertyEditor(ILogger logger, ILocalizedTextService textService) : base(logger) { + _textService = textService; } /// @@ -28,7 +32,7 @@ namespace Umbraco.Web.PropertyEditors /// protected override PreValueEditor CreatePreValueEditor() { - return new ValueListPreValueEditor(); + return new ValueListPreValueEditor(_textService); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs index 9b4e722b30..809716af62 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs @@ -24,14 +24,17 @@ namespace Umbraco.Web.PropertyEditors { private readonly MediaFileSystem _mediaFileSystem; private readonly IContentSection _contentSettings; + private readonly ILocalizedTextService _textService; - public FileUploadPropertyEditor(ILogger logger, MediaFileSystem mediaFileSystem, IContentSection contentSettings) + public FileUploadPropertyEditor(ILogger logger, MediaFileSystem mediaFileSystem, IContentSection contentSettings, ILocalizedTextService textService) : base(logger) { if (mediaFileSystem == null) throw new ArgumentNullException("mediaFileSystem"); if (contentSettings == null) throw new ArgumentNullException("contentSettings"); + if (textService == null) throw new ArgumentNullException("textService"); _mediaFileSystem = mediaFileSystem; _contentSettings = contentSettings; + _textService = textService; MemberService.Deleted += (sender, args) => args.MediaFilesToDelete.AddRange(ServiceDeleted(args.DeletedEntities.Cast())); } @@ -49,7 +52,7 @@ namespace Umbraco.Web.PropertyEditors protected override PreValueEditor CreatePreValueEditor() { - return new FileUploadPreValueEditor(); + return new FileUploadPreValueEditor(_textService); } /// @@ -172,8 +175,8 @@ namespace Umbraco.Web.PropertyEditors /// internal class FileUploadPreValueEditor : ValueListPreValueEditor { - public FileUploadPreValueEditor() - : base() + public FileUploadPreValueEditor(ILocalizedTextService textService) + : base(textService) { var field = Fields.First(); field.Description = "Enter a max width/height for each thumbnail"; diff --git a/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs index 24c3a275fb..8ac6bff69e 100644 --- a/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RadioButtonsPropertyEditor.cs @@ -1,6 +1,7 @@ using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors { @@ -18,7 +19,7 @@ namespace Umbraco.Web.PropertyEditors /// /// The constructor will setup the property editor based on the attribute if one is found /// - public RadioButtonsPropertyEditor(ILogger logger) : base(logger) + public RadioButtonsPropertyEditor(ILogger logger, ILocalizedTextService textService) : base(logger, textService) { } } diff --git a/src/Umbraco.Web/PropertyEditors/ValueListPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ValueListPreValueEditor.cs index 2c6357de3d..e11a969bfe 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueListPreValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueListPreValueEditor.cs @@ -22,9 +22,11 @@ namespace Umbraco.Web.PropertyEditors /// internal class ValueListPreValueEditor : PreValueEditor { + private readonly ILocalizedTextService _textService; - public ValueListPreValueEditor() + public ValueListPreValueEditor(ILocalizedTextService textService) { + _textService = textService; Fields.AddRange(CreatePreValueFields()); } @@ -46,7 +48,7 @@ namespace Umbraco.Web.PropertyEditors //It's also important to note that by default the dropdown angular controller is expecting the // config options to come in with a property called 'items' Key = "items", - Name = ApplicationContext.Current.Services.TextService.Localize("editdatatype/addPrevalue"), // todo: inject + Name = _textService.Localize("editdatatype/addPrevalue"), // todo: inject View = "multivalues" } }; From 081d6ecc079db510376991952b38e821fcf00ddd Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 21 Apr 2016 12:15:39 +0200 Subject: [PATCH 101/101] adds notes --- src/Umbraco.Tests/TestHelpers/ServiceContextHelper.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Tests/TestHelpers/ServiceContextHelper.cs b/src/Umbraco.Tests/TestHelpers/ServiceContextHelper.cs index 5c4485871e..468c4b19b3 100644 --- a/src/Umbraco.Tests/TestHelpers/ServiceContextHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/ServiceContextHelper.cs @@ -17,6 +17,8 @@ namespace Umbraco.Tests.TestHelpers { class ServiceContextHelper { + //NOTE: Should be used sparingly for integration tests only - for unit tests you can just mock the services to be passed to the + // ctor of the ServiceContext. public static ServiceContext GetServiceContext(RepositoryFactory repositoryFactory, IDatabaseUnitOfWorkProvider dbUnitOfWorkProvider, IUnitOfWorkProvider fileUnitOfWorkProvider,