From 6b23a8221d20a240c270d87dad8930580bff81e9 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 15 Apr 2019 16:30:46 +0200 Subject: [PATCH 001/776] Bugfix Audit service --- .../Persistence/Mappers/AuditItemMapper.cs | 3 ++- .../Querying/ModelToSqlExpressionVisitor.cs | 11 +++++++++++ .../Persistence/Repositories/IAuditRepository.cs | 2 ++ .../Repositories/Implement/AuditRepository.cs | 12 ++++++++++++ .../Services/Implement/AuditService.cs | 8 ++++---- src/Umbraco.Tests/Services/AuditServiceTests.cs | 15 +++++++++++++++ 6 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs b/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs index 853cd9f99e..48e7afdc7e 100644 --- a/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/AuditItemMapper.cs @@ -18,7 +18,8 @@ namespace Umbraco.Core.Persistence.Mappers DefineMap(nameof(AuditItem.Id), nameof(LogDto.NodeId)); DefineMap(nameof(AuditItem.CreateDate), nameof(LogDto.Datestamp)); DefineMap(nameof(AuditItem.UserId), nameof(LogDto.UserId)); - DefineMap(nameof(AuditItem.AuditType), nameof(LogDto.Header)); + // we cannot map that one - because AuditType is an enum but Header is a string + //DefineMap(nameof(AuditItem.AuditType), nameof(LogDto.Header)); DefineMap(nameof(AuditItem.Comment), nameof(LogDto.Comment)); } } diff --git a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs index 03d82a345f..c87937e41e 100644 --- a/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs +++ b/src/Umbraco.Core/Persistence/Querying/ModelToSqlExpressionVisitor.cs @@ -85,6 +85,17 @@ namespace Umbraco.Core.Persistence.Querying // I'm just unsure right now due to time constraints how to make it correct. It won't matter right now and has been working already with this bug but I've // only just discovered what it is actually doing. + // TODO + // in most cases we want to convert the value to a plain object, + // but for in some rare cases, we may want to do it differently, + // for instance a Models.AuditType (an enum) may in some cases + // need to be converted to its string value. + // but - we cannot have specific code here, really - and how would + // we configure this? is it even possible? + /* + var toString = typeof(object).GetMethod("ToString"); + var member = Expression.Call(m, toString); + */ var member = Expression.Convert(m, typeof(object)); var lambda = Expression.Lambda>(member); var getter = lambda.Compile(); diff --git a/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs index 7c8a82bb85..b2dd6a3297 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs @@ -33,5 +33,7 @@ namespace Umbraco.Core.Persistence.Repositories Direction orderDirection, AuditType[] auditTypeFilter, IQuery customFilter); + + IEnumerable Get(AuditType type, IQuery query); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs index cda89fd89a..c25328b10c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditRepository.cs @@ -74,6 +74,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return dtos.Select(x => new AuditItem(x.NodeId, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters)).ToList(); } + public IEnumerable Get(AuditType type, IQuery query) + { + var sqlClause = GetBaseQuery(false) + .Where(x => x.Header == type.ToString()); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate(); + + var dtos = Database.Fetch(sql); + + return dtos.Select(x => new AuditItem(x.NodeId, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters)).ToList(); + } + protected override Sql GetBaseQuery(bool isCount) { var sql = SqlContext.Sql(); diff --git a/src/Umbraco.Core/Services/Implement/AuditService.cs b/src/Umbraco.Core/Services/Implement/AuditService.cs index 46c851a789..5eb08f2dea 100644 --- a/src/Umbraco.Core/Services/Implement/AuditService.cs +++ b/src/Umbraco.Core/Services/Implement/AuditService.cs @@ -51,8 +51,8 @@ namespace Umbraco.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { var result = sinceDate.HasValue == false - ? _auditRepository.Get(Query().Where(x => x.UserId == userId && x.AuditType == type)) - : _auditRepository.Get(Query().Where(x => x.UserId == userId && x.AuditType == type && x.CreateDate >= sinceDate.Value)); + ? _auditRepository.Get(type, Query().Where(x => x.UserId == userId)) + : _auditRepository.Get(type, Query().Where(x => x.UserId == userId && x.CreateDate >= sinceDate.Value)); scope.Complete(); return result; } @@ -63,8 +63,8 @@ namespace Umbraco.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { var result = sinceDate.HasValue == false - ? _auditRepository.Get(Query().Where(x => x.AuditType == type)) - : _auditRepository.Get(Query().Where(x => x.AuditType == type && x.CreateDate >= sinceDate.Value)); + ? _auditRepository.Get(type, Query()) + : _auditRepository.Get(type, Query().Where(x => x.CreateDate >= sinceDate.Value)); scope.Complete(); return result; } diff --git a/src/Umbraco.Tests/Services/AuditServiceTests.cs b/src/Umbraco.Tests/Services/AuditServiceTests.cs index 6064fe4acc..bfec246e61 100644 --- a/src/Umbraco.Tests/Services/AuditServiceTests.cs +++ b/src/Umbraco.Tests/Services/AuditServiceTests.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using Umbraco.Core.Services.Implement; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; +using Umbraco.Core.Models; namespace Umbraco.Tests.Services { @@ -48,5 +49,19 @@ namespace Umbraco.Tests.Services Assert.AreEqual(123 + 5, entries[0].PerformingUserId); Assert.AreEqual(123 + 4, entries[1].PerformingUserId); } + + [Test] + public void CanReadEntries() + { + var yesterday = DateTime.UtcNow.AddDays(-1); + + for (var i = 0; i < 10; i++) + { + yesterday = yesterday.AddMinutes(1); + ServiceContext.AuditService.Add(AuditType.Unpublish, -1, 33, "", "blah"); + } + + var logs = ServiceContext.AuditService.GetUserLogs(-1, AuditType.Unpublish); + } } } From 463dfc45a60593bc3dd40a924b72fbf1b3ec8ce9 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 6 Jun 2019 09:34:27 +0200 Subject: [PATCH 002/776] Copy all items from one NC to another (WIP) --- .../nestedcontent/nestedcontent.controller.js | 52 +++++++++++++++++-- .../nestedcontent/nestedcontent.html | 3 ++ src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 1 + .../Umbraco/config/lang/en_us.xml | 1 + 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 4e8b35a276..b02ab3ffff 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -93,8 +93,10 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop "iconHelper", "clipboardService", "eventsService", - - function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService) { + "$routeParams", + "editorState", + + function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService, $routeParams, editorState) { var contentTypeAliases = []; _.each($scope.model.config.contentTypes, function (contentType) { @@ -160,7 +162,15 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop view: "itempicker", event: $event, clickPasteItem: function(item) { - $scope.pasteFromClipboard(item.data); + if (item.alias === "nc_pasteAllItems") { + _.each(item.data, function (node) { + delete node.$$hashKey; + $scope.pasteFromClipboard(node); + }); + } else { + $scope.pasteFromClipboard(item.data); + } + $scope.overlayMenu.show = false; $scope.overlayMenu = null; }, @@ -194,6 +204,20 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop $scope.overlayMenu.size = $scope.overlayMenu.availableItems.length > 6 ? "medium" : "small"; $scope.overlayMenu.pasteItems = []; + var nestedContentForPaste = clipboardService.retriveDataOfType("nestedContent", ["nc_copyOfAllItems"]); + _.each(nestedContentForPaste, function (nestedContent) { + if (_.every(nestedContent.nodes, + function(node) { + return contentTypeAliases.indexOf(node.contentTypeAlias) >= 0; + })) { + $scope.overlayMenu.pasteItems.push({ + alias: "nc_pasteAllItems", + name: nestedContent.name, // source property name + data: nestedContent.nodes, // all items from source property + icon: "icon-bulleted-list" + }); + } + }); var availableNodesForPaste = clipboardService.retriveDataOfType("elementType", contentTypeAliases); _.each(availableNodesForPaste, function (node) { $scope.overlayMenu.pasteItems.push({ @@ -210,6 +234,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop $event.stopPropagation(); $event.preventDefault(); clipboardService.clearEntriesOfType("elementType", contentTypeAliases); + clipboardService.clearEntriesOfType("nestedContent", ["nc_copyOfAllItems"]); $scope.overlayMenu.pasteItems = [];// This dialog is not connected via the clipboardService events, so we need to update manually. }; @@ -359,7 +384,26 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop clipboardService.copy("elementType", node.contentTypeAlias, node); $event.stopPropagation(); } - + + $scope.clickCopyAll = function () { + + syncCurrentNode(); + + var culture = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture; + var activeVariant = _.find(editorState.current.variants, function (v) { + return !v.language || v.language.culture === culture; + }); + + localizationService.localize("content_nestedContentCopyAllItemsName", [$scope.model.label, activeVariant.name]).then(function(data) { + var model = { + nodes: $scope.nodes, + key: "nc_" + $scope.model.alias, + name: data + }; + clipboardService.copy("nestedContent", "nc_copyOfAllItems", model); + }); + } + $scope.pasteFromClipboard = function(newNode) { if (newNode === undefined) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html index 1eb7311ca7..a3196e8928 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html @@ -42,6 +42,9 @@ + + + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 189bd9f10b..c439ed01c3 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -273,6 +273,7 @@ Are you sure you want to delete this item? Property %0% uses editor %1% which is not supported by Nested Content. No content types are configured for this property. + %0% from %1% Add another text box Remove this text box Content root 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 6ce6f82ccc..efcfcdc056 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -277,6 +277,7 @@ Are you sure you want to delete this item? Property %0% uses editor %1% which is not supported by Nested Content. No content types are configured for this property. + %0% from %1% Add another text box Remove this text box Content root From 1dd2c1cb915a9b807ccb9425d0e61ac0d4a1bf6d Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 19 Jun 2019 14:30:44 +0200 Subject: [PATCH 003/776] Revert "Revert "U4 10147 - Bypass User Security option in pickers (#2441)"" This reverts commit 994c6eed --- .../tree/umbtreesearchbox.directive.js | 7 + .../src/common/resources/content.resource.js | 1577 +++++++++-------- .../src/common/resources/entity.resource.js | 49 +- .../src/common/resources/media.resource.js | 6 +- .../src/common/services/search.service.js | 22 +- .../overlays/contentpicker/contentpicker.html | 3 +- .../linkpicker/linkpicker.controller.js | 301 ++-- .../overlays/linkpicker/linkpicker.html | 2 + .../mediaPicker/mediapicker.controller.js | 55 +- .../treepicker/treepicker.controller.js | 1047 +++++------ .../overlays/treepicker/treepicker.html | 1 + .../contentpicker/contentpicker.controller.js | 4 +- .../grid/editors/media.controller.js | 17 +- .../grid/editors/rte.controller.js | 17 +- .../mediapicker/mediapicker.controller.js | 16 +- .../multiurlpicker.controller.js | 3 +- .../relatedlinks/relatedlinks.controller.js | 2 + .../propertyeditors/rte/rte.controller.js | 15 +- .../propertyeditors/rte/rte.prevalues.html | 1 + src/Umbraco.Web/Editors/ContentController.cs | 12 +- src/Umbraco.Web/Editors/EntityController.cs | 106 +- src/Umbraco.Web/Editors/MediaController.cs | 14 +- .../ContentPicker2PropertyEditor.cs | 14 +- .../PropertyEditors/GridPropertyEditor.cs | 3 + .../MediaPicker2PropertyEditor.cs | 9 +- .../MultiNodeTreePicker2PropertyEditor.cs | 10 +- .../MultiUrlPickerPropertyEditor.cs | 7 + .../RelatedLinks2PropertyEditor.cs | 6 +- .../PropertyEditors/RichTextPreValueEditor.cs | 8 + src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 34 +- .../Trees/ContentTreeControllerBase.cs | 12 +- src/Umbraco.Web/Trees/TreeControllerBase.cs | 10 + .../Trees/TreeQueryStringParameters.cs | 3 +- ...EnsureUserPermissionForContentAttribute.cs | 15 +- .../FilterAllowedOutgoingMediaAttribute.cs | 9 +- 35 files changed, 1861 insertions(+), 1556 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js index 4ba4cf96bb..b81e62a66b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js @@ -12,6 +12,7 @@ function treeSearchBox(localizationService, searchService, $q) { searchFromName: "@", showSearch: "@", section: "@", + ignoreUserStartNodes: "@", hideSearchCallback: "=", searchCallback: "=" }, @@ -34,6 +35,7 @@ function treeSearchBox(localizationService, searchService, $q) { scope.showSearch = "false"; } + //used to cancel any request in progress if another one needs to take it's place var canceler = null; @@ -60,6 +62,11 @@ function treeSearchBox(localizationService, searchService, $q) { searchArgs["searchFrom"] = scope.searchFromId; } + //append ignoreUserStartNodes value if there is one + if (scope.ignoreUserStartNodes) { + searchArgs["ignoreUserStartNodes"] = scope.ignoreUserStartNodes; + } + searcher(searchArgs).then(function (data) { scope.searchCallback(data); //set back to null so it can be re-created 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 b695809eaa..77e587bcc6 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 @@ -1,782 +1,795 @@ -/** - * @ngdoc service - * @name umbraco.resources.contentResource - * @description Handles all transactions of content data - * from the angular application to the Umbraco database, using the Content WebApi controller - * - * all methods returns a resource promise async, so all operations won't complete untill .then() is completed. - * - * @requires $q - * @requires $http - * @requires umbDataFormatter - * @requires umbRequestHelper - * - * ##usage - * To use, simply inject the contentResource into any controller or service that needs it, and make - * sure the umbraco.resources module is accesible - which it should be by default. - * - *
-  *    contentResource.getById(1234)
-  *          .then(function(data) {
-  *              $scope.content = data;
-  *          });    
-  * 
- **/ - -function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { - - /** internal method process the saving of data and post processing the result */ - function saveContentItem(content, action, files, restApiUrl) { - return umbRequestHelper.postSaveContent({ - restApiUrl: restApiUrl, - content: content, - action: action, - files: files, - dataFormatter: function (c, a) { - return umbDataFormatter.formatContentPostData(c, a); - } - }); - } - - return { - - - savePermissions: function (saveModel) { - if (!saveModel) { - throw "saveModel cannot be null"; - } - if (!saveModel.contentId) { - throw "saveModel.contentId cannot be null"; - } - if (!saveModel.permissions) { - throw "saveModel.permissions cannot be null"; - } - - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSaveUserGroupPermissions"), - saveModel), - 'Failed to save permissions'); - }, - - - 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"; - } - - 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"; - } - - 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"; - } - - 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"; - } - - 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); - }, - - deleteBlueprint: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "DeleteBlueprint", - [{ id: id }])), - 'Failed to delete blueprint ' + 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); - }, - - getBlueprintById: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetBlueprintById", - [{ id: id }])), - 'Failed to retrieve data for content id ' + id); - }, - - getNotifySettingsById: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetNotificationOptions", - [{ contentId: id }])), - 'Failed to retrieve data for content id ' + id); - }, - - setNotifySettingsById: function (id, options) { - if (!id) { - throw "contentId cannot be null"; - } - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostNotificationOptions", - { contentId: id, notifyOptions: options })), - 'Failed to set notify settings 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); - }, - - getBlueprintScaffold: function (parentId, blueprintId) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetEmpty", - [{ blueprintId: blueprintId }, { parentId: parentId}])), - 'Failed to retrieve blueprint for id ' + blueprintId); - }, - - /** - * @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 = { - includeProperties: [], - 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, - includeProperties: _.pluck(options.includeProperties, 'alias').join(","), - 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); - }, - - getDetailedPermissions: function (contentId) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetDetailedPermissions", { contentId: contentId })), - 'Failed to retrieve permissions for content item ' + contentId); - }, - - 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) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"); - return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); - }, - - saveBlueprint: function (content, isNew, files) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSaveBlueprint"); - return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); - }, - - /** - * @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) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"); - return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint); - }, - - - /** - * @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) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"); - return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint); - }, - - /** - * @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); - - }, - - createBlueprintFromContent: function (contentId, name) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl("contentApiBaseUrl", "CreateBlueprintFromContent", { - contentId: contentId, name: name - }) - ), - "Failed to create blueprint from content with id " + contentId - ); - } - - - }; -} - -angular.module('umbraco.resources').factory('contentResource', contentResource); +/** + * @ngdoc service + * @name umbraco.resources.contentResource + * @description Handles all transactions of content data + * from the angular application to the Umbraco database, using the Content WebApi controller + * + * all methods returns a resource promise async, so all operations won't complete untill .then() is completed. + * + * @requires $q + * @requires $http + * @requires umbDataFormatter + * @requires umbRequestHelper + * + * ##usage + * To use, simply inject the contentResource into any controller or service that needs it, and make + * sure the umbraco.resources module is accesible - which it should be by default. + * + *
+  *    contentResource.getById(1234)
+  *          .then(function(data) {
+  *              $scope.content = data;
+  *          });    
+  * 
+ **/ + +function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { + + /** internal method process the saving of data and post processing the result */ + function saveContentItem(content, action, files, restApiUrl) { + return umbRequestHelper.postSaveContent({ + restApiUrl: restApiUrl, + content: content, + action: action, + files: files, + dataFormatter: function (c, a) { + return umbDataFormatter.formatContentPostData(c, a); + } + }); + } + + return { + + + savePermissions: function (saveModel) { + if (!saveModel) { + throw "saveModel cannot be null"; + } + if (!saveModel.contentId) { + throw "saveModel.contentId cannot be null"; + } + if (!saveModel.permissions) { + throw "saveModel.permissions cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSaveUserGroupPermissions"), + saveModel), + 'Failed to save permissions'); + }, + + + 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"; + } + + 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"; + } + + 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"; + } + + 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"; + } + + 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); + }, + + deleteBlueprint: function (id) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "DeleteBlueprint", + [{ id: id }])), + 'Failed to delete blueprint ' + 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 + * @param {Object} options optional options object + * @param {Bool} options.ignoreUserStartNodes set to true to allow a user to choose nodes that they normally don't have access to + * @returns {Promise} resourcePromise object containing the content item. + * + */ + getById: function (id, options) { + var defaults = { + ignoreUserStartNodes: false + }; + 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; + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetById", + [{ id: id }, { ignoreUserStartNodes: options.ignoreUserStartNodes }])), + 'Failed to retrieve data for content id ' + id); + }, + + getBlueprintById: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetBlueprintById", + [{ id: id }])), + 'Failed to retrieve data for content id ' + id); + }, + + getNotifySettingsById: function (id) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetNotificationOptions", + [{ contentId: id }])), + 'Failed to retrieve data for content id ' + id); + }, + + setNotifySettingsById: function (id, options) { + if (!id) { + throw "contentId cannot be null"; + } + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostNotificationOptions", + { contentId: id, notifyOptions: options })), + 'Failed to set notify settings 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); + }, + + getBlueprintScaffold: function (parentId, blueprintId) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmpty", + [{ blueprintId: blueprintId }, { parentId: parentId}])), + 'Failed to retrieve blueprint for id ' + blueprintId); + }, + + /** + * @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 = { + includeProperties: [], + 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, + includeProperties: _.pluck(options.includeProperties, 'alias').join(","), + 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); + }, + + getDetailedPermissions: function (contentId) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetDetailedPermissions", { contentId: contentId })), + 'Failed to retrieve permissions for content item ' + contentId); + }, + + 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) { + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"); + return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); + }, + + saveBlueprint: function (content, isNew, files) { + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSaveBlueprint"); + return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); + }, + + /** + * @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) { + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"); + return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint); + }, + + + /** + * @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) { + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"); + return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint); + }, + + /** + * @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); + + }, + + createBlueprintFromContent: function (contentId, name) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "CreateBlueprintFromContent", { + contentId: contentId, name: name + }) + ), + "Failed to create blueprint from content with id " + contentId + ); + } + + + }; +} + +angular.module('umbraco.resources').factory('contentResource', contentResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 72f8ad5539..4875491dc6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -292,14 +292,29 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity. * */ - getAncestors: function (id, type) { + getAncestors: function (id, type, options) { + var defaults = { + ignoreUserStartNodes: false + }; + 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; + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetAncestors", - [{id: id}, {type: type}])), - 'Failed to retrieve ancestor data for id ' + id); + [ + { id: id }, + { type: type }, + { ignoreUserStartNodes: options.ignoreUserStartNodes } + ])), + 'Failed to retrieve ancestor data for id ' + id); }, /** @@ -431,7 +446,8 @@ function entityResource($q, $http, umbRequestHelper) { pageNumber: 1, filter: '', orderDirection: "Ascending", - orderBy: "SortOrder" + orderBy: "SortOrder", + ignoreUserStartNodes: false }; if (options === undefined) { options = {}; @@ -460,7 +476,8 @@ function entityResource($q, $http, umbRequestHelper) { pageSize: options.pageSize, orderBy: options.orderBy, orderDirection: options.orderDirection, - filter: encodeURIComponent(options.filter) + filter: encodeURIComponent(options.filter), + ignoreUserStartNodes: options.ignoreUserStartNodes } )), 'Failed to retrieve child data for id ' + parentId); @@ -488,12 +505,19 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity array. * */ - search: function (query, type, searchFrom, canceler) { + search: function (query, type, options, canceler) { - var args = [{ query: query }, { type: type }]; - if (searchFrom) { - args.push({ searchFrom: searchFrom }); + var defaults = { + searchFrom: null, + ignoreUserStartNodes: false + }; + 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; var httpConfig = {}; if (canceler) { @@ -505,7 +529,12 @@ function entityResource($q, $http, umbRequestHelper) { umbRequestHelper.getApiUrl( "entityApiBaseUrl", "Search", - args), + { + query: query, + type: type, + searchFrom: options.searchFrom, + ignoreUserStartNodes: options.ignoreUserStartNodes + }), httpConfig), 'Failed to retrieve entity data for query ' + query); }, 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 8c27f20aea..e968913047 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 @@ -329,7 +329,8 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { filter: '', orderDirection: "Ascending", orderBy: "SortOrder", - orderBySystemField: true + orderBySystemField: true, + ignoreUserStartNodes: false }; if (options === undefined) { options = {}; @@ -372,7 +373,8 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { { orderBy: options.orderBy }, { orderDirection: options.orderDirection }, { orderBySystemField: toBool(options.orderBySystemField) }, - { filter: options.filter } + { filter: options.filter }, + { ignoreUserStartNodes: options.ignoreUserStartNodes } ])), 'Failed to retrieve children for media item ' + parentId); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js index 8738c1011e..0d00678282 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js @@ -42,7 +42,11 @@ angular.module('umbraco.services') throw "args.term is required"; } - return entityResource.search(args.term, "Member", args.searchFrom).then(function (data) { + var options = { + searchFrom: args.searchFrom + } + + return entityResource.search(args.term, "Member", options).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureMemberResult(item); }); @@ -67,7 +71,12 @@ angular.module('umbraco.services') throw "args.term is required"; } - return entityResource.search(args.term, "Document", args.searchFrom, args.canceler).then(function (data) { + var options = { + searchFrom: args.searchFrom, + ignoreUserStartNodes: args.ignoreUserStartNodes + } + + return entityResource.search(args.term, "Document", options, args.canceler).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureContentResult(item); }); @@ -92,7 +101,12 @@ angular.module('umbraco.services') throw "args.term is required"; } - return entityResource.search(args.term, "Media", args.searchFrom).then(function (data) { + var options = { + searchFrom: args.searchFrom, + ignoreUserStartNodes: args.ignoreUserStartNodes + } + + return entityResource.search(args.term, "Media", options).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureMediaResult(item); }); @@ -157,4 +171,4 @@ angular.module('umbraco.services') var currentSection = sectionAlias; } }; - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html index 43eab532d4..4391e50c28 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html @@ -9,6 +9,7 @@ search-from-id="{{searchInfo.searchFromId}}" search-from-name="{{searchInfo.searchFromName}}" show-search="{{searchInfo.showSearch}}" + ignore-user-startnodes="{{searchInfo.ignoreUserStartNodes}}" section="content"> @@ -45,4 +46,4 @@ on-close="closeMiniListView()"> - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js index fcce34621b..79b9362d3f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js @@ -1,57 +1,58 @@ //used for the media picker dialog angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", - function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) { - var dialogOptions = $scope.model; + function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) { + var dialogOptions = $scope.model; - var searchText = "Search..."; - localizationService.localize("general_search").then(function (value) { - searchText = value + "..."; - }); + var searchText = "Search..."; + localizationService.localize("general_search").then(function (value) { + searchText = value + "..."; + }); - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectLink"); - } + if (!$scope.model.title) { + $scope.model.title = localizationService.localize("defaultdialogs_selectLink"); + } - $scope.dialogTreeEventHandler = $({}); - $scope.model.target = {}; - $scope.searchInfo = { - searchFromId: null, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - }; + $scope.dialogTreeEventHandler = $({}); + $scope.model.target = {}; + $scope.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + results: [], + selectedSearchResults: [] + }; + $scope.customTreeParams = dialogOptions.ignoreUserStartNodes ? "ignoreUserStartNodes=" + dialogOptions.ignoreUserStartNodes : ""; + $scope.showTarget = $scope.model.hideTarget !== true; - $scope.showTarget = $scope.model.hideTarget !== true; + if (dialogOptions.currentTarget) { + // clone the current target so we don't accidentally update the caller's model while manipulating $scope.model.target + $scope.model.target = angular.copy(dialogOptions.currentTarget); + //if we have a node ID, we fetch the current node to build the form data + if ($scope.model.target.id || $scope.model.target.udi) { - if (dialogOptions.currentTarget) { - // clone the current target so we don't accidentally update the caller's model while manipulating $scope.model.target - $scope.model.target = angular.copy(dialogOptions.currentTarget); - //if we have a node ID, we fetch the current node to build the form data - if ($scope.model.target.id || $scope.model.target.udi) { + //will be either a udi or an int + var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id; - //will be either a udi or an int - var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id; + // is it a content link? + if (!$scope.model.target.isMedia) { + // get the content path + entityResource.getPath(id, "Document").then(function (path) { + //now sync the tree to this path + $scope.dialogTreeEventHandler.syncTree({ + path: path, + tree: "content" + }); + }); - // is it a content link? - if (!$scope.model.target.isMedia) { - // get the content path - entityResource.getPath(id, "Document").then(function(path) { - //now sync the tree to this path - $scope.dialogTreeEventHandler.syncTree({ - path: path, - tree: "content" - }); - }); - - // get the content properties to build the anchor name list - contentResource.getById(id).then(function (resp) { - $scope.model.target.url = resp.urls[0]; - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - }); - } - } else if ($scope.model.target.url.length) { - // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs + // if a link exists, get the properties to build the anchor name list + contentResource.getById(id, { ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes }).then(function (resp) { + $scope.model.target.url = resp.urls[0]; + $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); + }); + } + } else if ($scope.model.target.url.length) { + // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs // only do the substring if there's a # or a ? var indexOfAnchor = $scope.model.target.url.search(/(#|\?)/); if (indexOfAnchor > -1) { @@ -60,124 +61,132 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", // then rewrite the model and populate the link $scope.model.target.url = $scope.model.target.url.substring(0, indexOfAnchor); } - } - } else if (dialogOptions.anchors) { - $scope.anchorValues = dialogOptions.anchors; - } + } + } else if (dialogOptions.anchors) { + $scope.anchorValues = dialogOptions.anchors; + } - function nodeSelectHandler(ev, args) { - if (args && args.event) { - args.event.preventDefault(); - args.event.stopPropagation(); - } + function nodeSelectHandler(ev, args) { + if (args && args.event) { + args.event.preventDefault(); + args.event.stopPropagation(); + } - eventsService.emit("dialogs.linkPicker.select", args); + eventsService.emit("dialogs.linkPicker.select", args); - if ($scope.currentNode) { - //un-select if there's a current one selected - $scope.currentNode.selected = false; - } + if ($scope.currentNode) { + //un-select if there's a current one selected + $scope.currentNode.selected = false; + } - $scope.currentNode = args.node; - $scope.currentNode.selected = true; - $scope.model.target.id = args.node.id; - $scope.model.target.udi = args.node.udi; - $scope.model.target.name = args.node.name; + $scope.currentNode = args.node; + $scope.currentNode.selected = true; + $scope.model.target.id = args.node.id; + $scope.model.target.udi = args.node.udi; + $scope.model.target.name = args.node.name; - if (args.node.id < 0) { - $scope.model.target.url = "/"; - } else { - contentResource.getById(args.node.id).then(function (resp) { - $scope.model.target.url = resp.urls[0]; - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - }); - } + if (args.node.id < 0) { + $scope.model.target.url = "/"; + } else { + contentResource.getById(args.node.id, { ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes }).then(function (resp) { + $scope.model.target.url = resp.urls[0]; + $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); + }); + } - if (!angular.isUndefined($scope.model.target.isMedia)) { - delete $scope.model.target.isMedia; - } - } + if (!angular.isUndefined($scope.model.target.isMedia)) { + delete $scope.model.target.isMedia; + } + } - function nodeExpandedHandler(ev, args) { - // open mini list view for list views - if (args.node.metaData.isContainer) { - openMiniListView(args.node); - } - } + function nodeExpandedHandler(ev, args) { + // open mini list view for list views + if (args.node.metaData.isContainer) { + openMiniListView(args.node); + } + } - $scope.switchToMediaPicker = function () { - userService.getCurrentUser().then(function (userData) { - $scope.mediaPickerOverlay = { - view: "mediapicker", - startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], - startNodeIsVirtual: userData.startMediaIds.length !== 1, - show: true, - submit: function (model) { - var media = model.selectedImages[0]; + $scope.switchToMediaPicker = function () { + userService.getCurrentUser().then(function (userData) { + var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + var startNodeIsVirtual = userData.startMediaIds.length !== 1; - $scope.model.target.id = media.id; - $scope.model.target.udi = media.udi; - $scope.model.target.isMedia = true; - $scope.model.target.name = media.name; - $scope.model.target.url = mediaHelper.resolveFile(media); + if (dialogOptions.ignoreUserStartNodes) { + startNodeId = -1; + startNodeIsVirtual = true; + } + $scope.mediaPickerOverlay = { + view: "mediapicker", + startNodeId: startNodeId, + startNodeIsVirtual: startNodeIsVirtual, + show: true, + ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + submit: function (model) { + var media = model.selectedImages[0]; - $scope.mediaPickerOverlay.show = false; - $scope.mediaPickerOverlay = null; + $scope.model.target.id = media.id; + $scope.model.target.udi = media.udi; + $scope.model.target.isMedia = true; + $scope.model.target.name = media.name; + $scope.model.target.url = mediaHelper.resolveFile(media); - // make sure the content tree has nothing highlighted - $scope.dialogTreeEventHandler.syncTree({ - path: "-1", - tree: "content" - }); - } - }; - }); - }; + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; - $scope.hideSearch = function () { - $scope.searchInfo.showSearch = false; - $scope.searchInfo.searchFromId = null; - $scope.searchInfo.searchFromName = null; - $scope.searchInfo.results = []; - } + // make sure the content tree has nothing highlighted + $scope.dialogTreeEventHandler.syncTree({ + path: "-1", + tree: "content" + }); + } + }; + }); + }; - // method to select a search result - $scope.selectResult = function (evt, result) { - result.selected = result.selected === true ? false : true; - nodeSelectHandler(evt, { - event: evt, - node: result - }); - }; + $scope.hideSearch = function () { + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = null; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + } - //callback when there are search results - $scope.onSearchResults = function (results) { - $scope.searchInfo.results = results; - $scope.searchInfo.showSearch = true; - }; + // method to select a search result + $scope.selectResult = function (evt, result) { + result.selected = result.selected === true ? false : true; + nodeSelectHandler(evt, { + event: evt, + node: result + }); + }; - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + //callback when there are search results + $scope.onSearchResults = function (results) { + $scope.searchInfo.results = results; + $scope.searchInfo.showSearch = true; + }; - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - }); + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - // Mini list view - $scope.selectListViewNode = function (node) { - node.selected = node.selected === true ? false : true; - nodeSelectHandler({}, { - node: node - }); - }; + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + }); - $scope.closeMiniListView = function () { - $scope.miniListView = undefined; - }; + // Mini list view + $scope.selectListViewNode = function (node) { + node.selected = node.selected === true ? false : true; + nodeSelectHandler({}, { + node: node + }); + }; - function openMiniListView(node) { - $scope.miniListView = node; - } + $scope.closeMiniListView = function () { + $scope.miniListView = undefined; + }; - }); + function openMiniListView(node) { + $scope.miniListView = node; + } + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html index deba19aa11..c9f3ad54a7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html @@ -50,6 +50,7 @@ search-from-id="{{searchInfo.searchFromId}}" search-from-name="{{searchInfo.searchFromName}}" show-search="{{searchInfo.showSearch}}" + ignore-user-startnodes="{{searchInfo.ignoreUserStartNodes}}" section="{{section}}"> @@ -64,6 +65,7 @@ 0) { - entityResource.getAncestors(folder.id, "media") - .then(function(anc) { + entityResource.getAncestors(folder.id, "media", { ignoreUserStartNodes: $scope.model.ignoreUserStartNodes }) + .then(function(anc) { $scope.path = _.filter(anc, function(f) { return f.path.indexOf($scope.startNodeId) !== -1; @@ -161,13 +168,13 @@ angular.module("umbraco") } else { $scope.path = []; } - + mediaTypeHelper.getAllowedImagetypes(folder.id) .then(function (types) { $scope.acceptedMediatypes = types; }); - - $scope.lockedFolder = folder.id === -1 && $scope.model.startNodeIsVirtual; + + $scope.lockedFolder = (folder.id === -1 && $scope.model.startNodeIsVirtual) || hasFolderAccess(folder) === false; $scope.currentFolder = folder; localStorageService.set("umbLastOpenedMediaNodeId", folder.id); @@ -263,6 +270,17 @@ angular.module("umbraco") } } + function hasFolderAccess(node) { + var nodePath = node.path ? node.path.split(',') : [node.id]; + + for (var i = 0; i < nodePath.length; i++) { + if (userStartNodes.indexOf(parseInt(nodePath[i])) !== -1) + return true; + } + + return false; + } + function gotoStartNode(err) { $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); } @@ -297,7 +315,8 @@ angular.module("umbraco") pageSize: 100, totalItems: 0, totalPages: 0, - filter: '' + filter: '', + ignoreUserStartNodes: $scope.model.ignoreUserStartNodes }; getChildren($scope.currentFolder.id); } @@ -369,7 +388,7 @@ angular.module("umbraco") function getChildren(id) { $scope.loading = true; - return mediaResource.getChildren(id) + return mediaResource.getChildren(id, { ignoreUserStartNodes: $scope.model.ignoreUserStartNodes }) .then(function(data) { $scope.searchOptions.filter = ""; $scope.images = data.items ? data.items : []; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js index 615f86b384..e1ce332b48 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -1,517 +1,530 @@ -//used for the media picker dialog -angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", - function ($scope, $q, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService, contentResource, mediaResource, memberResource) { - - var tree = null; - var dialogOptions = $scope.model; - $scope.treeReady = false; - $scope.dialogTreeEventHandler = $({}); - $scope.section = dialogOptions.section; - $scope.treeAlias = dialogOptions.treeAlias; - $scope.multiPicker = dialogOptions.multiPicker; - $scope.hideHeader = (typeof dialogOptions.hideHeader) === "boolean" ? dialogOptions.hideHeader : true; - // if you need to load a not initialized tree set this value to false - default is true - $scope.onlyInitialized = dialogOptions.onlyInitialized; - $scope.searchInfo = { - searchFromId: dialogOptions.startNodeId, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - } - - $scope.model.selection = []; - - //Used for toggling an empty-state message - //Some trees can have no items (dictionary & forms email templates) - $scope.hasItems = true; - $scope.emptyStateMessage = dialogOptions.emptyStateMessage; - var node = dialogOptions.currentNode; - - //This is called from ng-init - //it turns out it is called from the angular html : / Have a look at views/common / overlays / contentpicker / contentpicker.html you'll see ng-init. - //this is probably an anti pattern IMO and shouldn't be used - $scope.init = function (contentType) { - - if (contentType === "content") { - $scope.entityType = "Document"; - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectContent"); - } - } else if (contentType === "member") { - $scope.entityType = "Member"; - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectMember"); - } - } else if (contentType === "media") { - $scope.entityType = "Media"; - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectMedia"); - } - } - } - - var searchText = "Search..."; - localizationService.localize("general_search").then(function (value) { - searchText = value + "..."; - }); - - // Allow the entity type to be passed in but defaults to Document for backwards compatibility. - $scope.entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document"; - - - //min / max values - if (dialogOptions.minNumber) { - dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10); - } - if (dialogOptions.maxNumber) { - dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10); - } - - if (dialogOptions.section === "member") { - $scope.entityType = "Member"; - } - else if (dialogOptions.section === "media") { - $scope.entityType = "Media"; - } - - // Search and listviews is only working for content, media and member section - var searchableSections = ["content", "media", "member"]; - - $scope.enableSearh = searchableSections.indexOf($scope.section) !== -1; - - //if a alternative startnode is used, we need to check if it is a container - if ($scope.enableSearh && dialogOptions.startNodeId && dialogOptions.startNodeId !== -1 && dialogOptions.startNodeId !== "-1") { - entityResource.getById(dialogOptions.startNodeId, $scope.entityType).then(function(node) { - if (node.metaData.IsContainer) { - openMiniListView(node); - } - initTree(); - }); - } - else { - initTree(); - } - - //Configures filtering - if (dialogOptions.filter) { - - dialogOptions.filterExclude = false; - dialogOptions.filterAdvanced = false; - - //used advanced filtering - if (angular.isFunction(dialogOptions.filter)) { - dialogOptions.filterAdvanced = true; - } - else if (angular.isObject(dialogOptions.filter)) { - dialogOptions.filterAdvanced = true; - } - else { - if (dialogOptions.filter.startsWith("!")) { - dialogOptions.filterExclude = true; - dialogOptions.filterTypes = dialogOptions.filter.substring(1); - } else { - dialogOptions.filterExclude = false; - dialogOptions.filterTypes = dialogOptions.filter; - } - - //used advanced filtering - if (dialogOptions.filter.startsWith("{")) { - dialogOptions.filterAdvanced = true; - //convert to object - dialogOptions.filter = angular.fromJson(dialogOptions.filter); - } - } - - $scope.filter = { - filterAdvanced: dialogOptions.filterAdvanced, - filterExclude: dialogOptions.filterExclude, - filter: dialogOptions.filterTypes - }; - } - - function initTree() { - //create the custom query string param for this tree - $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : ""; - $scope.customTreeParams += dialogOptions.customTreeParams ? "&" + dialogOptions.customTreeParams : ""; - $scope.treeReady = true; - } - - function nodeExpandedHandler(ev, args) { - - // open mini list view for list views - if (args.node.metaData.isContainer) { - openMiniListView(args.node); - } - - if (angular.isArray(args.children)) { - - //iterate children - _.each(args.children, function (child) { - - //now we need to look in the already selected search results and - // toggle the check boxes for those ones that are listed - var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { - return child.id == selected.id; - }); - if (exists) { - child.selected = true; - } - }); - - //check filter - performFiltering(args.children); - } - } - - //gets the tree object when it loads - function treeLoadedHandler(ev, args) { - //args.tree contains children (args.tree.root.children) - $scope.hasItems = args.tree.root.children.length > 0; - - tree = args.tree; - - var nodeHasPath = typeof node !== "undefined" && typeof node.path !== "undefined"; - var startNodeNotDefined = typeof dialogOptions.startNodeId === "undefined" || dialogOptions.startNodeId === "" || dialogOptions.startNodeId === "-1"; - if (startNodeNotDefined && nodeHasPath) { - $scope.dialogTreeEventHandler.syncTree({ path: node.path, activate: false }); - } - } - - //wires up selection - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if (args.node.metaData.isSearchResult) { - //check if the item selected was a search result from a list view - - //unselect - select(args.node.name, args.node.id); - - //remove it from the list view children - var listView = args.node.parent(); - listView.children = _.reject(listView.children, function (child) { - return child.id == args.node.id; - }); - - //remove it from the custom tracked search result list - $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { - return i.id == args.node.id; - }); - } - else { - eventsService.emit("dialogs.treePickerController.select", args); - - if (args.node.filtered) { - return; - } - - //This is a tree node, so we don't have an entity to pass in, it will need to be looked up - //from the server in this method. - if ($scope.model.select) { - $scope.model.select(args.node) - } else { - select(args.node.name, args.node.id); - //toggle checked state - args.node.selected = args.node.selected === true ? false : true; - } - - } - } - - /** Method used for selecting a node */ - function select(text, id, entity) { - //if we get the root, we just return a constructed entity, no need for server data - if (id < 0) { - - var rootNode = { - alias: null, - icon: "icon-folder", - id: id, - name: text - }; - - if ($scope.multiPicker) { - if (entity) { - multiSelectItem(entity); - } else { - multiSelectItem(rootNode); - } - } - else { - $scope.model.selection.push(rootNode); - $scope.model.submit($scope.model); - } - } - else { - - if ($scope.multiPicker) { - - if (entity) { - multiSelectItem(entity); - } else { - //otherwise we have to get it from the server - entityResource.getById(id, $scope.entityType).then(function (ent) { - multiSelectItem(ent); - }); - } - - } - - else { - - $scope.hideSearch(); - - //if an entity has been passed in, use it - if (entity) { - $scope.model.selection.push(entity); - $scope.model.submit($scope.model); - } else { - //otherwise we have to get it from the server - entityResource.getById(id, $scope.entityType).then(function (ent) { - $scope.model.selection.push(ent); - $scope.model.submit($scope.model); - }); - } - } - } - } - - function multiSelectItem(item) { - - var found = false; - var foundIndex = 0; - - if ($scope.model.selection.length > 0) { - for (i = 0; $scope.model.selection.length > i; i++) { - var selectedItem = $scope.model.selection[i]; - if (selectedItem.id === item.id) { - found = true; - foundIndex = i; - } - } - } - - if (found) { - $scope.model.selection.splice(foundIndex, 1); - } else { - $scope.model.selection.push(item); - } - - } - - function performFiltering(nodes) { - - if (!dialogOptions.filter) { - return; - } - - //remove any list view search nodes from being filtered since these are special nodes that always must - // be allowed to be clicked on - nodes = _.filter(nodes, function (n) { - return !angular.isObject(n.metaData.listViewNode); - }); - - if (dialogOptions.filterAdvanced) { - - //filter either based on a method or an object - var filtered = angular.isFunction(dialogOptions.filter) - ? _.filter(nodes, dialogOptions.filter) - : _.where(nodes, dialogOptions.filter); - - angular.forEach(filtered, function (value, key) { - value.filtered = true; - if (dialogOptions.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push(dialogOptions.filterCssClass); - } - }); - } else { - var a = dialogOptions.filterTypes.toLowerCase().replace(/\s/g, '').split(','); - angular.forEach(nodes, function (value, key) { - - var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; - - if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { - value.filtered = true; - - if (dialogOptions.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push(dialogOptions.filterCssClass); - } - } - }); - } - } - - $scope.multiSubmit = function (result) { - entityResource.getByIds(result, $scope.entityType).then(function (ents) { - $scope.submit(ents); - }); - }; - - /** method to select a search result */ - $scope.selectResult = function (evt, result) { - - if (result.filtered) { - return; - } - - result.selected = result.selected === true ? false : true; - - //since result = an entity, we'll pass it in so we don't have to go back to the server - select(result.name, result.id, result); - - //add/remove to our custom tracked list of selected search results - if (result.selected) { - $scope.searchInfo.selectedSearchResults.push(result); - } - else { - $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { - return i.id == result.id; - }); - } - - //ensure the tree node in the tree is checked/unchecked if it already exists there - if (tree) { - var found = treeService.getDescendantNode(tree.root, result.id); - if (found) { - found.selected = result.selected; - } - } - - }; - - $scope.hideSearch = function () { - - //Traverse the entire displayed tree and update each node to sync with the selected search results - if (tree) { - - //we need to ensure that any currently displayed nodes that get selected - // from the search get updated to have a check box! - function checkChildren(children) { - _.each(children, function (child) { - //check if the id is in the selection, if so ensure it's flagged as selected - var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { - return child.id == selected.id; - }); - //if the curr node exists in selected search results, ensure it's checked - if (exists) { - child.selected = true; - } - //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result - else if (child.metaData.isSearchResult) { - //if this tree node is under a list view it means that the node was added - // to the tree dynamically under the list view that was searched, so we actually want to remove - // it all together from the tree - var listView = child.parent(); - listView.children = _.reject(listView.children, function (c) { - return c.id == child.id; - }); - } - - //check if the current node is a list view and if so, check if there's any new results - // that need to be added as child nodes to it based on search results selected - if (child.metaData.isContainer) { - - child.cssClasses = _.reject(child.cssClasses, function (c) { - return c === 'tree-node-slide-up-hide-active'; - }); - - var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) { - return i.parentId == child.id; - }); - _.each(listViewResults, function (item) { - var childExists = _.find(child.children, function (c) { - return c.id == item.id; - }); - if (!childExists) { - var parent = child; - child.children.unshift({ - id: item.id, - name: item.name, - cssClass: "icon umb-tree-icon sprTree " + item.icon, - level: child.level + 1, - metaData: { - isSearchResult: true - }, - hasChildren: false, - parent: function () { - return parent; - } - }); - } - }); - } - - //recurse - if (child.children && child.children.length > 0) { - checkChildren(child.children); - } - }); - } - checkChildren(tree.root.children); - } - - - $scope.searchInfo.showSearch = false; - $scope.searchInfo.searchFromId = dialogOptions.startNodeId; - $scope.searchInfo.searchFromName = null; - $scope.searchInfo.results = []; - } - - $scope.onSearchResults = function (results) { - - //filter all items - this will mark an item as filtered - performFiltering(results); - - //now actually remove all filtered items so they are not even displayed - results = _.filter(results, function (item) { - return !item.filtered; - }); - - $scope.searchInfo.results = results; - - //sync with the curr selected results - _.each($scope.searchInfo.results, function (result) { - var exists = _.find($scope.model.selection, function (selectedId) { - return result.id == selectedId; - }); - if (exists) { - result.selected = true; - } - }); - - $scope.searchInfo.showSearch = true; - }; - - $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - }); - - $scope.selectListViewNode = function (node) { - select(node.name, node.id); - //toggle checked state - node.selected = node.selected === true ? false : true; - }; - - $scope.closeMiniListView = function () { - $scope.miniListView = undefined; - }; - - function openMiniListView(node) { - $scope.miniListView = node; - } - - }); +//used for the media picker dialog +angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", + function ($scope, $q, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService, contentResource, mediaResource, memberResource) { + + var tree = null; + var dialogOptions = $scope.model; + $scope.treeReady = false; + $scope.dialogTreeEventHandler = $({}); + $scope.section = dialogOptions.section; + $scope.treeAlias = dialogOptions.treeAlias; + $scope.multiPicker = dialogOptions.multiPicker; + $scope.hideHeader = (typeof dialogOptions.hideHeader) === "boolean" ? dialogOptions.hideHeader : true; + // if you need to load a not initialized tree set this value to false - default is true + $scope.onlyInitialized = dialogOptions.onlyInitialized; + $scope.searchInfo = { + searchFromId: dialogOptions.startNodeId, + searchFromName: null, + showSearch: false, + ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + results: [], + selectedSearchResults: [] + } + + $scope.model.selection = []; + + //Used for toggling an empty-state message + //Some trees can have no items (dictionary & forms email templates) + $scope.hasItems = true; + $scope.emptyStateMessage = dialogOptions.emptyStateMessage; + var node = dialogOptions.currentNode; + + //This is called from ng-init + //it turns out it is called from the angular html : / Have a look at views/common / overlays / contentpicker / contentpicker.html you'll see ng-init. + //this is probably an anti pattern IMO and shouldn't be used + $scope.init = function (contentType) { + + if (contentType === "content") { + $scope.entityType = "Document"; + if (!$scope.model.title) { + $scope.model.title = localizationService.localize("defaultdialogs_selectContent"); + } + } else if (contentType === "member") { + $scope.entityType = "Member"; + if (!$scope.model.title) { + $scope.model.title = localizationService.localize("defaultdialogs_selectMember"); + } + } else if (contentType === "media") { + $scope.entityType = "Media"; + if (!$scope.model.title) { + $scope.model.title = localizationService.localize("defaultdialogs_selectMedia"); + } + } + } + + var searchText = "Search..."; + localizationService.localize("general_search").then(function (value) { + searchText = value + "..."; + }); + + // Allow the entity type to be passed in but defaults to Document for backwards compatibility. + $scope.entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document"; + + + //min / max values + if (dialogOptions.minNumber) { + dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10); + } + if (dialogOptions.maxNumber) { + dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10); + } + + if (dialogOptions.section === "member") { + $scope.entityType = "Member"; + } + else if (dialogOptions.section === "media") { + $scope.entityType = "Media"; + } + + // Search and listviews is only working for content, media and member section + var searchableSections = ["content", "media", "member"]; + + $scope.enableSearh = searchableSections.indexOf($scope.section) !== -1; + + //if a alternative startnode is used, we need to check if it is a container + if ($scope.enableSearh && dialogOptions.startNodeId && dialogOptions.startNodeId !== -1 && dialogOptions.startNodeId !== "-1") { + entityResource.getById(dialogOptions.startNodeId, $scope.entityType).then(function(node) { + if (node.metaData.IsContainer) { + openMiniListView(node); + } + initTree(); + }); + } + else { + initTree(); + } + + //Configures filtering + if (dialogOptions.filter) { + + dialogOptions.filterExclude = false; + dialogOptions.filterAdvanced = false; + + //used advanced filtering + if (angular.isFunction(dialogOptions.filter)) { + dialogOptions.filterAdvanced = true; + } + else if (angular.isObject(dialogOptions.filter)) { + dialogOptions.filterAdvanced = true; + } + else { + if (dialogOptions.filter.startsWith("!")) { + dialogOptions.filterExclude = true; + dialogOptions.filterTypes = dialogOptions.filter.substring(1); + } else { + dialogOptions.filterExclude = false; + dialogOptions.filterTypes = dialogOptions.filter; + } + + //used advanced filtering + if (dialogOptions.filter.startsWith("{")) { + dialogOptions.filterAdvanced = true; + //convert to object + dialogOptions.filter = angular.fromJson(dialogOptions.filter); + } + } + + $scope.filter = { + filterAdvanced: dialogOptions.filterAdvanced, + filterExclude: dialogOptions.filterExclude, + filter: dialogOptions.filterTypes + }; + } + + function initTree() { + //create the custom query string param for this tree + var params = []; + + if (dialogOptions.startNodeId) + params.push("startNodeId=" + dialogOptions.startNodeId); + + if (dialogOptions.ignoreUserStartNodes) + params.push("ignoreUserStartNodes=" + dialogOptions.ignoreUserStartNodes); + + if (dialogOptions.customTreeParams) + params.push(dialogOptions.customTreeParams); + + $scope.customTreeParams = params.join('&'); + + $scope.treeReady = true; + } + + function nodeExpandedHandler(ev, args) { + + // open mini list view for list views + if (args.node.metaData.isContainer) { + openMiniListView(args.node); + } + + if (angular.isArray(args.children)) { + + //iterate children + _.each(args.children, function (child) { + + //now we need to look in the already selected search results and + // toggle the check boxes for those ones that are listed + var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { + return child.id == selected.id; + }); + if (exists) { + child.selected = true; + } + }); + + //check filter + performFiltering(args.children); + } + } + + //gets the tree object when it loads + function treeLoadedHandler(ev, args) { + //args.tree contains children (args.tree.root.children) + $scope.hasItems = args.tree.root.children.length > 0; + + tree = args.tree; + + var nodeHasPath = typeof node !== "undefined" && typeof node.path !== "undefined"; + var startNodeNotDefined = typeof dialogOptions.startNodeId === "undefined" || dialogOptions.startNodeId === "" || dialogOptions.startNodeId === "-1"; + if (startNodeNotDefined && nodeHasPath) { + $scope.dialogTreeEventHandler.syncTree({ path: node.path, activate: false }); + } + + } + + //wires up selection + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if (args.node.metaData.isSearchResult) { + //check if the item selected was a search result from a list view + + //unselect + select(args.node.name, args.node.id); + + //remove it from the list view children + var listView = args.node.parent(); + listView.children = _.reject(listView.children, function (child) { + return child.id == args.node.id; + }); + + //remove it from the custom tracked search result list + $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { + return i.id == args.node.id; + }); + } + else { + eventsService.emit("dialogs.treePickerController.select", args); + + if (args.node.filtered) { + return; + } + + //This is a tree node, so we don't have an entity to pass in, it will need to be looked up + //from the server in this method. + if ($scope.model.select) { + $scope.model.select(args.node) + } else { + select(args.node.name, args.node.id); + //toggle checked state + args.node.selected = args.node.selected === true ? false : true; + } + + } + } + + /** Method used for selecting a node */ + function select(text, id, entity) { + //if we get the root, we just return a constructed entity, no need for server data + if (id < 0) { + + var rootNode = { + alias: null, + icon: "icon-folder", + id: id, + name: text + }; + + if ($scope.multiPicker) { + if (entity) { + multiSelectItem(entity); + } else { + multiSelectItem(rootNode); + } + } + else { + $scope.model.selection.push(rootNode); + $scope.model.submit($scope.model); + } + } + else { + + if ($scope.multiPicker) { + + if (entity) { + multiSelectItem(entity); + } else { + //otherwise we have to get it from the server + entityResource.getById(id, $scope.entityType).then(function (ent) { + multiSelectItem(ent); + }); + } + + } + + else { + + $scope.hideSearch(); + + //if an entity has been passed in, use it + if (entity) { + $scope.model.selection.push(entity); + $scope.model.submit($scope.model); + } else { + //otherwise we have to get it from the server + entityResource.getById(id, $scope.entityType).then(function (ent) { + $scope.model.selection.push(ent); + $scope.model.submit($scope.model); + }); + } + } + } + } + + function multiSelectItem(item) { + + var found = false; + var foundIndex = 0; + + if ($scope.model.selection.length > 0) { + for (i = 0; $scope.model.selection.length > i; i++) { + var selectedItem = $scope.model.selection[i]; + if (selectedItem.id === item.id) { + found = true; + foundIndex = i; + } + } + } + + if (found) { + $scope.model.selection.splice(foundIndex, 1); + } else { + $scope.model.selection.push(item); + } + + } + + function performFiltering(nodes) { + + if (!dialogOptions.filter) { + return; + } + + //remove any list view search nodes from being filtered since these are special nodes that always must + // be allowed to be clicked on + nodes = _.filter(nodes, function (n) { + return !angular.isObject(n.metaData.listViewNode); + }); + + if (dialogOptions.filterAdvanced) { + + //filter either based on a method or an object + var filtered = angular.isFunction(dialogOptions.filter) + ? _.filter(nodes, dialogOptions.filter) + : _.where(nodes, dialogOptions.filter); + + angular.forEach(filtered, function (value, key) { + value.filtered = true; + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + }); + } else { + var a = dialogOptions.filterTypes.toLowerCase().replace(/\s/g, '').split(','); + angular.forEach(nodes, function (value, key) { + + var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; + + if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { + value.filtered = true; + + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + } + }); + } + } + + $scope.multiSubmit = function (result) { + entityResource.getByIds(result, $scope.entityType).then(function (ents) { + $scope.submit(ents); + }); + }; + + /** method to select a search result */ + $scope.selectResult = function (evt, result) { + + if (result.filtered) { + return; + } + + result.selected = result.selected === true ? false : true; + + //since result = an entity, we'll pass it in so we don't have to go back to the server + select(result.name, result.id, result); + + //add/remove to our custom tracked list of selected search results + if (result.selected) { + $scope.searchInfo.selectedSearchResults.push(result); + } + else { + $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { + return i.id == result.id; + }); + } + + //ensure the tree node in the tree is checked/unchecked if it already exists there + if (tree) { + var found = treeService.getDescendantNode(tree.root, result.id); + if (found) { + found.selected = result.selected; + } + } + + }; + + $scope.hideSearch = function () { + + //Traverse the entire displayed tree and update each node to sync with the selected search results + if (tree) { + + //we need to ensure that any currently displayed nodes that get selected + // from the search get updated to have a check box! + function checkChildren(children) { + _.each(children, function (child) { + //check if the id is in the selection, if so ensure it's flagged as selected + var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { + return child.id == selected.id; + }); + //if the curr node exists in selected search results, ensure it's checked + if (exists) { + child.selected = true; + } + //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result + else if (child.metaData.isSearchResult) { + //if this tree node is under a list view it means that the node was added + // to the tree dynamically under the list view that was searched, so we actually want to remove + // it all together from the tree + var listView = child.parent(); + listView.children = _.reject(listView.children, function (c) { + return c.id == child.id; + }); + } + + //check if the current node is a list view and if so, check if there's any new results + // that need to be added as child nodes to it based on search results selected + if (child.metaData.isContainer) { + + child.cssClasses = _.reject(child.cssClasses, function (c) { + return c === 'tree-node-slide-up-hide-active'; + }); + + var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) { + return i.parentId == child.id; + }); + _.each(listViewResults, function (item) { + var childExists = _.find(child.children, function (c) { + return c.id == item.id; + }); + if (!childExists) { + var parent = child; + child.children.unshift({ + id: item.id, + name: item.name, + cssClass: "icon umb-tree-icon sprTree " + item.icon, + level: child.level + 1, + metaData: { + isSearchResult: true + }, + hasChildren: false, + parent: function () { + return parent; + } + }); + } + }); + } + + //recurse + if (child.children && child.children.length > 0) { + checkChildren(child.children); + } + }); + } + checkChildren(tree.root.children); + } + + + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = dialogOptions.startNodeId; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + } + + $scope.onSearchResults = function (results) { + + //filter all items - this will mark an item as filtered + performFiltering(results); + + //now actually remove all filtered items so they are not even displayed + results = _.filter(results, function (item) { + return !item.filtered; + }); + + $scope.searchInfo.results = results; + + //sync with the curr selected results + _.each($scope.searchInfo.results, function (result) { + var exists = _.find($scope.model.selection, function (selectedId) { + return result.id == selectedId; + }); + if (exists) { + result.selected = true; + } + }); + + $scope.searchInfo.showSearch = true; + }; + + $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + }); + + $scope.selectListViewNode = function (node) { + select(node.name, node.id); + //toggle checked state + node.selected = node.selected === true ? false : true; + }; + + $scope.closeMiniListView = function () { + $scope.miniListView = undefined; + }; + + function openMiniListView(node) { + $scope.miniListView = node; + } + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html index a3a5fd6107..c338e3402c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html @@ -10,6 +10,7 @@ search-from-id="{{searchInfo.searchFromId}}" search-from-name="{{searchInfo.searchFromName}}" show-search="{{searchInfo.showSearch}}" + ignore-user-startnodes="{{searchInfo.ignoreUserStartNodes}}" section="{{section}}"> diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index ee8c193b4b..ce836a8d68 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -66,6 +66,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper showOpenButton: false, showEditButton: false, showPathOnHover: false, + ignoreUserStartNodes: false, maxNumber: 1, minNumber : 0, startNode: { @@ -99,7 +100,8 @@ function contentPickerController($scope, entityResource, editorState, iconHelper $scope.model.config.showOpenButton = ($scope.model.config.showOpenButton === "1" ? true : false); $scope.model.config.showEditButton = ($scope.model.config.showEditButton === "1" ? true : false); $scope.model.config.showPathOnHover = ($scope.model.config.showPathOnHover === "1" ? true : false); - + $scope.model.config.ignoreUserStartNodes = ($scope.model.config.ignoreUserStartNodes === "1" ? true : false); + var entityType = $scope.model.config.startNode.type === "member" ? "Member" : $scope.model.config.startNode.type === "media" diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index 21f9534848..a093ccb034 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -1,12 +1,20 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.Grid.MediaController", function ($scope, $rootScope, $timeout, userService) { + + var ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false; if (!$scope.model.config.startNodeId) { - userService.getCurrentUser().then(function (userData) { - $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; - }); + if (ignoreUserStartNodes === true) { + $scope.model.config.startNodeId = -1; + $scope.model.config.startNodeIsVirtual = true; + + } else { + userService.getCurrentUser().then(function (userData) { + $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; + }); + } } $scope.setImage = function(){ @@ -14,6 +22,7 @@ angular.module("umbraco") $scope.mediaPickerOverlay.view = "mediapicker"; $scope.mediaPickerOverlay.startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined; $scope.mediaPickerOverlay.startNodeIsVirtual = $scope.mediaPickerOverlay.startNodeId ? $scope.model.config.startNodeIsVirtual : undefined; + $scope.mediaPickerOverlay.ignoreUserStartNodes = ignoreUserStartNodes; $scope.mediaPickerOverlay.cropSize = $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined; $scope.mediaPickerOverlay.showDetails = true; $scope.mediaPickerOverlay.disableFolderSelect = true; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js index 397438d5a0..cf81300b32 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js @@ -16,6 +16,7 @@ view: "linkpicker", currentTarget: currentTarget, anchors: tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)), + ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1", show: true, submit: function(model) { tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); @@ -26,11 +27,23 @@ } function openMediaPicker(editor, currentTarget, userData) { + var ignoreUserStartNodes = false; + var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + var startNodeIsVirtual = userData.startMediaIds.length !== 1; + + if ($scope.model.config.ignoreUserStartNodes === "1") { + ignoreUserStartNodes = true; + startNodeId = -1; + startNodeIsVirtual = true; + } + vm.mediaPickerOverlay = { currentTarget: currentTarget, onlyImages: true, - showDetails: true, - startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], + showDetails: true, + startNodeId: startNodeId, + startNodeIsVirtual: startNodeIsVirtual, + ignoreUserStartNodes: ignoreUserStartNodes, view: "mediapicker", show: true, submit: function(model) { 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 7b8445a4dc..1f1305b0f0 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 @@ -7,12 +7,19 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false; var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false; var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false; + var ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false; if (!$scope.model.config.startNodeId) { - userService.getCurrentUser().then(function(userData) { - $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; - }); + if (ignoreUserStartNodes === true) { + $scope.model.config.startNodeId = -1; + $scope.model.config.startNodeIsVirtual = true; + + } else { + userService.getCurrentUser().then(function (userData) { + $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; + }); + } } function setupViewModel() { @@ -105,6 +112,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl title: "Select media", startNodeId: $scope.model.config.startNodeId, startNodeIsVirtual: $scope.model.config.startNodeIsVirtual, + ignoreUserStartNodes: ignoreUserStartNodes, multiPicker: multiPicker, onlyImages: onlyImages, disableFolderSelect: disableFolderSelect, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js index e4f2ae303f..c7e67a0a42 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js @@ -67,10 +67,11 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en url: link.url, target: link.target } : null; - + $scope.linkPickerOverlay = { view: "linkpicker", currentTarget: target, + ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1", show: true, submit: function (model) { if (model.target.url || model.target.anchor) { 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 142298e0d8..6047169c54 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 @@ -25,6 +25,7 @@ $scope.contentPickerOverlay.view = "contentpicker"; $scope.contentPickerOverlay.multiPicker = false; $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true: false; $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; $scope.contentPickerOverlay.submit = function(model) { @@ -50,6 +51,7 @@ $scope.contentPickerOverlay.view = "contentpicker"; $scope.contentPickerOverlay.multiPicker = false; $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false; $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; $scope.contentPickerOverlay.submit = function(model) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index 1fc648083e..f8dea2ee8b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -274,6 +274,7 @@ angular.module("umbraco") view: "linkpicker", currentTarget: currentTarget, anchors: editorState.current ? tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)) : [], + ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1", show: true, submit: function(model) { tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); @@ -285,14 +286,24 @@ angular.module("umbraco") //Create the insert media plugin tinyMceService.createMediaPicker(editor, $scope, function(currentTarget, userData){ + var ignoreUserStartNodes = false; + var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + var startNodeIsVirtual = userData.startMediaIds.length !== 1; + + if ($scope.model.config.ignoreUserStartNodes === "1") { + ignoreUserStartNodes = true; + startNodeId = -1; + startNodeIsVirtual = true; + } $scope.mediaPickerOverlay = { currentTarget: currentTarget, onlyImages: true, showDetails: true, disableFolderSelect: true, - startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], - startNodeIsVirtual: userData.startMediaIds.length !== 1, + startNodeId: startNodeId, + startNodeIsVirtual: startNodeIsVirtual, + ignoreUserStartNodes: ignoreUserStartNodes, view: "mediapicker", show: true, submit: function(model) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html index b9d63df9f3..9d7ff0cf40 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html @@ -40,4 +40,5 @@ Pixels + \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index beff2c2615..b8d9c8bcb6 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -262,10 +262,11 @@ namespace Umbraco.Web.Editors /// Gets the content json for the content id /// /// + /// If set to true, user and group start node permissions will be ignored. /// [OutgoingEditorModelEvent] [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetById(int id) + public ContentItemDisplay GetById(int id, [FromUri]bool ignoreUserStartNodes = false) { var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); if (foundContent == null) @@ -1116,6 +1117,7 @@ namespace Umbraco.Web.Editors /// The content to lookup, if the contentItem is not specified /// /// Specifies the already resolved content item to check against + /// If set to true, user and group start node permissions will be ignored. /// internal static bool CheckPermissions( IDictionary storage, @@ -1125,7 +1127,8 @@ namespace Umbraco.Web.Editors IEntityService entityService, int nodeId, char[] permissionsToCheck = null, - IContent contentItem = null) + IContent contentItem = null, + bool ignoreUserStartNodes = false) { if (storage == null) throw new ArgumentNullException("storage"); if (user == null) throw new ArgumentNullException("user"); @@ -1146,6 +1149,11 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } + if(ignoreUserStartNodes) + { + return true; + } + var hasPathAccess = (nodeId == Constants.System.Root) ? user.HasContentRootAccess(entityService) : (nodeId == Constants.System.RecycleBinContent) diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 8bde435ef6..a3f76db4f2 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -83,8 +83,25 @@ namespace Umbraco.Web.Editors /// A starting point for the search, generally a node id, but for members this is a member type alias /// /// + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] [HttpGet] public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null) + { + return Search(query, type, false, searchFrom); + } + + /// + /// Searches for results based on the entity type + /// + /// + /// + /// + /// A starting point for the search, generally a node id, but for members this is a member type alias + /// + /// If set to true, user and group start node permissions will be ignored. + /// + [HttpGet] + public IEnumerable Search(string query, UmbracoEntityTypes type, bool? ignoreUserStartNodes, string searchFrom = null) { //TODO: Should we restrict search results based on what app the user has access to? // - Theoretically you shouldn't be able to see member data if you don't have access to members right? @@ -92,7 +109,7 @@ namespace Umbraco.Web.Editors if (string.IsNullOrEmpty(query)) return Enumerable.Empty(); - return ExamineSearch(query, type, searchFrom); + return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes != null && ignoreUserStartNodes.Value); } /// @@ -527,6 +544,7 @@ namespace Umbraco.Web.Editors } } + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] public PagedResult GetPagedDescendants( int id, UmbracoEntityTypes type, @@ -535,6 +553,20 @@ namespace Umbraco.Web.Editors string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = "") + { + return GetPagedDescendants(id, type, pageNumber, pageSize, + false, orderBy, orderDirection, filter); + } + + public PagedResult GetPagedDescendants( + int id, + UmbracoEntityTypes type, + int pageNumber, + int pageSize, + bool ignoreUserStartNodes, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") { if (pageNumber <= 0) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -562,7 +594,7 @@ namespace Umbraco.Web.Editors break; } - entities = aids == null || aids.Contains(Constants.System.Root) + entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes ? Services.EntityService.GetPagedDescendantsFromRoot(objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter, includeTrashed: false) : Services.EntityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); } @@ -598,9 +630,15 @@ namespace Umbraco.Web.Editors } } + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] public IEnumerable GetAncestors(int id, UmbracoEntityTypes type) { - return GetResultForAncestors(id, type); + return GetResultForAncestors(id, type, false); + } + + public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, bool ignoreUserStartNodes) + { + return GetResultForAncestors(id, type, ignoreUserStartNodes); } public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter, [FromUri]IDictionary postFilterParams) @@ -614,11 +652,12 @@ namespace Umbraco.Web.Editors /// /// /// + /// If set to true, user and group start node permissions will be ignored. /// - private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null) + private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false) { long total; - return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, searchFrom); + return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, ignoreUserStartNodes, searchFrom); } @@ -645,7 +684,7 @@ namespace Umbraco.Web.Editors } } - private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType) + private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, bool ignoreUserStartNodes = false) { var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) @@ -654,35 +693,38 @@ namespace Umbraco.Web.Editors var ids = Services.EntityService.Get(id).Path.Split(',').Select(int.Parse).Distinct().ToArray(); - int[] aids = null; - switch (entityType) + if (ignoreUserStartNodes == false) { - case UmbracoEntityTypes.Document: - aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); - break; - case UmbracoEntityTypes.Media: - aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); - break; - } - - if (aids != null) - { - var lids = new List(); - var ok = false; - foreach (var i in ids) + int[] aids = null; + switch (entityType) { - if (ok) - { - lids.Add(i); - continue; - } - if (aids.Contains(i)) - { - lids.Add(i); - ok = true; - } + case UmbracoEntityTypes.Document: + aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + } + + if (aids != null) + { + var lids = new List(); + var ok = false; + foreach (var i in ids) + { + if (ok) + { + lids.Add(i); + continue; + } + if (aids.Contains(i)) + { + lids.Add(i); + ok = true; + } + } + ids = lids.ToArray(); } - ids = lids.ToArray(); } return ids.Length == 0 diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index d411bf2197..6c0c293572 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -264,11 +264,12 @@ namespace Umbraco.Web.Editors string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, - string filter = "") + string filter = "", + bool ignoreUserStartNodes = false) { //if a request is made for the root node data but the user's start node is not the default, then // we need to return their start nodes - if (id == Constants.System.Root && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false) + if (id == Constants.System.Root && UserStartNodes.Length > 0 && (UserStartNodes.Contains(Constants.System.Root) == false && ignoreUserStartNodes == false)) { if (pageNumber > 0) return new PagedResult>(0, 0, 0); @@ -346,6 +347,7 @@ namespace Umbraco.Web.Editors /// /// /// + /// If set to true, user and group start node permissions will be ignored. /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(Guid id, @@ -360,7 +362,7 @@ namespace Umbraco.Web.Editors var entity = Services.EntityService.GetByKey(id); if (entity != null) { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, ignoreUserStartNodes); } throw new HttpResponseException(HttpStatusCode.NotFound); } @@ -400,6 +402,7 @@ namespace Umbraco.Web.Editors /// /// /// + /// If set to true, user and group start node permissions will be ignored. /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(Udi id, @@ -417,7 +420,7 @@ namespace Umbraco.Web.Editors var entity = Services.EntityService.GetByKey(guidUdi.Guid); if (entity != null) { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, ignoreUserStartNodes); } } @@ -433,7 +436,8 @@ namespace Umbraco.Web.Editors string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, - string filter = "") + string filter = "", + bool ignoreUserStartNodes = false) { foreach (var type in new[] { typeof(int), typeof(Guid) }) { diff --git a/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs index 5b99264113..0039385e5f 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs @@ -15,10 +15,11 @@ namespace Umbraco.Web.PropertyEditors { InternalPreValues = new Dictionary { - {"startNodeId", "-1"}, + {"startNodeId", "-1"}, {"showOpenButton", "0"}, {"showEditButton", "0"}, {"showPathOnHover", "0"}, + {"ignoreUserStartNodes", "0"}, {"idType", "udi"} }; } @@ -39,7 +40,7 @@ namespace Umbraco.Web.PropertyEditors { public ContentPickerPreValueEditor() { - //create the fields + //create the fields Fields.Add(new PreValueField() { Key = "showOpenButton", @@ -48,6 +49,13 @@ namespace Umbraco.Web.PropertyEditors Description = "Opens the node in a dialog" }); Fields.Add(new PreValueField() + { + Key = "ignoreUserStartNodes", + View = "boolean", + Name = "Ignore user start nodes", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." + }); + Fields.Add(new PreValueField() { Key = "startNodeId", View = "treepicker", @@ -60,4 +68,4 @@ namespace Umbraco.Web.PropertyEditors } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index af30b4ceeb..4a8803a099 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -136,6 +136,9 @@ namespace Umbraco.Web.PropertyEditors [PreValueField("rte", "Rich text editor", "views/propertyeditors/rte/rte.prevalues.html", Description = "Rich text editor configuration")] public string Rte { get; set; } + + [PreValueField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] + public bool IgnoreUserStartNodes { get; set; } } #region Application event handler, used to bind to events on startup diff --git a/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs index 2d9ee68b3b..6aa7ab4a54 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs @@ -57,6 +57,13 @@ namespace Umbraco.Web.PropertyEditors Description = "Do not allow folders to be picked." }); Fields.Add(new PreValueField() + { + Key = "ignoreUserStartNodes", + View = "boolean", + Name = "Ignore user start nodes", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." + }); + Fields.Add(new PreValueField() { Key = "startNodeId", View = "mediapicker", @@ -69,4 +76,4 @@ namespace Umbraco.Web.PropertyEditors } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs index f43ecd48be..f57a9951b6 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs @@ -16,6 +16,7 @@ namespace Umbraco.Web.PropertyEditors {"showOpenButton", "0"}, {"showEditButton", "0"}, {"showPathOnHover", "0"}, + {"ignoreUserStartNodes", "0"}, {"idType", "udi"} }; } @@ -38,6 +39,13 @@ namespace Umbraco.Web.PropertyEditors { //create the fields Fields.Add(new PreValueField() + { + Key = "ignoreUserStartNodes", + View = "boolean", + Name = "Ignore user start nodes", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." + }); + Fields.Add(new PreValueField() { Key = "startNode", View = "treesource", @@ -118,4 +126,4 @@ namespace Umbraco.Web.PropertyEditors } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs index a058a16b8a..f3ae317efa 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs @@ -32,6 +32,13 @@ namespace Umbraco.Web.PropertyEditors { public MultiUrlPickerPreValueEditor() { + Fields.Add(new PreValueField() + { + Key = "ignoreUserStartNodes", + View = "boolean", + Name = "Ignore user start nodes", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." + }); Fields.Add(new PreValueField { Key = "minNumber", diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs index 541dccaa4e..a96c0724ff 100644 --- a/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs @@ -14,6 +14,7 @@ namespace Umbraco.Web.PropertyEditors { InternalPreValues = new Dictionary { + {"ignoreUserStartNodes", "0"}, {"idType", "udi"} }; } @@ -32,8 +33,11 @@ namespace Umbraco.Web.PropertyEditors internal class RelatedLinksPreValueEditor : PreValueEditor { + [PreValueField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] + public bool IgnoreUserStartNodes { get; set; } + [PreValueField("max", "Maximum number of links", "number", Description = "Enter the maximum amount of links to be added, enter 0 for unlimited")] - public int Maximum { get; set; } + public int Maximum { get; set; } } } } diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs index cd08a8ad45..69445bc304 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs @@ -23,6 +23,14 @@ namespace Umbraco.Web.PropertyEditors Key = "editor" }); + Fields.Add(new PreValueField() + { + Key = "ignoreUserStartNodes", + View = "boolean", + Name = "Ignore user start nodes", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." + }); + Fields.Add(new PreValueField() { Name = "Hide Label", diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index 38347de9ed..8845e9c323 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -16,6 +16,7 @@ namespace Umbraco.Web.Search internal class UmbracoTreeSearcher { /// + /// This method is obsolete, use the overload with ignoreUserStartNodes instead /// Searches for results based on the entity type /// /// @@ -28,12 +29,37 @@ namespace Umbraco.Web.Search /// /// /// + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] public IEnumerable ExamineSearch( UmbracoHelper umbracoHelper, string query, UmbracoEntityTypes entityType, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) + { + return ExamineSearch(umbracoHelper, query, entityType, pageSize, pageIndex, out totalFound, false, searchFrom); + } + + /// + /// Searches for results based on the entity type + /// + /// + /// + /// + /// + /// + /// A starting point for the search, generally a node id, but for members this is a member type alias + /// + /// + /// + /// If set to true, user and group start node permissions will be ignored. + /// + public IEnumerable ExamineSearch( + UmbracoHelper umbracoHelper, + string query, + UmbracoEntityTypes entityType, + int pageSize, + long pageIndex, out long totalFound, bool ignoreUserStartNodes, string searchFrom = null) { var sb = new StringBuilder(); @@ -61,12 +87,12 @@ namespace Umbraco.Web.Search case UmbracoEntityTypes.Media: type = "media"; var allMediaStartNodes = umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(appContext.Services.EntityService); - AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, appContext.Services.EntityService); + AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, appContext.Services.EntityService); break; case UmbracoEntityTypes.Document: type = "content"; var allContentStartNodes = umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(appContext.Services.EntityService); - AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, appContext.Services.EntityService); + AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, ignoreUserStartNodes, appContext.Services.EntityService); break; default: throw new NotSupportedException("The " + typeof(UmbracoTreeSearcher) + " currently does not support searching against object type " + entityType); @@ -203,7 +229,7 @@ namespace Umbraco.Web.Search } } - private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, int[] startNodeIds, string searchFrom, IEntityService entityService) + private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, int[] startNodeIds, string searchFrom, bool ignoreUserStartNodes, IEntityService entityService) { if (sb == null) throw new ArgumentNullException("sb"); if (entityService == null) throw new ArgumentNullException("entityService"); @@ -228,7 +254,7 @@ namespace Umbraco.Web.Search // make sure we don't find anything sb.Append("+__Path:none "); } - else if (startNodeIds.Contains(-1) == false) // -1 = no restriction + else if (startNodeIds.Contains(-1) == false && ignoreUserStartNodes == false) // -1 = no restriction { var entityPaths = entityService.GetAllPaths(objectType, startNodeIds); diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index af38e21546..2624c89b56 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -69,7 +69,7 @@ namespace Umbraco.Web.Trees { var node = base.CreateRootNode(queryStrings); - if (IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false) + if (IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false && IgnoreUserStartNodes(queryStrings) == false) { node.AdditionalData["noAccess"] = true; } @@ -91,7 +91,7 @@ namespace Umbraco.Web.Trees { bool hasPathAccess; var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out hasPathAccess); - if (entityIsAncestorOfStartNodes == false) + if (IgnoreUserStartNodes(queryStrings) == false && entityIsAncestorOfStartNodes == false) return null; var treeNode = GetSingleTreeNode(e, parentId, queryStrings); @@ -101,7 +101,7 @@ namespace Umbraco.Web.Trees //the node so we need to return null; return null; } - if (hasPathAccess == false) + if (IgnoreUserStartNodes(queryStrings) == false && hasPathAccess == false) { treeNode.AdditionalData["noAccess"] = true; } @@ -141,7 +141,7 @@ namespace Umbraco.Web.Trees // ensure that the user has access to that node, otherwise return the empty tree nodes collection // TODO: in the future we could return a validation statement so we can have some UI to notify the user they don't have access - if (HasPathAccess(id, queryStrings) == false) + if (IgnoreUserStartNodes(queryStrings) == false && HasPathAccess(id, queryStrings) == false) { LogHelper.Warn("User " + Security.CurrentUser.Username + " does not have access to node with id " + id); return nodes; @@ -158,7 +158,7 @@ namespace Umbraco.Web.Trees // get child entities - if id is root, but user's start nodes do not contain the // root node, this returns the start nodes instead of root's children - var entities = GetChildEntities(id).ToList(); + var entities = GetChildEntities(id, queryStrings).ToList(); nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings)).Where(x => x != null)); // if the user does not have access to the root node, what we have is the start nodes, @@ -190,7 +190,7 @@ namespace Umbraco.Web.Trees protected abstract UmbracoObjectTypes UmbracoObjectType { get; } - protected IEnumerable GetChildEntities(string id) + protected IEnumerable GetChildEntities(string id, FormDataCollection queryStrings) { // try to parse id as an integer else use GetEntityFromId // which will grok Guids, Udis, etc and let use obtain the id diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index 98eb2ec8bc..2ea41ff128 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -349,6 +349,16 @@ namespace Umbraco.Web.Trees return queryStrings.GetValue(TreeQueryStringParameters.IsDialog); } + /// + /// If the request should allows a user to choose nodes that they normally don't have access to + /// + /// + /// + protected bool IgnoreUserStartNodes(FormDataCollection queryStrings) + { + return queryStrings.GetValue(TreeQueryStringParameters.IgnoreUserStartNodes); + } + /// /// An event that allows developers to modify the tree node collection that is being rendered /// diff --git a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs index 7f6fa28187..c79b9f8781 100644 --- a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs +++ b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs @@ -8,7 +8,8 @@ public const string IsDialog = "isDialog"; public const string Application = "application"; public const string StartNodeId = "startNodeId"; + public const string IgnoreUserStartNodes = "ignoreUserStartNodes"; //public const string OnNodeClick = "OnNodeClick"; //public const string RenderParent = "RenderParent"; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs index 18880b9f96..64fe9a4b65 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Globalization; @@ -52,7 +52,7 @@ namespace Umbraco.Web.WebApi.Filters _paramName = paramName; _permissionToCheck = ActionBrowse.Instance.Letter; - } + } public EnsureUserPermissionForContentAttribute(string paramName, char permissionToCheck) : this(paramName) @@ -72,6 +72,9 @@ namespace Umbraco.Web.WebApi.Filters //not logged in throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized); } + + var ignoreUserStartNodes = actionContext.ActionArguments.ContainsKey("ignoreUserStartNodes") && + bool.Parse(actionContext.ActionArguments.GetValueAsString("ignoreUserStartNodes")); int nodeId; if (_nodeId.HasValue == false) @@ -124,9 +127,11 @@ namespace Umbraco.Web.WebApi.Filters actionContext.Request.Properties, UmbracoContext.Current.Security.CurrentUser, ApplicationContext.Current.Services.UserService, - ApplicationContext.Current.Services.ContentService, - ApplicationContext.Current.Services.EntityService, - nodeId, _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null)) + ApplicationContext.Current.Services.ContentService, + ApplicationContext.Current.Services.EntityService, + nodeId, + _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null, + ignoreUserStartNodes: ignoreUserStartNodes)) { base.OnActionExecuting(actionContext); } diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index beb67f3395..23d4fb871c 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -3,11 +3,13 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Web; using System.Web.Http.Filters; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Trees; namespace Umbraco.Web.WebApi.Filters { @@ -77,7 +79,12 @@ namespace Umbraco.Web.WebApi.Filters protected virtual void FilterItems(IUser user, IList items) { - FilterBasedOnStartNode(items, user); + bool.TryParse(HttpContext.Current.Request.QueryString.Get(TreeQueryStringParameters.IgnoreUserStartNodes), out var ignoreUserStartNodes); + + if (ignoreUserStartNodes == false) + { + FilterBasedOnStartNode(items, user); + } } internal void FilterBasedOnStartNode(IList items, IUser user) From 7557c584d8331482ffb7157dcf2c4d083c5e226d Mon Sep 17 00:00:00 2001 From: Linus Elander Date: Wed, 19 Jun 2019 21:41:47 +0200 Subject: [PATCH 004/776] Adjusts conditions for which indexes should be affected when a node is unpublished --- src/Umbraco.Web/Search/ExamineComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index 3583e5b7f9..74b9e720b1 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -737,7 +737,7 @@ namespace Umbraco.Web.Search { var strId = id.ToString(CultureInfo.InvariantCulture); foreach (var index in examineComponent._examineManager.Indexes.OfType() - .Where(x => (keepIfUnpublished && !x.PublishedValuesOnly) || !keepIfUnpublished) + .Where(x => x.PublishedValuesOnly || !keepIfUnpublished) .Where(x => x.EnableDefaultEventHandler)) { index.DeleteFromIndex(strId); From 4e4487167126d89dc2174592d529dc73b5f9828b Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 20 Jun 2019 15:57:31 +0200 Subject: [PATCH 005/776] The NuGet packages needs an extra DotNetCompilerPlatform transform to be sure it gets applied as it sometimes didn't Added SignalR binding redirect for Umbraco Cloud Added System.Web.Http.WebHost binding redirect for uCommerce --- build/NuSpecs/tools/Web.config.install.xdt | 18 ++++++++++++++++-- src/Umbraco.Web.UI/web.Template.config | 8 ++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index a381a58c3e..4f8a1927a8 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -324,7 +324,6 @@ - @@ -337,6 +336,9 @@ + + + @@ -369,7 +371,7 @@ - + @@ -398,6 +400,18 @@ + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index fe25dd0c0f..583952f29d 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -466,6 +466,14 @@ + + + + + + + + From 4df838c20c5f128d429602f4f933f851469dfe0c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 20 Jun 2019 16:02:42 +0200 Subject: [PATCH 006/776] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1358 - Changed the ignoreUserStartNodes in public endpoints to the data type id, and then look up the setting --- src/Umbraco.Core/Services/DataTypeService.cs | 1296 ++++----- src/Umbraco.Core/Services/IDataTypeService.cs | 335 +-- .../tree/umbtreesearchbox.directive.js | 10 +- .../src/common/resources/content.resource.js | 122 +- .../src/common/resources/entity.resource.js | 1168 ++++---- .../src/common/resources/media.resource.js | 1118 ++++---- .../src/common/services/search.service.js | 338 +-- .../common/dialogs/linkpicker.controller.js | 334 +-- .../src/views/common/dialogs/linkpicker.html | 173 +- .../common/dialogs/treepicker.controller.js | 861 +++--- .../overlays/contentpicker/contentpicker.html | 2 +- .../linkpicker/linkpicker.controller.js | 12 +- .../overlays/linkpicker/linkpicker.html | 3 +- .../mediaPicker/mediapicker.controller.js | 852 +++--- .../treepicker/treepicker.controller.js | 6 +- .../overlays/treepicker/treepicker.html | 112 +- .../contentpicker/contentpicker.controller.js | 732 ++--- .../grid/editors/media.controller.js | 6 +- .../grid/editors/rte.controller.js | 12 +- .../mediapicker/mediapicker.controller.js | 369 +-- .../multiurlpicker.controller.js | 5 +- .../relatedlinks/relatedlinks.controller.js | 484 ++-- .../propertyeditors/rte/rte.controller.js | 805 +++--- src/Umbraco.Web/Editors/ContentController.cs | 2454 ++++++++--------- src/Umbraco.Web/Editors/EntityController.cs | 2003 +++++++------- src/Umbraco.Web/Editors/MediaController.cs | 2031 +++++++------- .../ContentEditing/ContentPropertyBasic.cs | 97 +- .../Mapping/ContentPropertyBasicConverter.cs | 153 +- src/Umbraco.Web/Trees/TreeControllerBase.cs | 808 +++--- .../Trees/TreeQueryStringParameters.cs | 30 +- ...EnsureUserPermissionForContentAttribute.cs | 302 +- .../FilterAllowedOutgoingMediaAttribute.cs | 11 +- 32 files changed, 8547 insertions(+), 8497 deletions(-) diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 0ee3e9b823..43c2b69c47 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -1,640 +1,656 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Events; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.PropertyEditors; -using umbraco.interfaces; -using Umbraco.Core.Exceptions; - -namespace Umbraco.Core.Services -{ - /// - /// Represents the DataType Service, which is an easy access to operations involving - /// - public class DataTypeService : ScopeRepositoryService, IDataTypeService - { - - public DataTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) - : base(provider, repositoryFactory, logger, eventMessagesFactory) - { - } - - #region Containers - - public Attempt> CreateContainer(int parentId, string name, int userId = 0) - { - var evtMsgs = EventMessagesFactory.Get(); - using (var uow = UowProvider.GetUnitOfWork()) - { - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - - try - { - var container = new EntityContainer(Constants.ObjectTypes.DataTypeGuid) - { - Name = name, - ParentId = parentId, - CreatorId = userId - }; - - if (uow.Events.DispatchCancelable(SavingContainer, this, new SaveEventArgs(container, evtMsgs))) - { - uow.Commit(); - return Attempt.Fail(new OperationStatus(container, OperationStatusType.FailedCancelledByEvent, evtMsgs)); - } - - repo.AddOrUpdate(container); - uow.Commit(); - - uow.Events.Dispatch(SavedContainer, this, new SaveEventArgs(container, evtMsgs)); - //TODO: Audit trail ? - - return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); - } - catch (Exception ex) - { - return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); - } - } - } - - public EntityContainer GetContainer(int containerId) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - return repo.Get(containerId); - } - } - - public EntityContainer GetContainer(Guid containerId) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - return repo.Get(containerId); - } - } - - public IEnumerable GetContainers(string name, int level) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - return repo.Get(name, level); - } - } - - public IEnumerable GetContainers(IDataTypeDefinition dataTypeDefinition) - { - var ancestorIds = dataTypeDefinition.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => - { - var asInt = x.TryConvertTo(); - if (asInt) return asInt.Result; - return int.MinValue; - }) - .Where(x => x != int.MinValue && x != dataTypeDefinition.Id) - .ToArray(); - - return GetContainers(ancestorIds); - } - - public IEnumerable GetContainers(int[] containerIds) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - return repo.GetAll(containerIds); - } - } - - public Attempt SaveContainer(EntityContainer container, int userId = 0) - { - var evtMsgs = EventMessagesFactory.Get(); - - if (container.ContainedObjectType != Constants.ObjectTypes.DataTypeGuid) - { - var ex = new InvalidOperationException("Not a " + Constants.ObjectTypes.DataTypeGuid + " container."); - return OperationStatus.Exception(evtMsgs, ex); - } - - if (container.HasIdentity && container.IsPropertyDirty("ParentId")) - { - var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); - return OperationStatus.Exception(evtMsgs, ex); - } - - using (var uow = UowProvider.GetUnitOfWork()) - { - if (uow.Events.DispatchCancelable(SavingContainer, this, new SaveEventArgs(container, evtMsgs))) - return OperationStatus.Cancelled(evtMsgs); - - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - repo.AddOrUpdate(container); - uow.Commit(); - uow.Events.Dispatch(SavedContainer, this, new SaveEventArgs(container, evtMsgs)); - } - - //TODO: Audit trail ? - - return OperationStatus.Success(evtMsgs); - } - - public Attempt DeleteContainer(int containerId, int userId = 0) - { - var evtMsgs = EventMessagesFactory.Get(); - using (var uow = UowProvider.GetUnitOfWork()) - { - var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - var container = repo.Get(containerId); - if (container == null) return OperationStatus.NoOperation(evtMsgs); - - if (uow.Events.DispatchCancelable(DeletingContainer, this, new DeleteEventArgs(container, evtMsgs))) - { - uow.Commit(); - return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); - } - - repo.Delete(container); - uow.Commit(); - - uow.Events.Dispatch(DeletedContainer, this, new DeleteEventArgs(container, evtMsgs)); - - return OperationStatus.Success(evtMsgs); - //TODO: Audit trail ? - } - } - - #endregion - - /// - /// Gets a by its Name - /// - /// Name of the - /// - public IDataTypeDefinition GetDataTypeDefinitionByName(string name) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - return repository.GetByQuery(new Query().Where(x => x.Name == name)).FirstOrDefault(); - } - } - - /// - /// Gets a by its Id - /// - /// Id of the - /// - public IDataTypeDefinition GetDataTypeDefinitionById(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - return repository.Get(id); - } - } - - /// - /// Gets a by its unique guid Id - /// - /// Unique guid Id of the DataType - /// - public IDataTypeDefinition GetDataTypeDefinitionById(Guid id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - var query = Query.Builder.Where(x => x.Key == id); - - var definitions = repository.GetByQuery(query); - return definitions.FirstOrDefault(); - } - } - - /// - /// Gets a by its control Id - /// - /// Id of the DataType control - /// Collection of objects with a matching contorl id - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] - public IEnumerable GetDataTypeDefinitionByControlId(Guid id) - { - var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(id, true); - return GetDataTypeDefinitionByPropertyEditorAlias(alias); - } - - /// - /// Gets a by its control Id - /// - /// Alias of the property editor - /// Collection of objects with a matching contorl id - public IEnumerable GetDataTypeDefinitionByPropertyEditorAlias(string propertyEditorAlias) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - var query = Query.Builder.Where(x => x.PropertyEditorAlias == propertyEditorAlias); - return repository.GetByQuery(query); - } - } - - /// - /// Gets all objects or those with the ids passed in - /// - /// Optional array of Ids - /// An enumerable list of objects - public IEnumerable GetAllDataTypeDefinitions(params int[] ids) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - return repository.GetAll(ids); - } - } - - /// - /// Gets all prevalues for an - /// - /// Id of the to retrieve prevalues from - /// An enumerable list of string values - public IEnumerable GetPreValuesByDataTypeId(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - //now convert the collection to a string list - var collection = repository.GetPreValuesCollectionByDataTypeId(id); - //now convert the collection to a string list - return collection.FormatAsDictionary().Select(x => x.Value.Value).ToList(); - } - } - - /// - /// Returns the PreValueCollection for the specified data type - /// - /// - /// - public PreValueCollection GetPreValuesCollectionByDataTypeId(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - return repository.GetPreValuesCollectionByDataTypeId(id); - } - } - - /// - /// Gets a specific PreValue by its Id - /// - /// Id of the PreValue to retrieve the value from - /// PreValue as a string - public string GetPreValueAsString(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - return repository.GetPreValueAsString(id); - } - } - - public Attempt> Move(IDataTypeDefinition toMove, int parentId) - { - var evtMsgs = EventMessagesFactory.Get(); - - var moveInfo = new List>(); - using (var uow = UowProvider.GetUnitOfWork()) - { - var moveEventInfo = new MoveEventInfo(toMove, toMove.Path, parentId); - var moveEventArgs = new MoveEventArgs(evtMsgs, moveEventInfo); - if (uow.Events.DispatchCancelable(Moving, this, moveEventArgs)) - { - uow.Commit(); - return Attempt.Fail(new OperationStatus(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs)); - } - - var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - - try - { - EntityContainer container = null; - if (parentId > 0) - { - container = containerRepository.Get(parentId); - if (container == null) - throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); - } - moveInfo.AddRange(repository.Move(toMove, container)); - } - catch (DataOperationException ex) - { - return Attempt.Fail( - new OperationStatus(ex.Operation, evtMsgs)); - } - uow.Commit(); - moveEventArgs.MoveInfoCollection = moveInfo; - moveEventArgs.CanCancel = false; - uow.Events.Dispatch(Moved, this, moveEventArgs); - } - - return Attempt.Succeed( - new OperationStatus(MoveOperationStatusType.Success, evtMsgs)); - } - - /// - /// Saves an - /// - /// to save - /// Id of the user issueing the save - public void Save(IDataTypeDefinition dataTypeDefinition, int userId = 0) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(dataTypeDefinition); - if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs)) - { - uow.Commit(); - return; - } - - if (string.IsNullOrWhiteSpace(dataTypeDefinition.Name)) - { - throw new ArgumentException("Cannot save datatype with empty name."); - } - - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - - dataTypeDefinition.CreatorId = userId; - repository.AddOrUpdate(dataTypeDefinition); - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs); - - Audit(uow, AuditType.Save, "Save DataTypeDefinition performed by user", userId, dataTypeDefinition.Id); - uow.Commit(); - } - } - - /// - /// Saves a collection of - /// - /// to save - /// Id of the user issueing the save - public void Save(IEnumerable dataTypeDefinitions, int userId = 0) - { - Save(dataTypeDefinitions, userId, true); - } - - /// - /// Saves a collection of - /// - /// to save - /// Id of the user issueing the save - /// Boolean indicating whether or not to raise events - public void Save(IEnumerable dataTypeDefinitions, int userId, bool raiseEvents) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(dataTypeDefinitions); - if (raiseEvents) - { - if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs)) - { - uow.Commit(); - return; - } - } - - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - - foreach (var dataTypeDefinition in dataTypeDefinitions) - { - dataTypeDefinition.CreatorId = userId; - repository.AddOrUpdate(dataTypeDefinition); - } - - if (raiseEvents) - { - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs); - } - - Audit(uow, AuditType.Save, string.Format("Save DataTypeDefinition performed by user"), userId, -1); - uow.Commit(); - } - } - - /// - /// Saves a list of PreValues for a given DataTypeDefinition - /// - /// Id of the DataTypeDefinition to save PreValues for - /// List of string values to save - [Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")] - public void SavePreValues(int dataTypeId, IEnumerable values) - { - //TODO: Should we raise an event here since we are really saving values for the data type? - - using (var uow = UowProvider.GetUnitOfWork()) - { - var sortOrderObj = - uow.Database.ExecuteScalar( - "SELECT max(sortorder) FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = dataTypeId }); - int sortOrder; - if (sortOrderObj == null || int.TryParse(sortOrderObj.ToString(), out sortOrder) == false) - { - sortOrder = 1; - } - - foreach (var value in values) - { - var dto = new DataTypePreValueDto { DataTypeNodeId = dataTypeId, Value = value, SortOrder = sortOrder }; - uow.Database.Insert(dto); - sortOrder++; - } - - uow.Commit(); - } - } - - /// - /// Saves/updates the pre-values - /// - /// - /// - /// - /// We need to actually look up each pre-value and maintain it's id if possible - this is because of silly property editors - /// like 'dropdown list publishing keys' - /// - public void SavePreValues(int dataTypeId, IDictionary values) - { - var dtd = GetDataTypeDefinitionById(dataTypeId); - if (dtd == null) - { - throw new InvalidOperationException("No data type found for id " + dataTypeId); - } - SavePreValues(dtd, values); - } - - /// - /// Saves/updates the pre-values - /// - /// - /// - /// - /// We need to actually look up each pre-value and maintain it's id if possible - this is because of silly property editors - /// like 'dropdown list publishing keys' - /// - public void SavePreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values) - { - //TODO: Should we raise an event here since we are really saving values for the data type? - - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - repository.AddOrUpdatePreValues(dataTypeDefinition, values); - uow.Commit(); - } - } - - /// - /// This will save a data type and it's pre-values in one transaction - /// - /// - /// - /// - public void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values, int userId = 0) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(dataTypeDefinition); - if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs)) - { - uow.Commit(); - return; - } - - // if preValues contain the data type, override the data type definition accordingly - if (values != null && values.ContainsKey(Constants.PropertyEditors.PreValueKeys.DataValueType)) - dataTypeDefinition.DatabaseType = PropertyValueEditor.GetDatabaseType(values[Constants.PropertyEditors.PreValueKeys.DataValueType].Value); - - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - - dataTypeDefinition.CreatorId = userId; - - //add/update the dtd - repository.AddOrUpdate(dataTypeDefinition); - - //add/update the prevalues - repository.AddOrUpdatePreValues(dataTypeDefinition, values); - - Audit(uow, AuditType.Save, string.Format("Save DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); - uow.Commit(); - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs); - } - } - - /// - /// Deletes an - /// - /// - /// Please note that deleting a will remove - /// all the data that references this . - /// - /// to delete - /// Optional Id of the user issueing the deletion - public void Delete(IDataTypeDefinition dataTypeDefinition, int userId = 0) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var deleteEventArgs = new DeleteEventArgs(dataTypeDefinition); - if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs)) - { - uow.Commit(); - return; - } - - var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); - - repository.Delete(dataTypeDefinition); - - Audit(uow, AuditType.Delete, "Delete DataTypeDefinition performed by user", userId, dataTypeDefinition.Id); - uow.Commit(); - deleteEventArgs.CanCancel = false; - uow.Events.Dispatch(Deleted, this, deleteEventArgs); - } - } - - /// - /// Gets the specified by it's unique ID - /// - /// Id of the DataType, which corresponds to the Guid Id of the control - /// object - [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] - public IDataType GetDataTypeById(Guid id) - { - return DataTypesResolver.Current.GetById(id); - } - - /// - /// Gets a complete list of all registered 's - /// - /// An enumerable list of objects - [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] - public IEnumerable GetAllDataTypes() - { - return DataTypesResolver.Current.DataTypes; - } - - private void Audit(IScopeUnitOfWork uow, AuditType type, string message, int userId, int objectId) - { - var auditRepo = RepositoryFactory.CreateAuditRepository(uow); - auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); - } - - #region Event Handlers - - public static event TypedEventHandler> SavingContainer; - public static event TypedEventHandler> SavedContainer; - public static event TypedEventHandler> DeletingContainer; - public static event TypedEventHandler> DeletedContainer; - - /// - /// Occurs before Delete - /// - 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 Move - /// - public static event TypedEventHandler> Moving; - - /// - /// Occurs after Move - /// - public static event TypedEventHandler> Moved; - #endregion - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.PropertyEditors; +using umbraco.interfaces; +using Umbraco.Core.Exceptions; + +namespace Umbraco.Core.Services +{ + /// + /// Represents the DataType Service, which is an easy access to operations involving + /// + public class DataTypeService : ScopeRepositoryService, IDataTypeService + { + + public DataTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory) + : base(provider, repositoryFactory, logger, eventMessagesFactory) + { + } + + #region Containers + + public Attempt> CreateContainer(int parentId, string name, int userId = 0) + { + var evtMsgs = EventMessagesFactory.Get(); + using (var uow = UowProvider.GetUnitOfWork()) + { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + + try + { + var container = new EntityContainer(Constants.ObjectTypes.DataTypeGuid) + { + Name = name, + ParentId = parentId, + CreatorId = userId + }; + + if (uow.Events.DispatchCancelable(SavingContainer, this, new SaveEventArgs(container, evtMsgs))) + { + uow.Commit(); + return Attempt.Fail(new OperationStatus(container, OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + + repo.AddOrUpdate(container); + uow.Commit(); + + uow.Events.Dispatch(SavedContainer, this, new SaveEventArgs(container, evtMsgs)); + //TODO: Audit trail ? + + return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); + } + catch (Exception ex) + { + return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); + } + } + } + + public EntityContainer GetContainer(int containerId) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + return repo.Get(containerId); + } + } + + public EntityContainer GetContainer(Guid containerId) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + return repo.Get(containerId); + } + } + + public IEnumerable GetContainers(string name, int level) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + return repo.Get(name, level); + } + } + + public IEnumerable GetContainers(IDataTypeDefinition dataTypeDefinition) + { + var ancestorIds = dataTypeDefinition.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => + { + var asInt = x.TryConvertTo(); + if (asInt) return asInt.Result; + return int.MinValue; + }) + .Where(x => x != int.MinValue && x != dataTypeDefinition.Id) + .ToArray(); + + return GetContainers(ancestorIds); + } + + public IEnumerable GetContainers(int[] containerIds) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + return repo.GetAll(containerIds); + } + } + + public Attempt SaveContainer(EntityContainer container, int userId = 0) + { + var evtMsgs = EventMessagesFactory.Get(); + + if (container.ContainedObjectType != Constants.ObjectTypes.DataTypeGuid) + { + var ex = new InvalidOperationException("Not a " + Constants.ObjectTypes.DataTypeGuid + " container."); + return OperationStatus.Exception(evtMsgs, ex); + } + + if (container.HasIdentity && container.IsPropertyDirty("ParentId")) + { + var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); + return OperationStatus.Exception(evtMsgs, ex); + } + + using (var uow = UowProvider.GetUnitOfWork()) + { + if (uow.Events.DispatchCancelable(SavingContainer, this, new SaveEventArgs(container, evtMsgs))) + return OperationStatus.Cancelled(evtMsgs); + + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + repo.AddOrUpdate(container); + uow.Commit(); + uow.Events.Dispatch(SavedContainer, this, new SaveEventArgs(container, evtMsgs)); + } + + //TODO: Audit trail ? + + return OperationStatus.Success(evtMsgs); + } + + public Attempt DeleteContainer(int containerId, int userId = 0) + { + var evtMsgs = EventMessagesFactory.Get(); + using (var uow = UowProvider.GetUnitOfWork()) + { + var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + var container = repo.Get(containerId); + if (container == null) return OperationStatus.NoOperation(evtMsgs); + + if (uow.Events.DispatchCancelable(DeletingContainer, this, new DeleteEventArgs(container, evtMsgs))) + { + uow.Commit(); + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + + repo.Delete(container); + uow.Commit(); + + uow.Events.Dispatch(DeletedContainer, this, new DeleteEventArgs(container, evtMsgs)); + + return OperationStatus.Success(evtMsgs); + //TODO: Audit trail ? + } + } + + #endregion + + /// + /// Gets a by its Name + /// + /// Name of the + /// + public IDataTypeDefinition GetDataTypeDefinitionByName(string name) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + return repository.GetByQuery(new Query().Where(x => x.Name == name)).FirstOrDefault(); + } + } + + /// + /// Gets a by its Id + /// + /// Id of the + /// + public IDataTypeDefinition GetDataTypeDefinitionById(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + return repository.Get(id); + } + } + + /// + /// Gets a by its unique guid Id + /// + /// Unique guid Id of the DataType + /// + public IDataTypeDefinition GetDataTypeDefinitionById(Guid id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + var query = Query.Builder.Where(x => x.Key == id); + + var definitions = repository.GetByQuery(query); + return definitions.FirstOrDefault(); + } + } + + /// + /// Gets a by its control Id + /// + /// Id of the DataType control + /// Collection of objects with a matching contorl id + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] + public IEnumerable GetDataTypeDefinitionByControlId(Guid id) + { + var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(id, true); + return GetDataTypeDefinitionByPropertyEditorAlias(alias); + } + + /// + /// Gets a by its control Id + /// + /// Alias of the property editor + /// Collection of objects with a matching contorl id + public IEnumerable GetDataTypeDefinitionByPropertyEditorAlias(string propertyEditorAlias) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + var query = Query.Builder.Where(x => x.PropertyEditorAlias == propertyEditorAlias); + return repository.GetByQuery(query); + } + } + + /// + /// Gets all objects or those with the ids passed in + /// + /// Optional array of Ids + /// An enumerable list of objects + public IEnumerable GetAllDataTypeDefinitions(params int[] ids) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + return repository.GetAll(ids); + } + } + + /// + /// Gets all prevalues for an + /// + /// Id of the to retrieve prevalues from + /// An enumerable list of string values + public IEnumerable GetPreValuesByDataTypeId(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + //now convert the collection to a string list + var collection = repository.GetPreValuesCollectionByDataTypeId(id); + //now convert the collection to a string list + return collection.FormatAsDictionary().Select(x => x.Value.Value).ToList(); + } + } + + /// + /// Returns the PreValueCollection for the specified data type + /// + /// + /// + public PreValueCollection GetPreValuesCollectionByDataTypeId(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + return repository.GetPreValuesCollectionByDataTypeId(id); + } + } + + /// + /// Gets a specific PreValue by its Id + /// + /// Id of the PreValue to retrieve the value from + /// PreValue as a string + public string GetPreValueAsString(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + return repository.GetPreValueAsString(id); + } + } + + public Attempt> Move(IDataTypeDefinition toMove, int parentId) + { + var evtMsgs = EventMessagesFactory.Get(); + + var moveInfo = new List>(); + using (var uow = UowProvider.GetUnitOfWork()) + { + var moveEventInfo = new MoveEventInfo(toMove, toMove.Path, parentId); + var moveEventArgs = new MoveEventArgs(evtMsgs, moveEventInfo); + if (uow.Events.DispatchCancelable(Moving, this, moveEventArgs)) + { + uow.Commit(); + return Attempt.Fail(new OperationStatus(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + + var containerRepository = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid); + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + + try + { + EntityContainer container = null; + if (parentId > 0) + { + container = containerRepository.Get(parentId); + if (container == null) + throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); + } + moveInfo.AddRange(repository.Move(toMove, container)); + } + catch (DataOperationException ex) + { + return Attempt.Fail( + new OperationStatus(ex.Operation, evtMsgs)); + } + uow.Commit(); + moveEventArgs.MoveInfoCollection = moveInfo; + moveEventArgs.CanCancel = false; + uow.Events.Dispatch(Moved, this, moveEventArgs); + } + + return Attempt.Succeed( + new OperationStatus(MoveOperationStatusType.Success, evtMsgs)); + } + + public bool IsDataTypeIgnoringUserStartNodes(Guid key) + { + var dataType = GetDataTypeDefinitionById(key); + + if (dataType != null) + { + var preValues = GetPreValuesCollectionByDataTypeId(dataType.Id); + if (preValues.PreValuesAsDictionary.TryGetValue("ignoreUserStartNodes", out var preValue)) + { + return string.Equals(preValue.Value, "1", StringComparison.InvariantCulture); + } + } + + return false; + } + + /// + /// Saves an + /// + /// to save + /// Id of the user issueing the save + public void Save(IDataTypeDefinition dataTypeDefinition, int userId = 0) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(dataTypeDefinition); + if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs)) + { + uow.Commit(); + return; + } + + if (string.IsNullOrWhiteSpace(dataTypeDefinition.Name)) + { + throw new ArgumentException("Cannot save datatype with empty name."); + } + + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + + dataTypeDefinition.CreatorId = userId; + repository.AddOrUpdate(dataTypeDefinition); + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs); + + Audit(uow, AuditType.Save, "Save DataTypeDefinition performed by user", userId, dataTypeDefinition.Id); + uow.Commit(); + } + } + + /// + /// Saves a collection of + /// + /// to save + /// Id of the user issueing the save + public void Save(IEnumerable dataTypeDefinitions, int userId = 0) + { + Save(dataTypeDefinitions, userId, true); + } + + /// + /// Saves a collection of + /// + /// to save + /// Id of the user issueing the save + /// Boolean indicating whether or not to raise events + public void Save(IEnumerable dataTypeDefinitions, int userId, bool raiseEvents) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(dataTypeDefinitions); + if (raiseEvents) + { + if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs)) + { + uow.Commit(); + return; + } + } + + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + + foreach (var dataTypeDefinition in dataTypeDefinitions) + { + dataTypeDefinition.CreatorId = userId; + repository.AddOrUpdate(dataTypeDefinition); + } + + if (raiseEvents) + { + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs); + } + + Audit(uow, AuditType.Save, string.Format("Save DataTypeDefinition performed by user"), userId, -1); + uow.Commit(); + } + } + + /// + /// Saves a list of PreValues for a given DataTypeDefinition + /// + /// Id of the DataTypeDefinition to save PreValues for + /// List of string values to save + [Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")] + public void SavePreValues(int dataTypeId, IEnumerable values) + { + //TODO: Should we raise an event here since we are really saving values for the data type? + + using (var uow = UowProvider.GetUnitOfWork()) + { + var sortOrderObj = + uow.Database.ExecuteScalar( + "SELECT max(sortorder) FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = dataTypeId }); + int sortOrder; + if (sortOrderObj == null || int.TryParse(sortOrderObj.ToString(), out sortOrder) == false) + { + sortOrder = 1; + } + + foreach (var value in values) + { + var dto = new DataTypePreValueDto { DataTypeNodeId = dataTypeId, Value = value, SortOrder = sortOrder }; + uow.Database.Insert(dto); + sortOrder++; + } + + uow.Commit(); + } + } + + /// + /// Saves/updates the pre-values + /// + /// + /// + /// + /// We need to actually look up each pre-value and maintain it's id if possible - this is because of silly property editors + /// like 'dropdown list publishing keys' + /// + public void SavePreValues(int dataTypeId, IDictionary values) + { + var dtd = GetDataTypeDefinitionById(dataTypeId); + if (dtd == null) + { + throw new InvalidOperationException("No data type found for id " + dataTypeId); + } + SavePreValues(dtd, values); + } + + /// + /// Saves/updates the pre-values + /// + /// + /// + /// + /// We need to actually look up each pre-value and maintain it's id if possible - this is because of silly property editors + /// like 'dropdown list publishing keys' + /// + public void SavePreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values) + { + //TODO: Should we raise an event here since we are really saving values for the data type? + + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + repository.AddOrUpdatePreValues(dataTypeDefinition, values); + uow.Commit(); + } + } + + /// + /// This will save a data type and it's pre-values in one transaction + /// + /// + /// + /// + public void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values, int userId = 0) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(dataTypeDefinition); + if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs)) + { + uow.Commit(); + return; + } + + // if preValues contain the data type, override the data type definition accordingly + if (values != null && values.ContainsKey(Constants.PropertyEditors.PreValueKeys.DataValueType)) + dataTypeDefinition.DatabaseType = PropertyValueEditor.GetDatabaseType(values[Constants.PropertyEditors.PreValueKeys.DataValueType].Value); + + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + + dataTypeDefinition.CreatorId = userId; + + //add/update the dtd + repository.AddOrUpdate(dataTypeDefinition); + + //add/update the prevalues + repository.AddOrUpdatePreValues(dataTypeDefinition, values); + + Audit(uow, AuditType.Save, string.Format("Save DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); + uow.Commit(); + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs); + } + } + + /// + /// Deletes an + /// + /// + /// Please note that deleting a will remove + /// all the data that references this . + /// + /// to delete + /// Optional Id of the user issueing the deletion + public void Delete(IDataTypeDefinition dataTypeDefinition, int userId = 0) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var deleteEventArgs = new DeleteEventArgs(dataTypeDefinition); + if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs)) + { + uow.Commit(); + return; + } + + var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow); + + repository.Delete(dataTypeDefinition); + + Audit(uow, AuditType.Delete, "Delete DataTypeDefinition performed by user", userId, dataTypeDefinition.Id); + uow.Commit(); + deleteEventArgs.CanCancel = false; + uow.Events.Dispatch(Deleted, this, deleteEventArgs); + } + } + + /// + /// Gets the specified by it's unique ID + /// + /// Id of the DataType, which corresponds to the Guid Id of the control + /// object + [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] + public IDataType GetDataTypeById(Guid id) + { + return DataTypesResolver.Current.GetById(id); + } + + /// + /// Gets a complete list of all registered 's + /// + /// An enumerable list of objects + [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] + public IEnumerable GetAllDataTypes() + { + return DataTypesResolver.Current.DataTypes; + } + + private void Audit(IScopeUnitOfWork uow, AuditType type, string message, int userId, int objectId) + { + var auditRepo = RepositoryFactory.CreateAuditRepository(uow); + auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); + } + + #region Event Handlers + + public static event TypedEventHandler> SavingContainer; + public static event TypedEventHandler> SavedContainer; + public static event TypedEventHandler> DeletingContainer; + public static event TypedEventHandler> DeletedContainer; + + /// + /// Occurs before Delete + /// + 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 Move + /// + public static event TypedEventHandler> Moving; + + /// + /// Occurs after Move + /// + public static event TypedEventHandler> Moved; + #endregion + } +} diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index 9e119cd28a..de8ee5f49c 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -1,167 +1,168 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models; -using umbraco.interfaces; - -namespace Umbraco.Core.Services -{ - /// - /// Defines the DataType Service, which is an easy access to operations involving - /// - public interface IDataTypeService : IService - { - Attempt> CreateContainer(int parentId, string name, int userId = 0); - Attempt SaveContainer(EntityContainer container, int userId = 0); - EntityContainer GetContainer(int containerId); - EntityContainer GetContainer(Guid containerId); - IEnumerable GetContainers(string folderName, int level); - IEnumerable GetContainers(IDataTypeDefinition dataTypeDefinition); - IEnumerable GetContainers(int[] containerIds); - Attempt DeleteContainer(int containerId, int userId = 0); - - /// - /// Gets a by its Name - /// - /// Name of the - /// - IDataTypeDefinition GetDataTypeDefinitionByName(string name); - - /// - /// Gets a by its Id - /// - /// Id of the - /// - IDataTypeDefinition GetDataTypeDefinitionById(int id); - - /// - /// Gets a by its unique guid Id - /// - /// Unique guid Id of the DataType - /// - IDataTypeDefinition GetDataTypeDefinitionById(Guid id); - - /// - /// Gets all objects or those with the ids passed in - /// - /// Optional array of Ids - /// An enumerable list of objects - IEnumerable GetAllDataTypeDefinitions(params int[] ids); - - /// - /// Saves an - /// - /// to save - /// Id of the user issueing the save - void Save(IDataTypeDefinition dataTypeDefinition, int userId = 0); - - /// - /// Saves a collection of - /// - /// to save - /// Id of the user issueing the save - void Save(IEnumerable dataTypeDefinitions, int userId = 0); - - /// - /// Saves a collection of - /// - /// to save - /// Id of the user issueing the save - /// Boolean indicating whether or not to raise events - void Save(IEnumerable dataTypeDefinitions, int userId, bool raiseEvents); - - /// - /// Deletes an - /// - /// - /// Please note that deleting a will remove - /// all the data that references this . - /// - /// to delete - /// Id of the user issueing the deletion - void Delete(IDataTypeDefinition dataTypeDefinition, int userId = 0); - - /// - /// Gets the specified by it's unique ID - /// - /// Id of the DataType, which corresponds to the Guid Id of the control - /// object - [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] - IDataType GetDataTypeById(Guid id); - - /// - /// Gets a complete list of all registered 's - /// - /// An enumerable list of objects - [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] - IEnumerable GetAllDataTypes(); - - /// - /// Gets a by its control Id - /// - /// Id of the DataType control - /// - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] - IEnumerable GetDataTypeDefinitionByControlId(Guid id); - - /// - /// Gets a by its control Id - /// - /// Alias of the property editor - /// Collection of objects with a matching contorl id - IEnumerable GetDataTypeDefinitionByPropertyEditorAlias(string propertyEditorAlias); - - /// - /// Gets all values for an - /// - /// Id of the to retrieve prevalues from - /// An enumerable list of string values - IEnumerable GetPreValuesByDataTypeId(int id); - - /// - /// Gets a pre-value collection by data type id - /// - /// - /// - PreValueCollection GetPreValuesCollectionByDataTypeId(int id); - - /// - /// Saves a list of PreValues for a given DataTypeDefinition - /// - /// Id of the DataTypeDefinition to save PreValues for - /// List of string values to save - [Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")] - void SavePreValues(int dataTypeId, IEnumerable values); - - /// - /// Saves a list of PreValues for a given DataTypeDefinition - /// - /// Id of the DataTypeDefinition to save PreValues for - /// List of key/value pairs to save - void SavePreValues(int dataTypeId, IDictionary values); - - /// - /// Saves a list of PreValues for a given DataTypeDefinition - /// - /// The DataTypeDefinition to save PreValues for - /// List of key/value pairs to save - void SavePreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values); - - /// - /// Saves the data type and it's prevalues - /// - /// - /// - /// - void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values, int userId = 0); - - /// - /// Gets a specific PreValue by its Id - /// - /// Id of the PreValue to retrieve the value from - /// PreValue as a string - string GetPreValueAsString(int id); - - Attempt> Move(IDataTypeDefinition toMove, int parentId); - - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using umbraco.interfaces; + +namespace Umbraco.Core.Services +{ + /// + /// Defines the DataType Service, which is an easy access to operations involving + /// + public interface IDataTypeService : IService + { + Attempt> CreateContainer(int parentId, string name, int userId = 0); + Attempt SaveContainer(EntityContainer container, int userId = 0); + EntityContainer GetContainer(int containerId); + EntityContainer GetContainer(Guid containerId); + IEnumerable GetContainers(string folderName, int level); + IEnumerable GetContainers(IDataTypeDefinition dataTypeDefinition); + IEnumerable GetContainers(int[] containerIds); + Attempt DeleteContainer(int containerId, int userId = 0); + + /// + /// Gets a by its Name + /// + /// Name of the + /// + IDataTypeDefinition GetDataTypeDefinitionByName(string name); + + /// + /// Gets a by its Id + /// + /// Id of the + /// + IDataTypeDefinition GetDataTypeDefinitionById(int id); + + /// + /// Gets a by its unique guid Id + /// + /// Unique guid Id of the DataType + /// + IDataTypeDefinition GetDataTypeDefinitionById(Guid id); + + /// + /// Gets all objects or those with the ids passed in + /// + /// Optional array of Ids + /// An enumerable list of objects + IEnumerable GetAllDataTypeDefinitions(params int[] ids); + + /// + /// Saves an + /// + /// to save + /// Id of the user issueing the save + void Save(IDataTypeDefinition dataTypeDefinition, int userId = 0); + + /// + /// Saves a collection of + /// + /// to save + /// Id of the user issueing the save + void Save(IEnumerable dataTypeDefinitions, int userId = 0); + + /// + /// Saves a collection of + /// + /// to save + /// Id of the user issueing the save + /// Boolean indicating whether or not to raise events + void Save(IEnumerable dataTypeDefinitions, int userId, bool raiseEvents); + + /// + /// Deletes an + /// + /// + /// Please note that deleting a will remove + /// all the data that references this . + /// + /// to delete + /// Id of the user issueing the deletion + void Delete(IDataTypeDefinition dataTypeDefinition, int userId = 0); + + /// + /// Gets the specified by it's unique ID + /// + /// Id of the DataType, which corresponds to the Guid Id of the control + /// object + [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] + IDataType GetDataTypeById(Guid id); + + /// + /// Gets a complete list of all registered 's + /// + /// An enumerable list of objects + [Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")] + IEnumerable GetAllDataTypes(); + + /// + /// Gets a by its control Id + /// + /// Id of the DataType control + /// + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the overload GetDataTypeDefinitionByPropertyEditorAlias instead")] + IEnumerable GetDataTypeDefinitionByControlId(Guid id); + + /// + /// Gets a by its control Id + /// + /// Alias of the property editor + /// Collection of objects with a matching contorl id + IEnumerable GetDataTypeDefinitionByPropertyEditorAlias(string propertyEditorAlias); + + /// + /// Gets all values for an + /// + /// Id of the to retrieve prevalues from + /// An enumerable list of string values + IEnumerable GetPreValuesByDataTypeId(int id); + + /// + /// Gets a pre-value collection by data type id + /// + /// + /// + PreValueCollection GetPreValuesCollectionByDataTypeId(int id); + + /// + /// Saves a list of PreValues for a given DataTypeDefinition + /// + /// Id of the DataTypeDefinition to save PreValues for + /// List of string values to save + [Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")] + void SavePreValues(int dataTypeId, IEnumerable values); + + /// + /// Saves a list of PreValues for a given DataTypeDefinition + /// + /// Id of the DataTypeDefinition to save PreValues for + /// List of key/value pairs to save + void SavePreValues(int dataTypeId, IDictionary values); + + /// + /// Saves a list of PreValues for a given DataTypeDefinition + /// + /// The DataTypeDefinition to save PreValues for + /// List of key/value pairs to save + void SavePreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values); + + /// + /// Saves the data type and it's prevalues + /// + /// + /// + /// + void SaveDataTypeAndPreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values, int userId = 0); + + /// + /// Gets a specific PreValue by its Id + /// + /// Id of the PreValue to retrieve the value from + /// PreValue as a string + string GetPreValueAsString(int id); + + Attempt> Move(IDataTypeDefinition toMove, int parentId); + + bool IsDataTypeIgnoringUserStartNodes(Guid dataTypeId); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js index b81e62a66b..3b6fb5f9cd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js @@ -12,7 +12,7 @@ function treeSearchBox(localizationService, searchService, $q) { searchFromName: "@", showSearch: "@", section: "@", - ignoreUserStartNodes: "@", + datatypeId: "@", hideSearchCallback: "=", searchCallback: "=" }, @@ -62,10 +62,10 @@ function treeSearchBox(localizationService, searchService, $q) { searchArgs["searchFrom"] = scope.searchFromId; } - //append ignoreUserStartNodes value if there is one - if (scope.ignoreUserStartNodes) { - searchArgs["ignoreUserStartNodes"] = scope.ignoreUserStartNodes; - } + //append dataTypeId value if there is one + if (scope.datatypeId) { + searchArgs["dataTypeId"] = scope.datatypeId; + } searcher(searchArgs).then(function (data) { scope.searchCallback(data); 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 77e587bcc6..31eccfb5aa 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 @@ -19,8 +19,8 @@ * contentResource.getById(1234) * .then(function(data) { * $scope.content = data; - * }); - * + * }); + * **/ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { @@ -83,7 +83,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .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 @@ -124,9 +124,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert("node was moved"); * }, function(err){ - * alert("node didnt move:" + err.data.Message); + * 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 @@ -167,9 +167,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert("node was copied"); * }, function(err){ - * alert("node wasnt copy:" + err.data.Message); + * 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 @@ -208,9 +208,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert("node was unpulished"); * }, function(err){ - * alert("node wasnt unpublished:" + err.data.Message); + * alert("node wasnt unpublished:" + err.data.Message); * }); - * + * * @param {Int} id the ID of the node to unpublish * @returns {Promise} resourcePromise object. * @@ -242,8 +242,8 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert('its empty!'); * }); - * - * + * + * * @returns {Promise} resourcePromise object. * */ @@ -270,9 +270,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .then(function() { * alert('its gone!'); * }); - * - * - * @param {Int} id id of content item to delete + * + * + * @param {Int} id id of content item to delete * @returns {Promise} resourcePromise object. * */ @@ -308,20 +308,20 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { *
           * contentResource.getById(1234)
           *    .then(function(content) {
-          *        var myDoc = content; 
+          *        var myDoc = content;
           *        alert('its here!');
           *    });
-          * 
- * + * + * * @param {Int} id id of content item to return * @param {Object} options optional options object - * @param {Bool} options.ignoreUserStartNodes set to true to allow a user to choose nodes that they normally don't have access to + * @param {Guid} options.dataTypeId set to determine whether to allow a user to choose nodes that they normally don't have access to * @returns {Promise} resourcePromise object containing the content item. * */ getById: function (id, options) { var defaults = { - ignoreUserStartNodes: false + dataTypeId: null }; if (options === undefined) { options = {}; @@ -336,7 +336,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { umbRequestHelper.getApiUrl( "contentApiBaseUrl", "GetById", - [{ id: id }, { ignoreUserStartNodes: options.ignoreUserStartNodes }])), + [{ id: id }, { dataTypeId: options.dataTypeId }])), 'Failed to retrieve data for content id ' + id); }, @@ -385,12 +385,12 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { *
           * contentResource.getByIds( [1234,2526,28262])
           *    .then(function(contentArray) {
-          *        var myDoc = contentArray; 
+          *        var myDoc = contentArray;
           *        alert('they are here!');
           *    });
-          * 
- * - * @param {Array} ids ids of content items to return as an array + * + * + * @param {Array} ids ids of content items to return as an array * @returns {Promise} resourcePromise object containing the content items array. * */ @@ -418,28 +418,28 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @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 - * + * - 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"; 
+          *        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 + * @param {String} alias contenttype alias to base the scaffold on * @returns {Promise} resourcePromise object containing the content scaffold. * */ @@ -479,8 +479,8 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .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. * @@ -506,11 +506,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { *
           * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
           *    .then(function(contentArray) {
-          *        var children = 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 @@ -594,10 +594,10 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * .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 + * @param {Int} id id of content item to delete * @returns {Promise} resourcePromise object. * */ @@ -637,9 +637,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @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 - * - * + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * * ##usage *
           * contentResource.getById(1234)
@@ -650,11 +650,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
           *                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 + * @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. * */ @@ -679,9 +679,9 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @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 - * - * + * if the content item needs to have files attached, they must be provided as the files param and passed separately + * + * * ##usage *
           * contentResource.getById(1234)
@@ -692,11 +692,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
           *                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 + * @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. * */ @@ -715,7 +715,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @description * Saves changes made to a content item, and notifies any subscribers about a pending publication - * + * * ##usage *
           * contentResource.getById(1234)
@@ -726,11 +726,11 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
           *                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 + * @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. * */ @@ -748,15 +748,15 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * @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. * diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 4875491dc6..fff4ddde37 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -1,584 +1,584 @@ -/** - * @ngdoc service - * @name umbraco.resources.entityResource - * @description Loads in basic data for all entities - * - * ##What is an entity? - * An entity is a basic **read-only** representation of an Umbraco node. It contains only the most - * basic properties used to display the item in trees, lists and navigation. - * - * ##What is the difference between entity and content/media/etc...? - * the entity only contains the basic node data, name, id and guid, whereas content - * nodes fetched through the content service also contains additional all of the content property data, etc.. - * This is the same principal for all entity types. Any user that is logged in to the back office will have access - * to view the basic entity information for all entities since the basic entity information does not contain sensitive information. - * - * ##Entity object types? - * You need to specify the type of object you want returned. - * - * The core object types are: - * - * - Document - * - Media - * - Member - * - Template - * - DocumentType - * - MediaType - * - MemberType - * - Macro - * - User - * - Language - * - Domain - * - DataType - **/ -function entityResource($q, $http, umbRequestHelper) { - - //the factory object returned - return { - - getSafeAlias: function (value, camelCase) { - - if (!value) { - return ""; - } - value = value.replace("#", ""); - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetSafeAlias", { value: value, camelCase: camelCase })), - 'Failed to retrieve content type scaffold'); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getPath - * @methodOf umbraco.resources.entityResource - * - * @description - * Returns a path, given a node ID and type - * - * ##usage - *
-         * entityResource.getPath(id, type)
-         *    .then(function(pathArray) {
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {Int} id Id of node to return the public url to - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the url. - * - */ - getPath: function (id, type) { - - if (id === -1 || id === "-1") { - return "-1"; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetPath", - [{ id: id }, {type: type }])), - 'Failed to retrieve path for id:' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getUrl - * @methodOf umbraco.resources.entityResource - * - * @description - * Returns a url, given a node ID and type - * - * ##usage - *
-         * entityResource.getUrl(id, type)
-         *    .then(function(url) {
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {Int} id Id of node to return the public url to - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the url. - * - */ - getUrl: function (id, type) { - - if (id === -1 || id === "-1") { - return ""; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetUrl", - [{ id: id }, {type: type }])), - 'Failed to retrieve url for id:' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getById - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an entity with a given id - * - * ##usage - *
-         * //get media by id
-         * entityResource.getEntityById(0, "Media")
-         *    .then(function(ent) {
-         *        var myDoc = ent; 
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {Int} id id of entity to return - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getById: function (id, type) { - - if (id === -1 || id === "-1") { - return null; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetById", - [{ id: id }, { type: type }])), - 'Failed to retrieve entity data for id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getByIds - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an array of entities, given a collection of ids - * - * ##usage - *
-         * //Get templates for ids
-         * entityResource.getEntitiesByIds( [1234,2526,28262], "Template")
-         *    .then(function(templateArray) {
-         *        var myDoc = contentArray; 
-         *        alert('they are here!');
-         *    });
-         * 
- * - * @param {Array} ids ids of entities to return as an array - * @param {string} type type name - * @returns {Promise} resourcePromise object containing the entity array. - * - */ - getByIds: function (ids, type) { - - var query = "type=" + type; - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetByIds", - query), - { - ids: ids - }), - 'Failed to retrieve entity data for ids ' + ids); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getByQuery - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an entity from a given xpath - * - * ##usage - *
-         * //get content by xpath
-         * entityResource.getByQuery("$current", -1, "Document")
-         *    .then(function(ent) {
-         *        var myDoc = ent; 
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {string} query xpath to use in query - * @param {Int} nodeContextId id id to start from - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getByQuery: function (query, nodeContextId, type) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetByQuery", - [{ query: query }, { nodeContextId: nodeContextId }, { type: type }])), - 'Failed to retrieve entity data for query ' + query); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getAll - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an entity with a given id - * - * ##usage - *
-         *
-         * //Only return media
-         * entityResource.getAll("Media")
-         *    .then(function(ent) {
-         *        var myDoc = ent; 
-         *        alert('its here!');
-         *    });
-         * 
- * - * @param {string} type Object type name - * @param {string} postFilter optional filter expression which will execute a dynamic where clause on the server - * @param {string} postFilterParams optional parameters for the postFilter expression - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getAll: function (type, postFilter, postFilterParams) { - - //need to build the query string manually - var query = "type=" + type + "&postFilter=" + (postFilter ? postFilter : ""); - if (postFilter && postFilterParams) { - var counter = 0; - _.each(postFilterParams, function(val, key) { - query += "&postFilterParams[" + counter + "].key=" + key + "&postFilterParams[" + counter + "].value=" + val; - counter++; - }); - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetAll", - query)), - 'Failed to retrieve entity data for type ' + type); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getAncestors - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets ancestor entities for a given item - * - * - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getAncestors: function (id, type, options) { - var defaults = { - ignoreUserStartNodes: false - }; - 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; - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetAncestors", - [ - { id: id }, - { type: type }, - { ignoreUserStartNodes: options.ignoreUserStartNodes } - ])), - 'Failed to retrieve ancestor data for id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getChildren - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets children entities for a given item - * - * @param {Int} parentid id of content item to return children of - * @param {string} type Object type name - * @returns {Promise} resourcePromise object containing the entity. - * - */ - getChildren: function (id, type) { - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "GetChildren", - [{ id: id }, { type: type }])), - 'Failed to retrieve child data for id ' + id); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getPagedChildren - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets paged children of a content item with a given id - * - * ##usage - *
-          * entityResource.getPagedChildren(1234, "Content", {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 {string} type Object type name - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 1 - * @param {Int} options.pageNumber if paging data, current page index, default = 100 - * @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. - * - */ - getPagedChildren: function (parentId, type, options) { - - var defaults = { - pageSize: 1, - pageNumber: 100, - 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( - "entityApiBaseUrl", - "GetPagedChildren", - { - id: parentId, - type: type, - pageNumber: options.pageNumber, - pageSize: options.pageSize, - orderBy: options.orderBy, - orderDirection: options.orderDirection, - filter: encodeURIComponent(options.filter) - } - )), - 'Failed to retrieve child data for id ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#getPagedDescendants - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets paged descendants of a content item with a given id - * - * ##usage - *
-          * entityResource.getPagedDescendants(1234, "Document", {pageSize: 10, pageNumber: 2})
-          *    .then(function(contentArray) {
-          *        var children = contentArray; 
-          *        alert('they are here!');
-          *    });
-          * 
- * - * @param {Int} parentid id of content item to return descendants of - * @param {string} type Object type name - * @param {Object} options optional options object - * @param {Int} options.pageSize if paging data, number of nodes per page, default = 100 - * @param {Int} options.pageNumber if paging data, current page index, default = 1 - * @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. - * - */ - getPagedDescendants: function (parentId, type, options) { - - var defaults = { - pageSize: 100, - pageNumber: 1, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder", - ignoreUserStartNodes: false - }; - 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( - "entityApiBaseUrl", - "GetPagedDescendants", - { - id: parentId, - type: type, - pageNumber: options.pageNumber, - pageSize: options.pageSize, - orderBy: options.orderBy, - orderDirection: options.orderDirection, - filter: encodeURIComponent(options.filter), - ignoreUserStartNodes: options.ignoreUserStartNodes - } - )), - 'Failed to retrieve child data for id ' + parentId); - }, - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#search - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an array of entities, given a lucene query and a type - * - * ##usage - *
-         * entityResource.search("news", "Media")
-         *    .then(function(mediaArray) {
-         *        var myDoc = mediaArray; 
-         *        alert('they are here!');
-         *    });
-         * 
- * - * @param {String} Query search query - * @param {String} Type type of conten to search - * @returns {Promise} resourcePromise object containing the entity array. - * - */ - search: function (query, type, options, canceler) { - - var defaults = { - searchFrom: null, - ignoreUserStartNodes: false - }; - 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; - - var httpConfig = {}; - if (canceler) { - httpConfig["timeout"] = canceler; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "Search", - { - query: query, - type: type, - searchFrom: options.searchFrom, - ignoreUserStartNodes: options.ignoreUserStartNodes - }), - httpConfig), - 'Failed to retrieve entity data for query ' + query); - }, - - - /** - * @ngdoc method - * @name umbraco.resources.entityResource#searchAll - * @methodOf umbraco.resources.entityResource - * - * @description - * Gets an array of entities from all available search indexes, given a lucene query - * - * ##usage - *
-         * entityResource.searchAll("bob")
-         *    .then(function(array) {
-         *        var myDoc = array; 
-         *        alert('they are here!');
-         *    });
-         * 
- * - * @param {String} Query search query - * @returns {Promise} resourcePromise object containing the entity array. - * - */ - searchAll: function (query, canceler) { - - var httpConfig = {}; - if (canceler) { - httpConfig["timeout"] = canceler; - } - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - "SearchAll", - [{ query: query }]), - httpConfig), - 'Failed to retrieve entity data for query ' + query); - } - - }; -} - -angular.module('umbraco.resources').factory('entityResource', entityResource); +/** + * @ngdoc service + * @name umbraco.resources.entityResource + * @description Loads in basic data for all entities + * + * ##What is an entity? + * An entity is a basic **read-only** representation of an Umbraco node. It contains only the most + * basic properties used to display the item in trees, lists and navigation. + * + * ##What is the difference between entity and content/media/etc...? + * the entity only contains the basic node data, name, id and guid, whereas content + * nodes fetched through the content service also contains additional all of the content property data, etc.. + * This is the same principal for all entity types. Any user that is logged in to the back office will have access + * to view the basic entity information for all entities since the basic entity information does not contain sensitive information. + * + * ##Entity object types? + * You need to specify the type of object you want returned. + * + * The core object types are: + * + * - Document + * - Media + * - Member + * - Template + * - DocumentType + * - MediaType + * - MemberType + * - Macro + * - User + * - Language + * - Domain + * - DataType + **/ +function entityResource($q, $http, umbRequestHelper) { + + //the factory object returned + return { + + getSafeAlias: function (value, camelCase) { + + if (!value) { + return ""; + } + value = value.replace("#", ""); + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetSafeAlias", { value: value, camelCase: camelCase })), + 'Failed to retrieve content type scaffold'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getPath + * @methodOf umbraco.resources.entityResource + * + * @description + * Returns a path, given a node ID and type + * + * ##usage + *
+         * entityResource.getPath(id, type)
+         *    .then(function(pathArray) {
+         *        alert('its here!');
+         *    });
+         * 
+ * + * @param {Int} id Id of node to return the public url to + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the url. + * + */ + getPath: function (id, type) { + + if (id === -1 || id === "-1") { + return "-1"; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetPath", + [{ id: id }, {type: type }])), + 'Failed to retrieve path for id:' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getUrl + * @methodOf umbraco.resources.entityResource + * + * @description + * Returns a url, given a node ID and type + * + * ##usage + *
+         * entityResource.getUrl(id, type)
+         *    .then(function(url) {
+         *        alert('its here!');
+         *    });
+         * 
+ * + * @param {Int} id Id of node to return the public url to + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the url. + * + */ + getUrl: function (id, type) { + + if (id === -1 || id === "-1") { + return ""; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetUrl", + [{ id: id }, {type: type }])), + 'Failed to retrieve url for id:' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getById + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an entity with a given id + * + * ##usage + *
+         * //get media by id
+         * entityResource.getEntityById(0, "Media")
+         *    .then(function(ent) {
+         *        var myDoc = ent;
+         *        alert('its here!');
+         *    });
+         * 
+ * + * @param {Int} id id of entity to return + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getById: function (id, type) { + + if (id === -1 || id === "-1") { + return null; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetById", + [{ id: id }, { type: type }])), + 'Failed to retrieve entity data for id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getByIds + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an array of entities, given a collection of ids + * + * ##usage + *
+         * //Get templates for ids
+         * entityResource.getEntitiesByIds( [1234,2526,28262], "Template")
+         *    .then(function(templateArray) {
+         *        var myDoc = contentArray;
+         *        alert('they are here!');
+         *    });
+         * 
+ * + * @param {Array} ids ids of entities to return as an array + * @param {string} type type name + * @returns {Promise} resourcePromise object containing the entity array. + * + */ + getByIds: function (ids, type) { + + var query = "type=" + type; + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetByIds", + query), + { + ids: ids + }), + 'Failed to retrieve entity data for ids ' + ids); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getByQuery + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an entity from a given xpath + * + * ##usage + *
+         * //get content by xpath
+         * entityResource.getByQuery("$current", -1, "Document")
+         *    .then(function(ent) {
+         *        var myDoc = ent;
+         *        alert('its here!');
+         *    });
+         * 
+ * + * @param {string} query xpath to use in query + * @param {Int} nodeContextId id id to start from + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getByQuery: function (query, nodeContextId, type) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetByQuery", + [{ query: query }, { nodeContextId: nodeContextId }, { type: type }])), + 'Failed to retrieve entity data for query ' + query); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getAll + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an entity with a given id + * + * ##usage + *
+         *
+         * //Only return media
+         * entityResource.getAll("Media")
+         *    .then(function(ent) {
+         *        var myDoc = ent;
+         *        alert('its here!');
+         *    });
+         * 
+ * + * @param {string} type Object type name + * @param {string} postFilter optional filter expression which will execute a dynamic where clause on the server + * @param {string} postFilterParams optional parameters for the postFilter expression + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getAll: function (type, postFilter, postFilterParams) { + + //need to build the query string manually + var query = "type=" + type + "&postFilter=" + (postFilter ? postFilter : ""); + if (postFilter && postFilterParams) { + var counter = 0; + _.each(postFilterParams, function(val, key) { + query += "&postFilterParams[" + counter + "].key=" + key + "&postFilterParams[" + counter + "].value=" + val; + counter++; + }); + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetAll", + query)), + 'Failed to retrieve entity data for type ' + type); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getAncestors + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets ancestor entities for a given item + * + * + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getAncestors: function (id, type, options) { + var defaults = { + dataTypeId: null + }; + 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; + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetAncestors", + [ + { id: id }, + { type: type }, + { dataTypeId: options.dataTypeId } + ])), + 'Failed to retrieve ancestor data for id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getChildren + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets children entities for a given item + * + * @param {Int} parentid id of content item to return children of + * @param {string} type Object type name + * @returns {Promise} resourcePromise object containing the entity. + * + */ + getChildren: function (id, type) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetChildren", + [{ id: id }, { type: type }])), + 'Failed to retrieve child data for id ' + id); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getPagedChildren + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets paged children of a content item with a given id + * + * ##usage + *
+          * entityResource.getPagedChildren(1234, "Content", {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 {string} type Object type name + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 1 + * @param {Int} options.pageNumber if paging data, current page index, default = 100 + * @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. + * + */ + getPagedChildren: function (parentId, type, options) { + + var defaults = { + pageSize: 1, + pageNumber: 100, + 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( + "entityApiBaseUrl", + "GetPagedChildren", + { + id: parentId, + type: type, + pageNumber: options.pageNumber, + pageSize: options.pageSize, + orderBy: options.orderBy, + orderDirection: options.orderDirection, + filter: encodeURIComponent(options.filter) + } + )), + 'Failed to retrieve child data for id ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#getPagedDescendants + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets paged descendants of a content item with a given id + * + * ##usage + *
+          * entityResource.getPagedDescendants(1234, "Document", {pageSize: 10, pageNumber: 2})
+          *    .then(function(contentArray) {
+          *        var children = contentArray;
+          *        alert('they are here!');
+          *    });
+          * 
+ * + * @param {Int} parentid id of content item to return descendants of + * @param {string} type Object type name + * @param {Object} options optional options object + * @param {Int} options.pageSize if paging data, number of nodes per page, default = 100 + * @param {Int} options.pageNumber if paging data, current page index, default = 1 + * @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. + * + */ + getPagedDescendants: function (parentId, type, options) { + + var defaults = { + pageSize: 100, + pageNumber: 1, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + dataTypeId: null + }; + 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( + "entityApiBaseUrl", + "GetPagedDescendants", + { + id: parentId, + type: type, + pageNumber: options.pageNumber, + pageSize: options.pageSize, + orderBy: options.orderBy, + orderDirection: options.orderDirection, + filter: encodeURIComponent(options.filter), + dataTypeId: options.dataTypeId + } + )), + 'Failed to retrieve child data for id ' + parentId); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#search + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an array of entities, given a lucene query and a type + * + * ##usage + *
+         * entityResource.search("news", "Media")
+         *    .then(function(mediaArray) {
+         *        var myDoc = mediaArray;
+         *        alert('they are here!');
+         *    });
+         * 
+ * + * @param {String} Query search query + * @param {String} Type type of conten to search + * @returns {Promise} resourcePromise object containing the entity array. + * + */ + search: function (query, type, options, canceler) { + + var defaults = { + searchFrom: null, + dataTypeId: null + }; + 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; + + var httpConfig = {}; + if (canceler) { + httpConfig["timeout"] = canceler; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "Search", + { + query: query, + type: type, + searchFrom: options.searchFrom, + dataTypeId: options.dataTypeId + }), + httpConfig), + 'Failed to retrieve entity data for query ' + query); + }, + + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#searchAll + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an array of entities from all available search indexes, given a lucene query + * + * ##usage + *
+         * entityResource.searchAll("bob")
+         *    .then(function(array) {
+         *        var myDoc = array;
+         *        alert('they are here!');
+         *    });
+         * 
+ * + * @param {String} Query search query + * @returns {Promise} resourcePromise object containing the entity array. + * + */ + searchAll: function (query, canceler) { + + var httpConfig = {}; + if (canceler) { + httpConfig["timeout"] = canceler; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "SearchAll", + [{ query: query }]), + httpConfig), + 'Failed to retrieve entity data for query ' + query); + } + + }; +} + +angular.module('umbraco.resources').factory('entityResource', entityResource); 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 e968913047..6600aec0c7 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 @@ -1,559 +1,559 @@ -/** - * @ngdoc service - * @name umbraco.resources.mediaResource - * @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'); - }, - - /** - * @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 - }), - { - error: function(data){ - var errorMsg = 'Failed to move media'; - if (data.id !== undefined && data.parentId !== undefined) { - if (data.id === data.parentId) { - errorMsg = 'Media can\'t be moved into itself'; - } - } - else if (data.notifications !== undefined) { - if (data.notifications.length > 0) { - if (data.notifications[0].header.length > 0) { - errorMsg = data.notifications[0].header; - } - if (data.notifications[0].message.length > 0) { - errorMsg = errorMsg + ": " + data.notifications[0].message; - } - } - } - - return { - errorMsg: errorMsg - }; - } - }); - }, - - - /** - * @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#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) { - - 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); - }, - - /** - * @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); - - }, - - rootMedia: function () { - - 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) { - - var defaults = { - pageSize: 0, - pageNumber: 0, - filter: '', - orderDirection: "Ascending", - orderBy: "SortOrder", - orderBySystemField: true, - ignoreUserStartNodes: false - }; - 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( - "mediaApiBaseUrl", - "GetChildren", - [ - { id: parentId }, - { pageNumber: options.pageNumber }, - { pageSize: options.pageSize }, - { orderBy: options.orderBy }, - { orderDirection: options.orderDirection }, - { orderBySystemField: toBool(options.orderBySystemField) }, - { filter: options.filter }, - { ignoreUserStartNodes: options.ignoreUserStartNodes } - ])), - '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, - * - * NOTE: This will return a max of 500 folders, if more is required it needs to be paged - * - * ##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; - } - - //NOTE: This will return a max of 500 folders, if more is required it needs to be paged - 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#search - * @methodOf umbraco.resources.mediaResource - * - * @description - * Paginated search for media items starting on the supplied nodeId - * - * ##usage - *
-          * mediaResource.search("my search", 1, 100, -1)
-          *    .then(function(searchResult) {
-          *        alert('it's here!');
-          *    });
-          * 
- * - * @param {string} query The search query - * @param {int} pageNumber The page number - * @param {int} pageSize The number of media items on a page - * @param {int} searchFrom NodeId to search from (-1 for root) - * @returns {Promise} resourcePromise object. - * - */ - search: function (query, pageNumber, pageSize, searchFrom) { - - var args = [ - { "query": query }, - { "pageNumber": pageNumber }, - { "pageSize": pageSize }, - { "searchFrom": searchFrom } - ]; - - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "Search", - args)), - 'Failed to retrieve media items for search: ' + query); - } - - }; -} - -angular.module('umbraco.resources').factory('mediaResource', mediaResource); +/** + * @ngdoc service + * @name umbraco.resources.mediaResource + * @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'); + }, + + /** + * @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 + }), + { + error: function(data){ + var errorMsg = 'Failed to move media'; + if (data.id !== undefined && data.parentId !== undefined) { + if (data.id === data.parentId) { + errorMsg = 'Media can\'t be moved into itself'; + } + } + else if (data.notifications !== undefined) { + if (data.notifications.length > 0) { + if (data.notifications[0].header.length > 0) { + errorMsg = data.notifications[0].header; + } + if (data.notifications[0].message.length > 0) { + errorMsg = errorMsg + ": " + data.notifications[0].message; + } + } + } + + return { + errorMsg: errorMsg + }; + } + }); + }, + + + /** + * @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#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) { + + 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); + }, + + /** + * @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); + + }, + + rootMedia: function () { + + 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) { + + var defaults = { + pageSize: 0, + pageNumber: 0, + filter: '', + orderDirection: "Ascending", + orderBy: "SortOrder", + orderBySystemField: true, + dataTypeId: null + }; + 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( + "mediaApiBaseUrl", + "GetChildren", + [ + { id: parentId }, + { pageNumber: options.pageNumber }, + { pageSize: options.pageSize }, + { orderBy: options.orderBy }, + { orderDirection: options.orderDirection }, + { orderBySystemField: toBool(options.orderBySystemField) }, + { filter: options.filter }, + { dataTypeId: options.dataTypeId } + ])), + '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, + * + * NOTE: This will return a max of 500 folders, if more is required it needs to be paged + * + * ##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; + } + + //NOTE: This will return a max of 500 folders, if more is required it needs to be paged + 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#search + * @methodOf umbraco.resources.mediaResource + * + * @description + * Paginated search for media items starting on the supplied nodeId + * + * ##usage + *
+          * mediaResource.search("my search", 1, 100, -1)
+          *    .then(function(searchResult) {
+          *        alert('it's here!');
+          *    });
+          * 
+ * + * @param {string} query The search query + * @param {int} pageNumber The page number + * @param {int} pageSize The number of media items on a page + * @param {int} searchFrom NodeId to search from (-1 for root) + * @returns {Promise} resourcePromise object. + * + */ + search: function (query, pageNumber, pageSize, searchFrom) { + + var args = [ + { "query": query }, + { "pageNumber": pageNumber }, + { "pageSize": pageSize }, + { "searchFrom": searchFrom } + ]; + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "Search", + args)), + 'Failed to retrieve media items for search: ' + query); + } + + }; +} + +angular.module('umbraco.resources').factory('mediaResource', mediaResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js index 0d00678282..24b946cdc8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js @@ -1,174 +1,174 @@ -/** - * @ngdoc service - * @name umbraco.services.searchService - * - * - * @description - * Service for handling the main application search, can currently search content, media and members - * - * ##usage - * To use, simply inject the searchService into any controller that needs it, and make - * sure the umbraco.services module is accesible - which it should be by default. - * - *
- *      searchService.searchMembers({term: 'bob'}).then(function(results){
- *          angular.forEach(results, function(result){
- *                  //returns:
- *                  {name: "name", id: 1234, menuUrl: "url", editorPath: "url", metaData: {}, subtitle: "/path/etc" }
- *           })          
- *           var result = 
- *       }) 
- * 
- */ -angular.module('umbraco.services') - .factory('searchService', function ($q, $log, entityResource, contentResource, umbRequestHelper, $injector, searchResultFormatter) { - - return { - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchMembers - * @methodOf umbraco.services.searchService - * - * @description - * Searches the default member search index - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching members - */ - searchMembers: function (args) { - - if (!args.term) { - throw "args.term is required"; - } - - var options = { - searchFrom: args.searchFrom - } - - return entityResource.search(args.term, "Member", options).then(function (data) { - _.each(data, function (item) { - searchResultFormatter.configureMemberResult(item); - }); - return data; - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchContent - * @methodOf umbraco.services.searchService - * - * @description - * Searches the default internal content search index - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching content items - */ - searchContent: function (args) { - - if (!args.term) { - throw "args.term is required"; - } - - var options = { - searchFrom: args.searchFrom, - ignoreUserStartNodes: args.ignoreUserStartNodes - } - - return entityResource.search(args.term, "Document", options, args.canceler).then(function (data) { - _.each(data, function (item) { - searchResultFormatter.configureContentResult(item); - }); - return data; - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchMedia - * @methodOf umbraco.services.searchService - * - * @description - * Searches the default media search index - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching media items - */ - searchMedia: function (args) { - - if (!args.term) { - throw "args.term is required"; - } - - var options = { - searchFrom: args.searchFrom, - ignoreUserStartNodes: args.ignoreUserStartNodes - } - - return entityResource.search(args.term, "Media", options).then(function (data) { - _.each(data, function (item) { - searchResultFormatter.configureMediaResult(item); - }); - return data; - }); - }, - - /** - * @ngdoc method - * @name umbraco.services.searchService#searchAll - * @methodOf umbraco.services.searchService - * - * @description - * Searches all available indexes and returns all results in one collection - * @param {Object} args argument object - * @param {String} args.term seach term - * @returns {Promise} returns promise containing all matching items - */ - searchAll: function (args) { - - if (!args.term) { - throw "args.term is required"; - } - - return entityResource.searchAll(args.term, args.canceler).then(function (data) { - - _.each(data, function (resultByType) { +/** + * @ngdoc service + * @name umbraco.services.searchService + * + * + * @description + * Service for handling the main application search, can currently search content, media and members + * + * ##usage + * To use, simply inject the searchService into any controller that needs it, and make + * sure the umbraco.services module is accesible - which it should be by default. + * + *
+ *      searchService.searchMembers({term: 'bob'}).then(function(results){
+ *          angular.forEach(results, function(result){
+ *                  //returns:
+ *                  {name: "name", id: 1234, menuUrl: "url", editorPath: "url", metaData: {}, subtitle: "/path/etc" }
+ *           })
+ *           var result =
+ *       })
+ * 
+ */ +angular.module('umbraco.services') + .factory('searchService', function ($q, $log, entityResource, contentResource, umbRequestHelper, $injector, searchResultFormatter) { + + return { + + /** + * @ngdoc method + * @name umbraco.services.searchService#searchMembers + * @methodOf umbraco.services.searchService + * + * @description + * Searches the default member search index + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching members + */ + searchMembers: function (args) { + + if (!args.term) { + throw "args.term is required"; + } + + var options = { + searchFrom: args.searchFrom + } + + return entityResource.search(args.term, "Member", options).then(function (data) { + _.each(data, function (item) { + searchResultFormatter.configureMemberResult(item); + }); + return data; + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.searchService#searchContent + * @methodOf umbraco.services.searchService + * + * @description + * Searches the default internal content search index + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching content items + */ + searchContent: function (args) { + + if (!args.term) { + throw "args.term is required"; + } + + var options = { + searchFrom: args.searchFrom, + dataTypeId: args.dataTypeId + } + + return entityResource.search(args.term, "Document", options, args.canceler).then(function (data) { + _.each(data, function (item) { + searchResultFormatter.configureContentResult(item); + }); + return data; + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.searchService#searchMedia + * @methodOf umbraco.services.searchService + * + * @description + * Searches the default media search index + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching media items + */ + searchMedia: function (args) { + + if (!args.term) { + throw "args.term is required"; + } + + var options = { + searchFrom: args.searchFrom, + dataTypeId: args.dataTypeId + } + + return entityResource.search(args.term, "Media", options).then(function (data) { + _.each(data, function (item) { + searchResultFormatter.configureMediaResult(item); + }); + return data; + }); + }, + + /** + * @ngdoc method + * @name umbraco.services.searchService#searchAll + * @methodOf umbraco.services.searchService + * + * @description + * Searches all available indexes and returns all results in one collection + * @param {Object} args argument object + * @param {String} args.term seach term + * @returns {Promise} returns promise containing all matching items + */ + searchAll: function (args) { + + if (!args.term) { + throw "args.term is required"; + } + + return entityResource.searchAll(args.term, args.canceler).then(function (data) { + + _.each(data, function (resultByType) { //we need to format the search result data to include things like the subtitle, urls, etc... - // this is done with registered angular services as part of the SearchableTreeAttribute, if that - // is not found, than we format with the default formatter + // this is done with registered angular services as part of the SearchableTreeAttribute, if that + // is not found, than we format with the default formatter var formatterMethod = searchResultFormatter.configureDefaultResult; //check if a custom formatter is specified... - if (resultByType.jsSvc) { - var searchFormatterService = $injector.get(resultByType.jsSvc); - if (searchFormatterService) { - if (!resultByType.jsMethod) { - resultByType.jsMethod = "format"; - } - formatterMethod = searchFormatterService[resultByType.jsMethod]; - - if (!formatterMethod) { - throw "The method " + resultByType.jsMethod + " on the angular service " + resultByType.jsSvc + " could not be found"; - } - } - } - //now apply the formatter for each result - _.each(resultByType.results, function (item) { - formatterMethod.apply(this, [item, resultByType.treeAlias, resultByType.appAlias]); + if (resultByType.jsSvc) { + var searchFormatterService = $injector.get(resultByType.jsSvc); + if (searchFormatterService) { + if (!resultByType.jsMethod) { + resultByType.jsMethod = "format"; + } + formatterMethod = searchFormatterService[resultByType.jsMethod]; + + if (!formatterMethod) { + throw "The method " + resultByType.jsMethod + " on the angular service " + resultByType.jsSvc + " could not be found"; + } + } + } + //now apply the formatter for each result + _.each(resultByType.results, function (item) { + formatterMethod.apply(this, [item, resultByType.treeAlias, resultByType.appAlias]); }); - - }); - - return data; - }); - - }, - - //TODO: This doesn't do anything! - setCurrent: function (sectionAlias) { - - var currentSection = sectionAlias; - } - }; - }); + + }); + + return data; + }); + + }, + + //TODO: This doesn't do anything! + setCurrent: function (sectionAlias) { + + var currentSection = sectionAlias; + } + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js index 5d6a3bf12c..67d62de223 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js @@ -1,170 +1,172 @@ -//used for the media picker dialog -angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", - function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) { - var dialogOptions = $scope.dialogOptions; - - var searchText = "Search..."; - localizationService.localize("general_search").then(function (value) { - searchText = value + "..."; - }); - - $scope.dialogTreeEventHandler = $({}); - $scope.target = {}; - $scope.searchInfo = { - searchFromId: null, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - } - - if (dialogOptions.currentTarget) { - $scope.target = dialogOptions.currentTarget; - - //if we have a node ID, we fetch the current node to build the form data - if ($scope.target.id || $scope.target.udi) { - - var id = $scope.target.udi ? $scope.target.udi : $scope.target.id; - - if (!$scope.target.path) { - entityResource.getPath(id, "Document").then(function (path) { - $scope.target.path = path; - //now sync the tree to this path - $scope.dialogTreeEventHandler.syncTree({ - path: $scope.target.path, - tree: "content" - }); - }); - } - - // if a link exists, get the properties to build the anchor name list - contentResource.getById(id).then(function (resp) { - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - $scope.target.url = resp.urls[0]; - }); - } else if ($scope.target.url.length) { +//used for the media picker dialog +angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", + function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) { + var dialogOptions = $scope.dialogOptions; + + var searchText = "Search..."; + localizationService.localize("general_search").then(function (value) { + searchText = value + "..."; + }); + + debugger; + $scope.dialogTreeEventHandler = $({}); + $scope.target = {}; + $scope.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + dataTypeId: $scope.model.dataTypeId, + results: [], + selectedSearchResults: [] + } + + if (dialogOptions.currentTarget) { + $scope.target = dialogOptions.currentTarget; + + //if we have a node ID, we fetch the current node to build the form data + if ($scope.target.id || $scope.target.udi) { + + var id = $scope.target.udi ? $scope.target.udi : $scope.target.id; + + if (!$scope.target.path) { + entityResource.getPath(id, "Document").then(function (path) { + $scope.target.path = path; + //now sync the tree to this path + $scope.dialogTreeEventHandler.syncTree({ + path: $scope.target.path, + tree: "content" + }); + }); + } + + // if a link exists, get the properties to build the anchor name list + contentResource.getById(id).then(function (resp) { + $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); + $scope.target.url = resp.urls[0]; + }); + } else if ($scope.target.url.length) { // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs // only do the substring if there's a # or a ? var indexOfAnchor = $scope.target.url.search(/(#|\?)/); - if (indexOfAnchor > -1) { - // populate the anchor - $scope.target.anchor = $scope.target.url.substring(indexOfAnchor); - // then rewrite the model and populate the link + if (indexOfAnchor > -1) { + // populate the anchor + $scope.target.anchor = $scope.target.url.substring(indexOfAnchor); + // then rewrite the model and populate the link $scope.target.url = $scope.target.url.substring(0, indexOfAnchor); - } - } - } - - if (dialogOptions.anchors) { - $scope.anchorValues = dialogOptions.anchors; - } - - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if (args.node.metaData.listViewNode) { - //check if list view 'search' node was selected - - $scope.searchInfo.showSearch = true; - $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; - $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; - } else { - eventsService.emit("dialogs.linkPicker.select", args); - - if ($scope.currentNode) { - //un-select if there's a current one selected - $scope.currentNode.selected = false; - } - - $scope.currentNode = args.node; - $scope.currentNode.selected = true; - $scope.target.id = args.node.id; - $scope.target.udi = args.node.udi; - $scope.target.name = args.node.name; - - if (args.node.id < 0) { - $scope.target.url = "/"; - } else { - contentResource.getById(args.node.id).then(function (resp) { - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - $scope.target.url = resp.urls[0]; - }); - } - - if (!angular.isUndefined($scope.target.isMedia)) { - delete $scope.target.isMedia; - } - } - } - - function nodeExpandedHandler(ev, args) { - if (angular.isArray(args.children)) { - - //iterate children - _.each(args.children, function (child) { - //check if any of the items are list views, if so we need to add a custom - // child: A node to activate the search - if (child.metaData.isContainer) { - child.hasChildren = true; - child.children = [ - { - level: child.level + 1, - hasChildren: false, - name: searchText, - metaData: { - listViewNode: child - }, - cssClass: "icon umb-tree-icon sprTree icon-search", - cssClasses: ["not-published"] - } - ]; - } - }); - } - } - - $scope.switchToMediaPicker = function () { - userService.getCurrentUser().then(function (userData) { - dialogService.mediaPicker({ - startNodeId: userData.startMediaIds.length == 0 ? -1 : userData.startMediaIds[0], - callback: function (media) { - $scope.target.id = media.id; - $scope.target.isMedia = true; - $scope.target.name = media.name; - $scope.target.url = mediaHelper.resolveFile(media); - } - }); - }); - }; - - $scope.hideSearch = function () { - $scope.searchInfo.showSearch = false; - $scope.searchInfo.searchFromId = null; - $scope.searchInfo.searchFromName = null; - $scope.searchInfo.results = []; - } - - // method to select a search result - $scope.selectResult = function (evt, result) { - result.selected = result.selected === true ? false : true; - nodeSelectHandler(evt, { - event: evt, - node: result - }); - }; - - //callback when there are search results - $scope.onSearchResults = function (results) { - $scope.searchInfo.results = results; - $scope.searchInfo.showSearch = true; - }; - - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - }); - }); + } + } + } + + if (dialogOptions.anchors) { + $scope.anchorValues = dialogOptions.anchors; + } + + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if (args.node.metaData.listViewNode) { + //check if list view 'search' node was selected + + $scope.searchInfo.showSearch = true; + $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; + $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; + } else { + eventsService.emit("dialogs.linkPicker.select", args); + + if ($scope.currentNode) { + //un-select if there's a current one selected + $scope.currentNode.selected = false; + } + + $scope.currentNode = args.node; + $scope.currentNode.selected = true; + $scope.target.id = args.node.id; + $scope.target.udi = args.node.udi; + $scope.target.name = args.node.name; + + if (args.node.id < 0) { + $scope.target.url = "/"; + } else { + contentResource.getById(args.node.id).then(function (resp) { + $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); + $scope.target.url = resp.urls[0]; + }); + } + + if (!angular.isUndefined($scope.target.isMedia)) { + delete $scope.target.isMedia; + } + } + } + + function nodeExpandedHandler(ev, args) { + if (angular.isArray(args.children)) { + + //iterate children + _.each(args.children, function (child) { + //check if any of the items are list views, if so we need to add a custom + // child: A node to activate the search + if (child.metaData.isContainer) { + child.hasChildren = true; + child.children = [ + { + level: child.level + 1, + hasChildren: false, + name: searchText, + metaData: { + listViewNode: child + }, + cssClass: "icon umb-tree-icon sprTree icon-search", + cssClasses: ["not-published"] + } + ]; + } + }); + } + } + + $scope.switchToMediaPicker = function () { + userService.getCurrentUser().then(function (userData) { + dialogService.mediaPicker({ + startNodeId: userData.startMediaIds.length == 0 ? -1 : userData.startMediaIds[0], + callback: function (media) { + $scope.target.id = media.id; + $scope.target.isMedia = true; + $scope.target.name = media.name; + $scope.target.url = mediaHelper.resolveFile(media); + } + }); + }); + }; + + $scope.hideSearch = function () { + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = null; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + } + + // method to select a search result + $scope.selectResult = function (evt, result) { + result.selected = result.selected === true ? false : true; + nodeSelectHandler(evt, { + event: evt, + node: result + }); + }; + + //callback when there are search results + $scope.onSearchResults = function (results) { + $scope.searchInfo.results = results; + $scope.searchInfo.showSearch = true; + }; + + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + }); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html index 3b02853127..c4ac0c2e93 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html @@ -1,86 +1,87 @@ -
- - - -
+
+ + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js index 844ee6e240..539cb418c4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js @@ -1,431 +1,430 @@ -//used for the media picker dialog -angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController", - function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService) { - - var tree = null; - var dialogOptions = $scope.dialogOptions; - $scope.dialogTreeEventHandler = $({}); - $scope.section = dialogOptions.section; - $scope.treeAlias = dialogOptions.treeAlias; - $scope.multiPicker = dialogOptions.multiPicker; - $scope.hideHeader = (typeof dialogOptions.hideHeader) === "boolean" ? dialogOptions.hideHeader : true;; - $scope.searchInfo = { - searchFromId: dialogOptions.startNodeId, - searchFromName: null, - showSearch: false, - results: [], - selectedSearchResults: [] - } - - //create the custom query string param for this tree - $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : ""; - $scope.customTreeParams += dialogOptions.customTreeParams ? "&" + dialogOptions.customTreeParams : ""; - - var searchText = "Search..."; - localizationService.localize("general_search").then(function (value) { - searchText = value + "..."; - }); - - // Allow the entity type to be passed in but defaults to Document for backwards compatibility. - var entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document"; - - - //min / max values - if (dialogOptions.minNumber) { - dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10); - } - if (dialogOptions.maxNumber) { - dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10); - } - - if (dialogOptions.section === "member") { - entityType = "Member"; - } - else if (dialogOptions.section === "media") { - entityType = "Media"; - } - - //Configures filtering - if (dialogOptions.filter) { - - dialogOptions.filterExclude = false; - dialogOptions.filterAdvanced = false; - - //used advanced filtering - if (angular.isFunction(dialogOptions.filter)) { - dialogOptions.filterAdvanced = true; - } - else if (angular.isObject(dialogOptions.filter)) { - dialogOptions.filterAdvanced = true; - } - else { - if (dialogOptions.filter.startsWith("!")) { - dialogOptions.filterExclude = true; - dialogOptions.filter = dialogOptions.filter.substring(1); - } - - //used advanced filtering - if (dialogOptions.filter.startsWith("{")) { - dialogOptions.filterAdvanced = true; - //convert to object - dialogOptions.filter = angular.fromJson(dialogOptions.filter); - } - } - } - - function nodeExpandedHandler(ev, args) { - if (angular.isArray(args.children)) { - - //iterate children - _.each(args.children, function (child) { - - //check if any of the items are list views, if so we need to add some custom - // children: A node to activate the search, any nodes that have already been - // selected in the search - if (child.metaData.isContainer) { - child.hasChildren = true; - child.children = [ - { - level: child.level + 1, - hasChildren: false, - parent: function () { - return child; - }, - name: searchText, - metaData: { - listViewNode: child - }, - cssClass: "icon-search", - cssClasses: ["not-published"] - } - ]; - //add base transition classes to this node - child.cssClasses.push("tree-node-slide-up"); - - var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function(i) { - return i.parentId == child.id; - }); - _.each(listViewResults, function(item) { - child.children.unshift({ - id: item.id, - name: item.name, - cssClass: "icon umb-tree-icon sprTree " + item.icon, - level: child.level + 1, - metaData: { - isSearchResult: true - }, - hasChildren: false, - parent: function () { - return child; - } - }); - }); - } - - //now we need to look in the already selected search results and - // toggle the check boxes for those ones that are listed - var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { - return child.id == selected.id; - }); - if (exists) { - child.selected = true; - } - }); - - //check filter - performFiltering(args.children); - } - } - - //gets the tree object when it loads - function treeLoadedHandler(ev, args) { - tree = args.tree; - } - - //wires up selection - function nodeSelectHandler(ev, args) { - args.event.preventDefault(); - args.event.stopPropagation(); - - if (args.node.metaData.listViewNode) { - //check if list view 'search' node was selected - - $scope.searchInfo.showSearch = true; - $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; - $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; - - //add transition classes - var listViewNode = args.node.parent(); - listViewNode.cssClasses.push('tree-node-slide-up-hide-active'); - } - else if (args.node.metaData.isSearchResult) { - //check if the item selected was a search result from a list view - - //unselect - select(args.node.name, args.node.id); - - //remove it from the list view children - var listView = args.node.parent(); - listView.children = _.reject(listView.children, function(child) { - return child.id == args.node.id; - }); - - //remove it from the custom tracked search result list - $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { - return i.id == args.node.id; - }); - } - else { - eventsService.emit("dialogs.treePickerController.select", args); - - if (args.node.filtered) { - return; - } - - //This is a tree node, so we don't have an entity to pass in, it will need to be looked up - //from the server in this method. - select(args.node.name, args.node.id); - - //toggle checked state - args.node.selected = args.node.selected === true ? false : true; - } - } - - /** Method used for selecting a node */ - function select(text, id, entity) { - //if we get the root, we just return a constructed entity, no need for server data - if (id < 0) { - if ($scope.multiPicker) { - $scope.select(id); - } - else { - var node = { - alias: null, - icon: "icon-folder", - id: id, - name: text - }; - $scope.submit(node); - } - } - else { - - if ($scope.multiPicker) { - $scope.select(Number(id)); - } - else { - - $scope.hideSearch(); - - //if an entity has been passed in, use it - if (entity) { - $scope.submit(entity); - } else { - //otherwise we have to get it from the server - entityResource.getById(id, entityType).then(function (ent) { - $scope.submit(ent); - }); - } - } - } - } - - function performFiltering(nodes) { - - if (!dialogOptions.filter) { - return; - } - - //remove any list view search nodes from being filtered since these are special nodes that always must - // be allowed to be clicked on - nodes = _.filter(nodes, function(n) { - return !angular.isObject(n.metaData.listViewNode); - }); - - if (dialogOptions.filterAdvanced) { - - //filter either based on a method or an object - var filtered = angular.isFunction(dialogOptions.filter) - ? _.filter(nodes, dialogOptions.filter) - : _.where(nodes, dialogOptions.filter); - - angular.forEach(filtered, function (value, key) { - value.filtered = true; - if (dialogOptions.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push(dialogOptions.filterCssClass); - } - }); - } else { - var a = dialogOptions.filter.toLowerCase().replace(/\s/g, '').split(','); - angular.forEach(nodes, function (value, key) { - - var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; - - if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { - value.filtered = true; - - if (dialogOptions.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push(dialogOptions.filterCssClass); - } - } - }); - } - } - - $scope.multiSubmit = function (result) { - entityResource.getByIds(result, entityType).then(function (ents) { - $scope.submit(ents); - }); - }; - - /** method to select a search result */ - $scope.selectResult = function (evt, result) { - - if (result.filtered) { - return; - } - - result.selected = result.selected === true ? false : true; - - //since result = an entity, we'll pass it in so we don't have to go back to the server - select(result.name, result.id, result); - - //add/remove to our custom tracked list of selected search results - if (result.selected) { - $scope.searchInfo.selectedSearchResults.push(result); - } - else { - $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function(i) { - return i.id == result.id; - }); - } - - //ensure the tree node in the tree is checked/unchecked if it already exists there - if (tree) { - var found = treeService.getDescendantNode(tree.root, result.id); - if (found) { - found.selected = result.selected; - } - } - - }; - - $scope.hideSearch = function () { - - //Traverse the entire displayed tree and update each node to sync with the selected search results - if (tree) { - - //we need to ensure that any currently displayed nodes that get selected - // from the search get updated to have a check box! - function checkChildren(children) { - _.each(children, function (child) { - //check if the id is in the selection, if so ensure it's flagged as selected - var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { - return child.id == selected.id; - }); - //if the curr node exists in selected search results, ensure it's checked - if (exists) { - child.selected = true; - } - //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result - else if (child.metaData.isSearchResult) { - //if this tree node is under a list view it means that the node was added - // to the tree dynamically under the list view that was searched, so we actually want to remove - // it all together from the tree - var listView = child.parent(); - listView.children = _.reject(listView.children, function(c) { - return c.id == child.id; - }); - } - - //check if the current node is a list view and if so, check if there's any new results - // that need to be added as child nodes to it based on search results selected - if (child.metaData.isContainer) { - - child.cssClasses = _.reject(child.cssClasses, function(c) { - return c === 'tree-node-slide-up-hide-active'; - }); - - var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) { - return i.parentId == child.id; - }); - _.each(listViewResults, function (item) { - var childExists = _.find(child.children, function(c) { - return c.id == item.id; - }); - if (!childExists) { - var parent = child; - child.children.unshift({ - id: item.id, - name: item.name, - cssClass: "icon umb-tree-icon sprTree " + item.icon, - level: child.level + 1, - metaData: { - isSearchResult: true - }, - hasChildren: false, - parent: function () { - return parent; - } - }); - } - }); - } - - //recurse - if (child.children && child.children.length > 0) { - checkChildren(child.children); - } - }); - } - checkChildren(tree.root.children); - } - - - $scope.searchInfo.showSearch = false; - $scope.searchInfo.searchFromId = dialogOptions.startNodeId; - $scope.searchInfo.searchFromName = null; - $scope.searchInfo.results = []; - } - - $scope.onSearchResults = function(results) { - - //filter all items - this will mark an item as filtered - performFiltering(results); - - //now actually remove all filtered items so they are not even displayed - results = _.filter(results, function(item) { - return !item.filtered; - }); - - $scope.searchInfo.results = results; - - //sync with the curr selected results - _.each($scope.searchInfo.results, function (result) { - var exists = _.find($scope.dialogData.selection, function (selectedId) { - return result.id == selectedId; - }); - if (exists) { - result.selected = true; - } - }); - - $scope.searchInfo.showSearch = true; - }; - - $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - }); - }); +//used for the media picker dialog +angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController", + function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService) { + + var tree = null; + var dialogOptions = $scope.dialogOptions; + $scope.dialogTreeEventHandler = $({}); + $scope.section = dialogOptions.section; + $scope.treeAlias = dialogOptions.treeAlias; + $scope.multiPicker = dialogOptions.multiPicker; + $scope.hideHeader = (typeof dialogOptions.hideHeader) === "boolean" ? dialogOptions.hideHeader : true;; + $scope.searchInfo = { + searchFromId: dialogOptions.startNodeId, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + } + + //create the custom query string param for this tree + $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : ""; + $scope.customTreeParams += dialogOptions.customTreeParams ? "&" + dialogOptions.customTreeParams : ""; + + var searchText = "Search..."; + localizationService.localize("general_search").then(function (value) { + searchText = value + "..."; + }); + + // Allow the entity type to be passed in but defaults to Document for backwards compatibility. + var entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document"; + + + //min / max values + if (dialogOptions.minNumber) { + dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10); + } + if (dialogOptions.maxNumber) { + dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10); + } + + if (dialogOptions.section === "member") { + entityType = "Member"; + } + else if (dialogOptions.section === "media") { + entityType = "Media"; + } + + //Configures filtering + if (dialogOptions.filter) { + + dialogOptions.filterExclude = false; + dialogOptions.filterAdvanced = false; + + //used advanced filtering + if (angular.isFunction(dialogOptions.filter)) { + dialogOptions.filterAdvanced = true; + } + else if (angular.isObject(dialogOptions.filter)) { + dialogOptions.filterAdvanced = true; + } + else { + if (dialogOptions.filter.startsWith("!")) { + dialogOptions.filterExclude = true; + dialogOptions.filter = dialogOptions.filter.substring(1); + } + + //used advanced filtering + if (dialogOptions.filter.startsWith("{")) { + dialogOptions.filterAdvanced = true; + //convert to object + dialogOptions.filter = angular.fromJson(dialogOptions.filter); + } + } + } + + function nodeExpandedHandler(ev, args) { + if (angular.isArray(args.children)) { + + //iterate children + _.each(args.children, function (child) { + + //check if any of the items are list views, if so we need to add some custom + // children: A node to activate the search, any nodes that have already been + // selected in the search + if (child.metaData.isContainer) { + child.hasChildren = true; + child.children = [ + { + level: child.level + 1, + hasChildren: false, + parent: function () { + return child; + }, + name: searchText, + metaData: { + listViewNode: child + }, + cssClass: "icon-search", + cssClasses: ["not-published"] + } + ]; + //add base transition classes to this node + child.cssClasses.push("tree-node-slide-up"); + + var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function(i) { + return i.parentId == child.id; + }); + _.each(listViewResults, function(item) { + child.children.unshift({ + id: item.id, + name: item.name, + cssClass: "icon umb-tree-icon sprTree " + item.icon, + level: child.level + 1, + metaData: { + isSearchResult: true + }, + hasChildren: false, + parent: function () { + return child; + } + }); + }); + } + + //now we need to look in the already selected search results and + // toggle the check boxes for those ones that are listed + var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { + return child.id == selected.id; + }); + if (exists) { + child.selected = true; + } + }); + + //check filter + performFiltering(args.children); + } + } + + //gets the tree object when it loads + function treeLoadedHandler(ev, args) { + tree = args.tree; + } + + //wires up selection + function nodeSelectHandler(ev, args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if (args.node.metaData.listViewNode) { + //check if list view 'search' node was selected + + $scope.searchInfo.showSearch = true; + $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id; + $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name; + + //add transition classes + var listViewNode = args.node.parent(); + listViewNode.cssClasses.push('tree-node-slide-up-hide-active'); + } + else if (args.node.metaData.isSearchResult) { + //check if the item selected was a search result from a list view + + //unselect + select(args.node.name, args.node.id); + + //remove it from the list view children + var listView = args.node.parent(); + listView.children = _.reject(listView.children, function(child) { + return child.id == args.node.id; + }); + + //remove it from the custom tracked search result list + $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) { + return i.id == args.node.id; + }); + } + else { + eventsService.emit("dialogs.treePickerController.select", args); + + if (args.node.filtered) { + return; + } + + //This is a tree node, so we don't have an entity to pass in, it will need to be looked up + //from the server in this method. + select(args.node.name, args.node.id); + + //toggle checked state + args.node.selected = args.node.selected === true ? false : true; + } + } + + /** Method used for selecting a node */ + function select(text, id, entity) { + //if we get the root, we just return a constructed entity, no need for server data + if (id < 0) { + if ($scope.multiPicker) { + $scope.select(id); + } + else { + var node = { + alias: null, + icon: "icon-folder", + id: id, + name: text + }; + $scope.submit(node); + } + } + else { + + if ($scope.multiPicker) { + $scope.select(Number(id)); + } + else { + + $scope.hideSearch(); + + //if an entity has been passed in, use it + if (entity) { + $scope.submit(entity); + } else { + //otherwise we have to get it from the server + entityResource.getById(id, entityType).then(function (ent) { + $scope.submit(ent); + }); + } + } + } + } + + function performFiltering(nodes) { + + if (!dialogOptions.filter) { + return; + } + + //remove any list view search nodes from being filtered since these are special nodes that always must + // be allowed to be clicked on + nodes = _.filter(nodes, function(n) { + return !angular.isObject(n.metaData.listViewNode); + }); + + if (dialogOptions.filterAdvanced) { + + //filter either based on a method or an object + var filtered = angular.isFunction(dialogOptions.filter) + ? _.filter(nodes, dialogOptions.filter) + : _.where(nodes, dialogOptions.filter); + + angular.forEach(filtered, function (value, key) { + value.filtered = true; + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + }); + } else { + var a = dialogOptions.filter.toLowerCase().replace(/\s/g, '').split(','); + angular.forEach(nodes, function (value, key) { + + var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; + + if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { + value.filtered = true; + + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + } + }); + } + } + + $scope.multiSubmit = function (result) { + entityResource.getByIds(result, entityType).then(function (ents) { + $scope.submit(ents); + }); + }; + + /** method to select a search result */ + $scope.selectResult = function (evt, result) { + + if (result.filtered) { + return; + } + + result.selected = result.selected === true ? false : true; + + //since result = an entity, we'll pass it in so we don't have to go back to the server + select(result.name, result.id, result); + + //add/remove to our custom tracked list of selected search results + if (result.selected) { + $scope.searchInfo.selectedSearchResults.push(result); + } + else { + $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function(i) { + return i.id == result.id; + }); + } + + //ensure the tree node in the tree is checked/unchecked if it already exists there + if (tree) { + var found = treeService.getDescendantNode(tree.root, result.id); + if (found) { + found.selected = result.selected; + } + } + + }; + + $scope.hideSearch = function () { + + //Traverse the entire displayed tree and update each node to sync with the selected search results + if (tree) { + + //we need to ensure that any currently displayed nodes that get selected + // from the search get updated to have a check box! + function checkChildren(children) { + _.each(children, function (child) { + //check if the id is in the selection, if so ensure it's flagged as selected + var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) { + return child.id == selected.id; + }); + //if the curr node exists in selected search results, ensure it's checked + if (exists) { + child.selected = true; + } + //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result + else if (child.metaData.isSearchResult) { + //if this tree node is under a list view it means that the node was added + // to the tree dynamically under the list view that was searched, so we actually want to remove + // it all together from the tree + var listView = child.parent(); + listView.children = _.reject(listView.children, function(c) { + return c.id == child.id; + }); + } + + //check if the current node is a list view and if so, check if there's any new results + // that need to be added as child nodes to it based on search results selected + if (child.metaData.isContainer) { + + child.cssClasses = _.reject(child.cssClasses, function(c) { + return c === 'tree-node-slide-up-hide-active'; + }); + + var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) { + return i.parentId == child.id; + }); + _.each(listViewResults, function (item) { + var childExists = _.find(child.children, function(c) { + return c.id == item.id; + }); + if (!childExists) { + var parent = child; + child.children.unshift({ + id: item.id, + name: item.name, + cssClass: "icon umb-tree-icon sprTree " + item.icon, + level: child.level + 1, + metaData: { + isSearchResult: true + }, + hasChildren: false, + parent: function () { + return parent; + } + }); + } + }); + } + + //recurse + if (child.children && child.children.length > 0) { + checkChildren(child.children); + } + }); + } + checkChildren(tree.root.children); + } + + + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = dialogOptions.startNodeId; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + } + + $scope.onSearchResults = function(results) {; + //filter all items - this will mark an item as filtered + performFiltering(results); + + //now actually remove all filtered items so they are not even displayed + results = _.filter(results, function(item) { + return !item.filtered; + }); + + $scope.searchInfo.results = results; + + //sync with the curr selected results + _.each($scope.searchInfo.results, function (result) { + var exists = _.find($scope.dialogData.selection, function (selectedId) { + return result.id == selectedId; + }); + if (exists) { + result.selected = true; + } + }); + + $scope.searchInfo.showSearch = true; + }; + + $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + }); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html index 4391e50c28..14bcf58f5a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html @@ -9,7 +9,7 @@ search-from-id="{{searchInfo.searchFromId}}" search-from-name="{{searchInfo.searchFromName}}" show-search="{{searchInfo.showSearch}}" - ignore-user-startnodes="{{searchInfo.ignoreUserStartNodes}}" + datatype-id="{{searchInfo.dataTypeId}}" section="content"> diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js index 79b9362d3f..8a1c3eb9a9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js @@ -18,11 +18,11 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", searchFromId: null, searchFromName: null, showSearch: false, - ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + dataTypeId: dialogOptions.dataTypeId, results: [], selectedSearchResults: [] }; - $scope.customTreeParams = dialogOptions.ignoreUserStartNodes ? "ignoreUserStartNodes=" + dialogOptions.ignoreUserStartNodes : ""; + $scope.customTreeParams = dialogOptions.dataTypeId ? "dataTypeId=" + dialogOptions.dataTypeId : ""; $scope.showTarget = $scope.model.hideTarget !== true; if (dialogOptions.currentTarget) { @@ -46,7 +46,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", }); // if a link exists, get the properties to build the anchor name list - contentResource.getById(id, { ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes }).then(function (resp) { + contentResource.getById(id, { dataTypeId: dialogOptions.dataTypeId }).then(function (resp) { $scope.model.target.url = resp.urls[0]; $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); }); @@ -88,7 +88,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", if (args.node.id < 0) { $scope.model.target.url = "/"; } else { - contentResource.getById(args.node.id, { ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes }).then(function (resp) { + contentResource.getById(args.node.id, { dataTypeId: dialogOptions.dataTypeId }).then(function (resp) { $scope.model.target.url = resp.urls[0]; $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); }); @@ -120,7 +120,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", startNodeId: startNodeId, startNodeIsVirtual: startNodeIsVirtual, show: true, - ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + dataTypeId: dialogOptions.dataTypeId, submit: function (model) { var media = model.selectedImages[0]; @@ -133,7 +133,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", $scope.mediaPickerOverlay.show = false; $scope.mediaPickerOverlay = null; - // make sure the content tree has nothing highlighted + // make sure the content tree has nothing highlighted $scope.dialogTreeEventHandler.syncTree({ path: "-1", tree: "content" diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html index c9f3ad54a7..4cc7b0fd46 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html @@ -43,14 +43,13 @@
Link to page
-
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 e3d9326c53..e7d14c9871 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 @@ -1,426 +1,426 @@ -//used for the media picker dialog -angular.module("umbraco") - .controller("Umbraco.Overlays.MediaPickerController", - function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, userService, $cookies, localStorageService, localizationService) { - - if (!$scope.model.title) { - $scope.model.title = localizationService.localize("defaultdialogs_selectMedia"); - } - - var dialogOptions = $scope.model; - - $scope.disableFolderSelect = dialogOptions.disableFolderSelect; - $scope.onlyImages = dialogOptions.onlyImages; - $scope.showDetails = dialogOptions.showDetails; - $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; - $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; - $scope.cropSize = dialogOptions.cropSize; - $scope.lastOpenedNode = localStorageService.get("umbLastOpenedMediaNodeId"); - $scope.lockedFolder = true; - - var userStartNodes = []; - - var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; - var allowedUploadFiles = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles); - if ($scope.onlyImages) { - $scope.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.imageFileTypes); - } else { - // Use whitelist of allowed file types if provided - if (allowedUploadFiles !== '') { - $scope.acceptedFileTypes = allowedUploadFiles; - } else { - // If no whitelist, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles - $scope.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles); - } - } - - $scope.maxFileSize = umbracoSettings.maxFileSize + "KB"; - - $scope.model.selectedImages = []; - - $scope.acceptedMediatypes = []; - mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) - .then(function(types) { - $scope.acceptedMediatypes = types; - }); - - $scope.searchOptions = { - pageNumber: 1, - pageSize: 100, - totalItems: 0, - totalPages: 0, - filter: '', - ignoreUserStartNodes: $scope.model.ignoreUserStartNodes - }; - - //preload selected item - $scope.target = undefined; - if (dialogOptions.currentTarget) { - $scope.target = dialogOptions.currentTarget; - } - - function onInit() { - userService.getCurrentUser().then(function (userData) { - userStartNodes = userData.startMediaIds; - - if ($scope.startNodeId !== -1) { - entityResource.getById($scope.startNodeId, "media") - .then(function (ent) { - $scope.startNodeId = ent.id; - run(); - }); - } else { - run(); - } - }); - } - - function run() { - //default root item - if (!$scope.target) { - if ($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) { - entityResource.getById($scope.lastOpenedNode, "media") - .then(ensureWithinStartNode, gotoStartNode); - } else { - gotoStartNode(); - } - } else { - //if a target is specified, go look it up - generally this target will just contain ids not the actual full - //media object so we need to look it up - var id = $scope.target.udi ? $scope.target.udi : $scope.target.id - var altText = $scope.target.altText; - if (id) { - mediaResource.getById(id) - .then(function (node) { - $scope.target = node; - if (ensureWithinStartNode(node)) { - selectImage(node); - $scope.target.url = mediaHelper.resolveFile(node); - $scope.target.altText = altText; - $scope.openDetailsDialog(); - } - }, - gotoStartNode); - } else { - gotoStartNode(); - } - } - } - - $scope.upload = function(v) { - angular.element(".umb-file-dropzone-directive .file-select").click(); - }; - - $scope.dragLeave = function(el, event) { - $scope.activeDrag = false; - }; - - $scope.dragEnter = function(el, event) { - $scope.activeDrag = true; - }; - - $scope.submitFolder = function() { - if ($scope.newFolderName) { - $scope.creatingFolder = true; - mediaResource - .addFolder($scope.newFolderName, $scope.currentFolder.id) - .then(function(data) { - //we've added a new folder so lets clear the tree cache for that specific item - treeService.clearCache({ - cacheKey: "__media", //this is the main media tree cache key - childrenOf: data.parentId //clear the children of the parent - }); - $scope.creatingFolder = false; - $scope.gotoFolder(data); - $scope.showFolderInput = false; - $scope.newFolderName = ""; - }); - } else { - $scope.showFolderInput = false; - } - }; - - $scope.enterSubmitFolder = function(event) { - if (event.keyCode === 13) { - $scope.submitFolder(); - event.stopPropagation(); - } - }; - - $scope.gotoFolder = function(folder) { - if (!$scope.multiPicker) { - deselectAllImages($scope.model.selectedImages); - } - - if (!folder) { - folder = { id: -1, name: "Media", icon: "icon-folder" }; - } - - if (folder.id > 0) { - entityResource.getAncestors(folder.id, "media", { ignoreUserStartNodes: $scope.model.ignoreUserStartNodes }) - .then(function(anc) { - $scope.path = _.filter(anc, - function(f) { - return f.path.indexOf($scope.startNodeId) !== -1; - }); - }); - - } else { - $scope.path = []; - } - - mediaTypeHelper.getAllowedImagetypes(folder.id) - .then(function (types) { - $scope.acceptedMediatypes = types; - }); - - $scope.lockedFolder = (folder.id === -1 && $scope.model.startNodeIsVirtual) || hasFolderAccess(folder) === false; - - $scope.currentFolder = folder; - localStorageService.set("umbLastOpenedMediaNodeId", folder.id); - return getChildren(folder.id); - }; - - $scope.clickHandler = function(image, event, index) { - if (image.isFolder) { - if ($scope.disableFolderSelect) { - $scope.gotoFolder(image); - } else { - eventsService.emit("dialogs.mediaPicker.select", image); - selectImage(image); - } - } else { - eventsService.emit("dialogs.mediaPicker.select", image); - if ($scope.showDetails) { - - $scope.target = image; - - // handle both entity and full media object - if (image.image) { - $scope.target.url = image.image; - } else { - $scope.target.url = mediaHelper.resolveFile(image); - } - - $scope.openDetailsDialog(); - } else { - selectImage(image); - } - } - }; - - $scope.clickItemName = function(item) { - if (item.isFolder) { - $scope.gotoFolder(item); - } - }; - - function selectImage(image) { - if (image.selected) { - for (var i = 0; $scope.model.selectedImages.length > i; i++) { - var imageInSelection = $scope.model.selectedImages[i]; - if (image.key === imageInSelection.key) { - image.selected = false; - $scope.model.selectedImages.splice(i, 1); - } - } - } else { - if (!$scope.multiPicker) { - deselectAllImages($scope.model.selectedImages); - } - image.selected = true; - $scope.model.selectedImages.push(image); - } - } - - function deselectAllImages(images) { - for (var i = 0; i < images.length; i++) { - var image = images[i]; - image.selected = false; - } - images.length = 0; - } - - $scope.onUploadComplete = function(files) { - $scope.gotoFolder($scope.currentFolder).then(function() { - if (files.length === 1 && $scope.model.selectedImages.length === 0) { - var image = $scope.images[$scope.images.length - 1]; - $scope.target = image; - $scope.target.url = mediaHelper.resolveFile(image); - selectImage(image); - } - }); - }; - - $scope.onFilesQueue = function() { - $scope.activeDrag = false; - }; - - function ensureWithinStartNode(node) { - // make sure that last opened node is on the same path as start node - var nodePath = node.path.split(","); - - // also make sure the node is not trashed - if (nodePath.indexOf($scope.startNodeId.toString()) !== -1 && node.trashed === false) { - $scope.gotoFolder({ id: $scope.lastOpenedNode, name: "Media", icon: "icon-folder" }); - return true; - } else { - $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); - return false; - } - } - - function hasFolderAccess(node) { - var nodePath = node.path ? node.path.split(',') : [node.id]; - - for (var i = 0; i < nodePath.length; i++) { - if (userStartNodes.indexOf(parseInt(nodePath[i])) !== -1) - return true; - } - - return false; - } - - function gotoStartNode(err) { - $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); - } - - $scope.openDetailsDialog = function() { - - $scope.mediaPickerDetailsOverlay = {}; - $scope.mediaPickerDetailsOverlay.show = true; - - $scope.mediaPickerDetailsOverlay.submit = function(model) { - $scope.model.selectedImages.push($scope.target); - $scope.model.submit($scope.model); - - $scope.mediaPickerDetailsOverlay.show = false; - $scope.mediaPickerDetailsOverlay = null; - }; - - $scope.mediaPickerDetailsOverlay.close = function(oldModel) { - $scope.mediaPickerDetailsOverlay.show = false; - $scope.mediaPickerDetailsOverlay = null; - }; - }; - - var debounceSearchMedia = _.debounce(function() { - $scope.$apply(function() { - if ($scope.searchOptions.filter) { - searchMedia(); - } else { - // reset pagination - $scope.searchOptions = { - pageNumber: 1, - pageSize: 100, - totalItems: 0, - totalPages: 0, - filter: '', - ignoreUserStartNodes: $scope.model.ignoreUserStartNodes - }; - getChildren($scope.currentFolder.id); - } - }); - }, 500); - - $scope.changeSearch = function() { - $scope.loading = true; - debounceSearchMedia(); - }; - - $scope.toggle = function() { - // Make sure to activate the changeSearch function everytime the toggle is clicked - $scope.changeSearch(); - } - - $scope.changePagination = function(pageNumber) { - $scope.loading = true; - $scope.searchOptions.pageNumber = pageNumber; - searchMedia(); - }; - - function searchMedia() { - $scope.loading = true; - entityResource.getPagedDescendants($scope.currentFolder.id, "Media", $scope.searchOptions) - .then(function(data) { - // update image data to work with image grid - angular.forEach(data.items, - function(mediaItem) { - // set thumbnail and src - mediaItem.thumbnail = mediaHelper.resolveFileFromEntity(mediaItem, true); - mediaItem.image = mediaHelper.resolveFileFromEntity(mediaItem, false); - // set properties to match a media object - mediaItem.properties = []; - if (mediaItem.metaData) { - if (mediaItem.metaData.umbracoWidth && mediaItem.metaData.umbracoHeight) { - mediaItem.properties.push({ - alias: "umbracoWidth", - value: mediaItem.metaData.umbracoWidth.Value - }); - mediaItem.properties.push({ - alias: "umbracoHeight", - value: mediaItem.metaData.umbracoHeight.Value - }); - } - if (mediaItem.metaData.umbracoFile) { - mediaItem.properties.push({ - alias: "umbracoFile", - editor: mediaItem.metaData.umbracoFile.PropertyEditorAlias, - value: mediaItem.metaData.umbracoFile.Value - }); - } - } - }); - // update images - $scope.images = data.items ? data.items : []; - // update pagination - if (data.pageNumber > 0) - $scope.searchOptions.pageNumber = data.pageNumber; - if (data.pageSize > 0) - $scope.searchOptions.pageSize = data.pageSize; - $scope.searchOptions.totalItems = data.totalItems; - $scope.searchOptions.totalPages = data.totalPages; - // set already selected images to selected - preSelectImages(); - $scope.loading = false; - }); - } - - function getChildren(id) { - $scope.loading = true; - return mediaResource.getChildren(id, { ignoreUserStartNodes: $scope.model.ignoreUserStartNodes }) - .then(function(data) { - $scope.searchOptions.filter = ""; - $scope.images = data.items ? data.items : []; - // set already selected images to selected - preSelectImages(); - $scope.loading = false; - }); - } - - function preSelectImages() { - for (var folderImageIndex = 0; folderImageIndex < $scope.images.length; folderImageIndex++) { - var folderImage = $scope.images[folderImageIndex]; - var imageIsSelected = false; - - if ($scope.model && angular.isArray($scope.model.selectedImages)) { - for (var selectedImageIndex = 0; - selectedImageIndex < $scope.model.selectedImages.length; - selectedImageIndex++) { - var selectedImage = $scope.model.selectedImages[selectedImageIndex]; - - if (folderImage.key === selectedImage.key) { - imageIsSelected = true; - } - } - } - - if (imageIsSelected) { - folderImage.selected = true; - } - } - } - - onInit(); - - }); +//used for the media picker dialog +angular.module("umbraco") + .controller("Umbraco.Overlays.MediaPickerController", + function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, userService, $cookies, localStorageService, localizationService) { + + if (!$scope.model.title) { + $scope.model.title = localizationService.localize("defaultdialogs_selectMedia"); + } + + var dialogOptions = $scope.model; + + $scope.disableFolderSelect = dialogOptions.disableFolderSelect; + $scope.onlyImages = dialogOptions.onlyImages; + $scope.showDetails = dialogOptions.showDetails; + $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; + $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; + $scope.cropSize = dialogOptions.cropSize; + $scope.lastOpenedNode = localStorageService.get("umbLastOpenedMediaNodeId"); + $scope.lockedFolder = true; + + var userStartNodes = []; + + var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; + var allowedUploadFiles = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles); + if ($scope.onlyImages) { + $scope.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.imageFileTypes); + } else { + // Use whitelist of allowed file types if provided + if (allowedUploadFiles !== '') { + $scope.acceptedFileTypes = allowedUploadFiles; + } else { + // If no whitelist, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles + $scope.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles); + } + } + + $scope.maxFileSize = umbracoSettings.maxFileSize + "KB"; + + $scope.model.selectedImages = []; + + $scope.acceptedMediatypes = []; + mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) + .then(function(types) { + $scope.acceptedMediatypes = types; + }); + + $scope.searchOptions = { + pageNumber: 1, + pageSize: 100, + totalItems: 0, + totalPages: 0, + filter: '', + dataTypeId: $scope.model.dataTypeId + }; + + //preload selected item + $scope.target = undefined; + if (dialogOptions.currentTarget) { + $scope.target = dialogOptions.currentTarget; + } + + function onInit() { + userService.getCurrentUser().then(function (userData) { + userStartNodes = userData.startMediaIds; + + if ($scope.startNodeId !== -1) { + entityResource.getById($scope.startNodeId, "media") + .then(function (ent) { + $scope.startNodeId = ent.id; + run(); + }); + } else { + run(); + } + }); + } + + function run() { + //default root item + if (!$scope.target) { + if ($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) { + entityResource.getById($scope.lastOpenedNode, "media") + .then(ensureWithinStartNode, gotoStartNode); + } else { + gotoStartNode(); + } + } else { + //if a target is specified, go look it up - generally this target will just contain ids not the actual full + //media object so we need to look it up + var id = $scope.target.udi ? $scope.target.udi : $scope.target.id + var altText = $scope.target.altText; + if (id) { + mediaResource.getById(id) + .then(function (node) { + $scope.target = node; + if (ensureWithinStartNode(node)) { + selectImage(node); + $scope.target.url = mediaHelper.resolveFile(node); + $scope.target.altText = altText; + $scope.openDetailsDialog(); + } + }, + gotoStartNode); + } else { + gotoStartNode(); + } + } + } + + $scope.upload = function(v) { + angular.element(".umb-file-dropzone-directive .file-select").click(); + }; + + $scope.dragLeave = function(el, event) { + $scope.activeDrag = false; + }; + + $scope.dragEnter = function(el, event) { + $scope.activeDrag = true; + }; + + $scope.submitFolder = function() { + if ($scope.newFolderName) { + $scope.creatingFolder = true; + mediaResource + .addFolder($scope.newFolderName, $scope.currentFolder.id) + .then(function(data) { + //we've added a new folder so lets clear the tree cache for that specific item + treeService.clearCache({ + cacheKey: "__media", //this is the main media tree cache key + childrenOf: data.parentId //clear the children of the parent + }); + $scope.creatingFolder = false; + $scope.gotoFolder(data); + $scope.showFolderInput = false; + $scope.newFolderName = ""; + }); + } else { + $scope.showFolderInput = false; + } + }; + + $scope.enterSubmitFolder = function(event) { + if (event.keyCode === 13) { + $scope.submitFolder(); + event.stopPropagation(); + } + }; + + $scope.gotoFolder = function(folder) { + if (!$scope.multiPicker) { + deselectAllImages($scope.model.selectedImages); + } + + if (!folder) { + folder = { id: -1, name: "Media", icon: "icon-folder" }; + } + + if (folder.id > 0) { + entityResource.getAncestors(folder.id, "media", { dataTypeId: $scope.model.dataTypeId }) + .then(function(anc) { + $scope.path = _.filter(anc, + function(f) { + return f.path.indexOf($scope.startNodeId) !== -1; + }); + }); + + } else { + $scope.path = []; + } + + mediaTypeHelper.getAllowedImagetypes(folder.id) + .then(function (types) { + $scope.acceptedMediatypes = types; + }); + + $scope.lockedFolder = (folder.id === -1 && $scope.model.startNodeIsVirtual) || hasFolderAccess(folder) === false; + + $scope.currentFolder = folder; + localStorageService.set("umbLastOpenedMediaNodeId", folder.id); + return getChildren(folder.id); + }; + + $scope.clickHandler = function(image, event, index) { + if (image.isFolder) { + if ($scope.disableFolderSelect) { + $scope.gotoFolder(image); + } else { + eventsService.emit("dialogs.mediaPicker.select", image); + selectImage(image); + } + } else { + eventsService.emit("dialogs.mediaPicker.select", image); + if ($scope.showDetails) { + + $scope.target = image; + + // handle both entity and full media object + if (image.image) { + $scope.target.url = image.image; + } else { + $scope.target.url = mediaHelper.resolveFile(image); + } + + $scope.openDetailsDialog(); + } else { + selectImage(image); + } + } + }; + + $scope.clickItemName = function(item) { + if (item.isFolder) { + $scope.gotoFolder(item); + } + }; + + function selectImage(image) { + if (image.selected) { + for (var i = 0; $scope.model.selectedImages.length > i; i++) { + var imageInSelection = $scope.model.selectedImages[i]; + if (image.key === imageInSelection.key) { + image.selected = false; + $scope.model.selectedImages.splice(i, 1); + } + } + } else { + if (!$scope.multiPicker) { + deselectAllImages($scope.model.selectedImages); + } + image.selected = true; + $scope.model.selectedImages.push(image); + } + } + + function deselectAllImages(images) { + for (var i = 0; i < images.length; i++) { + var image = images[i]; + image.selected = false; + } + images.length = 0; + } + + $scope.onUploadComplete = function(files) { + $scope.gotoFolder($scope.currentFolder).then(function() { + if (files.length === 1 && $scope.model.selectedImages.length === 0) { + var image = $scope.images[$scope.images.length - 1]; + $scope.target = image; + $scope.target.url = mediaHelper.resolveFile(image); + selectImage(image); + } + }); + }; + + $scope.onFilesQueue = function() { + $scope.activeDrag = false; + }; + + function ensureWithinStartNode(node) { + // make sure that last opened node is on the same path as start node + var nodePath = node.path.split(","); + + // also make sure the node is not trashed + if (nodePath.indexOf($scope.startNodeId.toString()) !== -1 && node.trashed === false) { + $scope.gotoFolder({ id: $scope.lastOpenedNode, name: "Media", icon: "icon-folder" }); + return true; + } else { + $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); + return false; + } + } + + function hasFolderAccess(node) { + var nodePath = node.path ? node.path.split(',') : [node.id]; + + for (var i = 0; i < nodePath.length; i++) { + if (userStartNodes.indexOf(parseInt(nodePath[i])) !== -1) + return true; + } + + return false; + } + + function gotoStartNode(err) { + $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); + } + + $scope.openDetailsDialog = function() { + + $scope.mediaPickerDetailsOverlay = {}; + $scope.mediaPickerDetailsOverlay.show = true; + + $scope.mediaPickerDetailsOverlay.submit = function(model) { + $scope.model.selectedImages.push($scope.target); + $scope.model.submit($scope.model); + + $scope.mediaPickerDetailsOverlay.show = false; + $scope.mediaPickerDetailsOverlay = null; + }; + + $scope.mediaPickerDetailsOverlay.close = function(oldModel) { + $scope.mediaPickerDetailsOverlay.show = false; + $scope.mediaPickerDetailsOverlay = null; + }; + }; + + var debounceSearchMedia = _.debounce(function() { + $scope.$apply(function() { + if ($scope.searchOptions.filter) { + searchMedia(); + } else { + // reset pagination + $scope.searchOptions = { + pageNumber: 1, + pageSize: 100, + totalItems: 0, + totalPages: 0, + filter: '', + dataTypeId: $scope.model.dataTypeId + }; + getChildren($scope.currentFolder.id); + } + }); + }, 500); + + $scope.changeSearch = function() { + $scope.loading = true; + debounceSearchMedia(); + }; + + $scope.toggle = function() { + // Make sure to activate the changeSearch function everytime the toggle is clicked + $scope.changeSearch(); + } + + $scope.changePagination = function(pageNumber) { + $scope.loading = true; + $scope.searchOptions.pageNumber = pageNumber; + searchMedia(); + }; + + function searchMedia() { + $scope.loading = true; + entityResource.getPagedDescendants($scope.currentFolder.id, "Media", $scope.searchOptions) + .then(function(data) { + // update image data to work with image grid + angular.forEach(data.items, + function(mediaItem) { + // set thumbnail and src + mediaItem.thumbnail = mediaHelper.resolveFileFromEntity(mediaItem, true); + mediaItem.image = mediaHelper.resolveFileFromEntity(mediaItem, false); + // set properties to match a media object + mediaItem.properties = []; + if (mediaItem.metaData) { + if (mediaItem.metaData.umbracoWidth && mediaItem.metaData.umbracoHeight) { + mediaItem.properties.push({ + alias: "umbracoWidth", + value: mediaItem.metaData.umbracoWidth.Value + }); + mediaItem.properties.push({ + alias: "umbracoHeight", + value: mediaItem.metaData.umbracoHeight.Value + }); + } + if (mediaItem.metaData.umbracoFile) { + mediaItem.properties.push({ + alias: "umbracoFile", + editor: mediaItem.metaData.umbracoFile.PropertyEditorAlias, + value: mediaItem.metaData.umbracoFile.Value + }); + } + } + }); + // update images + $scope.images = data.items ? data.items : []; + // update pagination + if (data.pageNumber > 0) + $scope.searchOptions.pageNumber = data.pageNumber; + if (data.pageSize > 0) + $scope.searchOptions.pageSize = data.pageSize; + $scope.searchOptions.totalItems = data.totalItems; + $scope.searchOptions.totalPages = data.totalPages; + // set already selected images to selected + preSelectImages(); + $scope.loading = false; + }); + } + + function getChildren(id) { + $scope.loading = true; + return mediaResource.getChildren(id, { dataTypeId: $scope.model.dataTypeId }) + .then(function(data) { + $scope.searchOptions.filter = ""; + $scope.images = data.items ? data.items : []; + // set already selected images to selected + preSelectImages(); + $scope.loading = false; + }); + } + + function preSelectImages() { + for (var folderImageIndex = 0; folderImageIndex < $scope.images.length; folderImageIndex++) { + var folderImage = $scope.images[folderImageIndex]; + var imageIsSelected = false; + + if ($scope.model && angular.isArray($scope.model.selectedImages)) { + for (var selectedImageIndex = 0; + selectedImageIndex < $scope.model.selectedImages.length; + selectedImageIndex++) { + var selectedImage = $scope.model.selectedImages[selectedImageIndex]; + + if (folderImage.key === selectedImage.key) { + imageIsSelected = true; + } + } + } + + if (imageIsSelected) { + folderImage.selected = true; + } + } + } + + onInit(); + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js index e1ce332b48..b4dd34dfff 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -16,7 +16,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", searchFromId: dialogOptions.startNodeId, searchFromName: null, showSearch: false, - ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, + dataTypeId: dialogOptions.dataTypeId, results: [], selectedSearchResults: [] } @@ -138,8 +138,8 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", if (dialogOptions.startNodeId) params.push("startNodeId=" + dialogOptions.startNodeId); - if (dialogOptions.ignoreUserStartNodes) - params.push("ignoreUserStartNodes=" + dialogOptions.ignoreUserStartNodes); + if (dialogOptions.dataTypeId) + params.push("dataTypeId=" + dialogOptions.dataTypeId); if (dialogOptions.customTreeParams) params.push(dialogOptions.customTreeParams); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html index c338e3402c..cfcbcfac75 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html @@ -1,56 +1,56 @@ -
- -
- -
- - -
- - - - - - {{ emptyStateMessage }} - - -
- - -
- -
- - - - -
+
+ +
+ +
+ + +
+ + + + + + {{ emptyStateMessage }} + + +
+ + +
+ +
+ + + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index ce836a8d68..627baa3e7a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -1,366 +1,366 @@ -//this controller simply tells the dialogs service to open a mediaPicker window -//with a specified callback, this callback will receive an object with a selection on it - -function contentPickerController($scope, entityResource, editorState, iconHelper, $routeParams, angularHelper, navigationService, $location, miniEditorHelper, localizationService) { - - var unsubscribe; - - function subscribe() { - unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - var currIds = _.map($scope.renderModel, function (i) { - return $scope.model.config.idType === "udi" ? i.udi : i.id; - }); - $scope.model.value = trim(currIds.join(), ","); - }); - } - - function trim(str, chr) { - var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); - return str.replace(rgxtrim, ''); - } - - function startWatch() { - //We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required - // because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable - // occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs. - // In their source code there is no event so we need to just subscribe to our model changes here. - //This also makes it easier to manage models, we update one and the rest will just work. - $scope.$watch(function () { - //return the joined Ids as a string to watch - return _.map($scope.renderModel, function (i) { - return $scope.model.config.idType === "udi" ? i.udi : i.id; - }).join(); - }, function (newVal) { - var currIds = _.map($scope.renderModel, function (i) { - return $scope.model.config.idType === "udi" ? i.udi : i.id; - }); - $scope.model.value = trim(currIds.join(), ","); - - //Validate! - if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) { - $scope.contentPickerForm.minCount.$setValidity("minCount", false); - } - else { - $scope.contentPickerForm.minCount.$setValidity("minCount", true); - } - - if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) { - $scope.contentPickerForm.maxCount.$setValidity("maxCount", false); - } - else { - $scope.contentPickerForm.maxCount.$setValidity("maxCount", true); - } - - setSortingState($scope.renderModel); - - }); - } - - $scope.renderModel = []; - - $scope.dialogEditor = editorState && editorState.current && editorState.current.isDialogEditor === true; - - //the default pre-values - var defaultConfig = { - multiPicker: false, - showOpenButton: false, - showEditButton: false, - showPathOnHover: false, - ignoreUserStartNodes: false, - maxNumber: 1, - minNumber : 0, - startNode: { - query: "", - type: "content", - id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1 // get start node for simple Content Picker - } - }; - - // sortable options - $scope.sortableOptions = { - axis: "y", - containment: "parent", - distance: 10, - opacity: 0.7, - tolerance: "pointer", - scroll: true, - zIndex: 6000, - update: function (e, ui) { - angularHelper.getCurrentForm($scope).$setDirty(); - } - }; - - if ($scope.model.config) { - //merge the server config on top of the default config, then set the server config to use the result - $scope.model.config = angular.extend(defaultConfig, $scope.model.config); - } - - //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that! - $scope.model.config.multiPicker = ($scope.model.config.multiPicker === "1" ? true : false); - $scope.model.config.showOpenButton = ($scope.model.config.showOpenButton === "1" ? true : false); - $scope.model.config.showEditButton = ($scope.model.config.showEditButton === "1" ? true : false); - $scope.model.config.showPathOnHover = ($scope.model.config.showPathOnHover === "1" ? true : false); - $scope.model.config.ignoreUserStartNodes = ($scope.model.config.ignoreUserStartNodes === "1" ? true : false); - - var entityType = $scope.model.config.startNode.type === "member" - ? "Member" - : $scope.model.config.startNode.type === "media" - ? "Media" - : "Document"; - $scope.allowOpenButton = entityType === "Document"; - $scope.allowEditButton = entityType === "Document"; - $scope.allowRemoveButton = true; - - //the dialog options for the picker - var dialogOptions = { - multiPicker: $scope.model.config.multiPicker, - entityType: entityType, - filterCssClass: "not-allowed not-published", - startNodeId: null, - currentNode: editorState ? editorState.current : null, - callback: function (data) { - if (angular.isArray(data)) { - _.each(data, function (item, i) { - $scope.add(item); - }); - } else { - $scope.clear(); - $scope.add(data); - } - angularHelper.getCurrentForm($scope).$setDirty(); - }, - treeAlias: $scope.model.config.startNode.type, - section: $scope.model.config.startNode.type, - idType: "int" - }; - - //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the - // pre-value config on to the dialog options - angular.extend(dialogOptions, $scope.model.config); - - //We need to manually handle the filter for members here since the tree displayed is different and only contains - // searchable list views - if (entityType === "Member") { - //first change the not allowed filter css class - dialogOptions.filterCssClass = "not-allowed"; - var currFilter = dialogOptions.filter; - //now change the filter to be a method - dialogOptions.filter = function(i) { - //filter out the list view nodes - if (i.metaData.isContainer) { - return true; - } - if (!currFilter) { - return false; - } - //now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller, - // but not much we can do about that since members require special filtering. - var filterItem = currFilter.toLowerCase().split(','); - var found = filterItem.indexOf(i.metaData.contentType.toLowerCase()) >= 0; - if (!currFilter.startsWith("!") && !found || currFilter.startsWith("!") && found) { - return true; - } - - return false; - } - } - - if ($routeParams.section === "settings" && $routeParams.tree === "documentTypes") { - //if the content-picker is being rendered inside the document-type editor, we don't need to process the startnode query - dialogOptions.startNodeId = -1; - } else if ($scope.model.config.startNode.query) { - //if we have a query for the startnode, we will use that. - var rootId = $routeParams.id; - entityResource.getByQuery($scope.model.config.startNode.query, rootId, "Document").then(function (ent) { - dialogOptions.startNodeId = $scope.model.config.idType === "udi" ? ent.udi : ent.id; - }); - } - else { - dialogOptions.startNodeId = $scope.model.config.startNode.id; - } - - //dialog - $scope.openContentPicker = function() { - $scope.contentPickerOverlay = dialogOptions; - $scope.contentPickerOverlay.view = "treepicker"; - $scope.contentPickerOverlay.show = true; - - $scope.contentPickerOverlay.submit = function(model) { - - if (angular.isArray(model.selection)) { - _.each(model.selection, function (item, i) { - $scope.add(item); - }); - angularHelper.getCurrentForm($scope).$setDirty(); - } - - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - } - - $scope.contentPickerOverlay.close = function(oldModel) { - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - } - - }; - - $scope.remove = function (index) { - $scope.renderModel.splice(index, 1); - angularHelper.getCurrentForm($scope).$setDirty(); - }; - - $scope.showNode = function (index) { - var item = $scope.renderModel[index]; - var id = item.id; - var section = $scope.model.config.startNode.type.toLowerCase(); - - entityResource.getPath(id, entityType).then(function (path) { - navigationService.changeSection(section); - navigationService.showTree(section, { - tree: section, path: path, forceReload: false, activate: true - }); - var routePath = section + "/" + section + "/edit/" + id.toString(); - $location.path(routePath).search(""); - }); - } - - $scope.add = function (item) { - var currIds = _.map($scope.renderModel, function (i) { - return $scope.model.config.idType === "udi" ? i.udi : i.id; - }); - - var itemId = $scope.model.config.idType === "udi" ? item.udi : item.id; - - if (currIds.indexOf(itemId) < 0) { - setEntityUrl(item); - } - }; - - $scope.clear = function () { - $scope.renderModel = []; - }; - - $scope.openMiniEditor = function(node) { - miniEditorHelper.launchMiniEditor(node).then(function(updatedNode){ - // update the node - node.name = updatedNode.name; - node.published = updatedNode.hasPublishedVersion; - if(entityType !== "Member") { - entityResource.getUrl(updatedNode.id, entityType).then(function(data){ - node.url = data; - }); - } - }); - }; - - //when the scope is destroyed we need to unsubscribe - $scope.$on('$destroy', function () { - if(unsubscribe) { - unsubscribe(); - } - }); - - var modelIds = $scope.model.value ? $scope.model.value.split(',') : []; - - //load current data if anything selected - if (modelIds.length > 0) { - entityResource.getByIds(modelIds, entityType).then(function(data) { - - _.each(modelIds, - function(id, i) { - var entity = _.find(data, - function(d) { - return $scope.model.config.idType === "udi" ? (d.udi == id) : (d.id == id); - }); - - if (entity) { - setEntityUrl(entity); - } - - }); - - //everything is loaded, start the watch on the model - startWatch(); - subscribe(); - }); - } - else { - //everything is loaded, start the watch on the model - startWatch(); - subscribe(); - } - - function setEntityUrl(entity) { - - // get url for content and media items - if(entityType !== "Member") { - entityResource.getUrl(entity.id, entityType).then(function(data){ - // update url - angular.forEach($scope.renderModel, function(item){ - if (item.id === entity.id) { - if (entity.trashed) { - item.url = localizationService.dictionary.general_recycleBin; - } else { - item.url = data; - } - } - }); - }); - } - - // add the selected item to the renderModel - // if it needs to show a url the item will get - // updated when the url comes back from server - addSelectedItem(entity); - - } - - function addSelectedItem(item) { - - // set icon - if(item.icon) { - item.icon = iconHelper.convertFromLegacyIcon(item.icon); - } - - // set default icon - if (!item.icon) { - switch (entityType) { - case "Document": - item.icon = "icon-document"; - break; - case "Media": - item.icon = "icon-picture"; - break; - case "Member": - item.icon = "icon-user"; - break; - } - } - - $scope.renderModel.push({ - "name": item.name, - "id": item.id, - "udi": item.udi, - "icon": item.icon, - "path": item.path, - "url": item.url, - "trashed": item.trashed, - "published": (item.metaData && item.metaData.IsPublished === false && entityType === "Document") ? false : true - // only content supports published/unpublished content so we set everything else to published so the UI looks correct - }); - - } - - function setSortingState(items) { - // disable sorting if the list only consist of one item - if(items.length > 1) { - $scope.sortableOptions.disabled = false; - } else { - $scope.sortableOptions.disabled = true; - } - } - -} - -angular.module('umbraco').controller("Umbraco.PropertyEditors.ContentPickerController", contentPickerController); +//this controller simply tells the dialogs service to open a mediaPicker window +//with a specified callback, this callback will receive an object with a selection on it + +function contentPickerController($scope, entityResource, editorState, iconHelper, $routeParams, angularHelper, navigationService, $location, miniEditorHelper, localizationService) { + + var unsubscribe; + + function subscribe() { + unsubscribe = $scope.$on("formSubmitting", function (ev, args) { + var currIds = _.map($scope.renderModel, function (i) { + return $scope.model.config.idType === "udi" ? i.udi : i.id; + }); + $scope.model.value = trim(currIds.join(), ","); + }); + } + + function trim(str, chr) { + var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g'); + return str.replace(rgxtrim, ''); + } + + function startWatch() { + //We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required + // because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable + // occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs. + // In their source code there is no event so we need to just subscribe to our model changes here. + //This also makes it easier to manage models, we update one and the rest will just work. + $scope.$watch(function () { + //return the joined Ids as a string to watch + return _.map($scope.renderModel, function (i) { + return $scope.model.config.idType === "udi" ? i.udi : i.id; + }).join(); + }, function (newVal) { + var currIds = _.map($scope.renderModel, function (i) { + return $scope.model.config.idType === "udi" ? i.udi : i.id; + }); + $scope.model.value = trim(currIds.join(), ","); + + //Validate! + if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) { + $scope.contentPickerForm.minCount.$setValidity("minCount", false); + } + else { + $scope.contentPickerForm.minCount.$setValidity("minCount", true); + } + + if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) { + $scope.contentPickerForm.maxCount.$setValidity("maxCount", false); + } + else { + $scope.contentPickerForm.maxCount.$setValidity("maxCount", true); + } + + setSortingState($scope.renderModel); + + }); + } + + $scope.renderModel = []; + + $scope.dialogEditor = editorState && editorState.current && editorState.current.isDialogEditor === true; + + //the default pre-values + var defaultConfig = { + multiPicker: false, + showOpenButton: false, + showEditButton: false, + showPathOnHover: false, + dataTypeId: null, + maxNumber: 1, + minNumber : 0, + startNode: { + query: "", + type: "content", + id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1 // get start node for simple Content Picker + } + }; + + // sortable options + $scope.sortableOptions = { + axis: "y", + containment: "parent", + distance: 10, + opacity: 0.7, + tolerance: "pointer", + scroll: true, + zIndex: 6000, + update: function (e, ui) { + angularHelper.getCurrentForm($scope).$setDirty(); + } + }; + + if ($scope.model.config) { + //merge the server config on top of the default config, then set the server config to use the result + $scope.model.config = angular.extend(defaultConfig, $scope.model.config); + } + + //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that! + $scope.model.config.multiPicker = ($scope.model.config.multiPicker === "1" ? true : false); + $scope.model.config.showOpenButton = ($scope.model.config.showOpenButton === "1" ? true : false); + $scope.model.config.showEditButton = ($scope.model.config.showEditButton === "1" ? true : false); + $scope.model.config.showPathOnHover = ($scope.model.config.showPathOnHover === "1" ? true : false); + + var entityType = $scope.model.config.startNode.type === "member" + ? "Member" + : $scope.model.config.startNode.type === "media" + ? "Media" + : "Document"; + $scope.allowOpenButton = entityType === "Document"; + $scope.allowEditButton = entityType === "Document"; + $scope.allowRemoveButton = true; + + //the dialog options for the picker + var dialogOptions = { + multiPicker: $scope.model.config.multiPicker, + entityType: entityType, + filterCssClass: "not-allowed not-published", + startNodeId: null, + currentNode: editorState ? editorState.current : null, + callback: function (data) { + if (angular.isArray(data)) { + _.each(data, function (item, i) { + $scope.add(item); + }); + } else { + $scope.clear(); + $scope.add(data); + } + angularHelper.getCurrentForm($scope).$setDirty(); + }, + treeAlias: $scope.model.config.startNode.type, + section: $scope.model.config.startNode.type, + idType: "int" + }; + + dialogOptions.dataTypeId = $scope.model.dataTypeId; + //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the + // pre-value config on to the dialog options + angular.extend(dialogOptions, $scope.model.config); + + //We need to manually handle the filter for members here since the tree displayed is different and only contains + // searchable list views + if (entityType === "Member") { + //first change the not allowed filter css class + dialogOptions.filterCssClass = "not-allowed"; + var currFilter = dialogOptions.filter; + //now change the filter to be a method + dialogOptions.filter = function(i) { + //filter out the list view nodes + if (i.metaData.isContainer) { + return true; + } + if (!currFilter) { + return false; + } + //now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller, + // but not much we can do about that since members require special filtering. + var filterItem = currFilter.toLowerCase().split(','); + var found = filterItem.indexOf(i.metaData.contentType.toLowerCase()) >= 0; + if (!currFilter.startsWith("!") && !found || currFilter.startsWith("!") && found) { + return true; + } + + return false; + } + } + + if ($routeParams.section === "settings" && $routeParams.tree === "documentTypes") { + //if the content-picker is being rendered inside the document-type editor, we don't need to process the startnode query + dialogOptions.startNodeId = -1; + } else if ($scope.model.config.startNode.query) { + //if we have a query for the startnode, we will use that. + var rootId = $routeParams.id; + entityResource.getByQuery($scope.model.config.startNode.query, rootId, "Document").then(function (ent) { + dialogOptions.startNodeId = $scope.model.config.idType === "udi" ? ent.udi : ent.id; + }); + } + else { + dialogOptions.startNodeId = $scope.model.config.startNode.id; + } + + //dialog + $scope.openContentPicker = function() { + $scope.contentPickerOverlay = dialogOptions; + $scope.contentPickerOverlay.view = "treepicker"; + $scope.contentPickerOverlay.show = true; + + $scope.contentPickerOverlay.submit = function(model) { + + if (angular.isArray(model.selection)) { + _.each(model.selection, function (item, i) { + $scope.add(item); + }); + angularHelper.getCurrentForm($scope).$setDirty(); + } + + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + } + + $scope.contentPickerOverlay.close = function(oldModel) { + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + } + + }; + + $scope.remove = function (index) { + $scope.renderModel.splice(index, 1); + angularHelper.getCurrentForm($scope).$setDirty(); + }; + + $scope.showNode = function (index) { + var item = $scope.renderModel[index]; + var id = item.id; + var section = $scope.model.config.startNode.type.toLowerCase(); + + entityResource.getPath(id, entityType).then(function (path) { + navigationService.changeSection(section); + navigationService.showTree(section, { + tree: section, path: path, forceReload: false, activate: true + }); + var routePath = section + "/" + section + "/edit/" + id.toString(); + $location.path(routePath).search(""); + }); + } + + $scope.add = function (item) { + var currIds = _.map($scope.renderModel, function (i) { + return $scope.model.config.idType === "udi" ? i.udi : i.id; + }); + + var itemId = $scope.model.config.idType === "udi" ? item.udi : item.id; + + if (currIds.indexOf(itemId) < 0) { + setEntityUrl(item); + } + }; + + $scope.clear = function () { + $scope.renderModel = []; + }; + + $scope.openMiniEditor = function(node) { + miniEditorHelper.launchMiniEditor(node).then(function(updatedNode){ + // update the node + node.name = updatedNode.name; + node.published = updatedNode.hasPublishedVersion; + if(entityType !== "Member") { + entityResource.getUrl(updatedNode.id, entityType).then(function(data){ + node.url = data; + }); + } + }); + }; + + //when the scope is destroyed we need to unsubscribe + $scope.$on('$destroy', function () { + if(unsubscribe) { + unsubscribe(); + } + }); + + var modelIds = $scope.model.value ? $scope.model.value.split(',') : []; + + //load current data if anything selected + if (modelIds.length > 0) { + entityResource.getByIds(modelIds, entityType).then(function(data) { + + _.each(modelIds, + function(id, i) { + var entity = _.find(data, + function(d) { + return $scope.model.config.idType === "udi" ? (d.udi == id) : (d.id == id); + }); + + if (entity) { + setEntityUrl(entity); + } + + }); + + //everything is loaded, start the watch on the model + startWatch(); + subscribe(); + }); + } + else { + //everything is loaded, start the watch on the model + startWatch(); + subscribe(); + } + + function setEntityUrl(entity) { + + // get url for content and media items + if(entityType !== "Member") { + entityResource.getUrl(entity.id, entityType).then(function(data){ + // update url + angular.forEach($scope.renderModel, function(item){ + if (item.id === entity.id) { + if (entity.trashed) { + item.url = localizationService.dictionary.general_recycleBin; + } else { + item.url = data; + } + } + }); + }); + } + + // add the selected item to the renderModel + // if it needs to show a url the item will get + // updated when the url comes back from server + addSelectedItem(entity); + + } + + function addSelectedItem(item) { + + // set icon + if(item.icon) { + item.icon = iconHelper.convertFromLegacyIcon(item.icon); + } + + // set default icon + if (!item.icon) { + switch (entityType) { + case "Document": + item.icon = "icon-document"; + break; + case "Media": + item.icon = "icon-picture"; + break; + case "Member": + item.icon = "icon-user"; + break; + } + } + + $scope.renderModel.push({ + "name": item.name, + "id": item.id, + "udi": item.udi, + "icon": item.icon, + "path": item.path, + "url": item.url, + "trashed": item.trashed, + "published": (item.metaData && item.metaData.IsPublished === false && entityType === "Document") ? false : true + // only content supports published/unpublished content so we set everything else to published so the UI looks correct + }); + + } + + function setSortingState(items) { + // disable sorting if the list only consist of one item + if(items.length > 1) { + $scope.sortableOptions.disabled = false; + } else { + $scope.sortableOptions.disabled = true; + } + } + +} + +angular.module('umbraco').controller("Umbraco.PropertyEditors.ContentPickerController", contentPickerController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index a093ccb034..c61f5419ca 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -1,11 +1,9 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.Grid.MediaController", function ($scope, $rootScope, $timeout, userService) { - - var ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false; if (!$scope.model.config.startNodeId) { - if (ignoreUserStartNodes === true) { + if ($scope.model.config.ignoreUserStartNodes === "1" ) { $scope.model.config.startNodeId = -1; $scope.model.config.startNodeIsVirtual = true; @@ -22,7 +20,7 @@ angular.module("umbraco") $scope.mediaPickerOverlay.view = "mediapicker"; $scope.mediaPickerOverlay.startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined; $scope.mediaPickerOverlay.startNodeIsVirtual = $scope.mediaPickerOverlay.startNodeId ? $scope.model.config.startNodeIsVirtual : undefined; - $scope.mediaPickerOverlay.ignoreUserStartNodes = ignoreUserStartNodes; + $scope.mediaPickerOverlay.dataTypeId = $scope.model.dataTypeId; $scope.mediaPickerOverlay.cropSize = $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined; $scope.mediaPickerOverlay.showDetails = true; $scope.mediaPickerOverlay.disableFolderSelect = true; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js index cf81300b32..e3e5d5e072 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js @@ -11,12 +11,13 @@ vm.openEmbed = openEmbed; function openLinkPicker(editor, currentTarget, anchorElement) { - + vm.linkPickerOverlay = { view: "linkpicker", currentTarget: currentTarget, anchors: tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)), - ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1", + dataTypeId: $scope.model.dataTypeId, + ignoreUserStartNodes : $scope.model.config.ignoreUserStartNodes, show: true, submit: function(model) { tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); @@ -27,12 +28,11 @@ } function openMediaPicker(editor, currentTarget, userData) { - var ignoreUserStartNodes = false; var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; var startNodeIsVirtual = userData.startMediaIds.length !== 1; + if ($scope.model.config.ignoreUserStartNodes === "1") { - ignoreUserStartNodes = true; startNodeId = -1; startNodeIsVirtual = true; } @@ -40,10 +40,10 @@ vm.mediaPickerOverlay = { currentTarget: currentTarget, onlyImages: true, - showDetails: true, + showDetails: true, startNodeId: startNodeId, startNodeIsVirtual: startNodeIsVirtual, - ignoreUserStartNodes: ignoreUserStartNodes, + dataTypeId: $scope.model.dataTypeId, view: "mediapicker", show: true, submit: function(model) { 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 1f1305b0f0..559d0e4a19 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 @@ -1,184 +1,185 @@ -//this controller simply tells the dialogs service to open a mediaPicker window -//with a specified callback, this callback will receive an object with a selection on it -angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerController", - function ($rootScope, $scope, dialogService, entityResource, mediaResource, mediaHelper, $timeout, userService, $location, localizationService) { - - //check the pre-values for multi-picker - var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false; - var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false; - var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false; - var ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false; - - if (!$scope.model.config.startNodeId) { - if (ignoreUserStartNodes === true) { - $scope.model.config.startNodeId = -1; - $scope.model.config.startNodeIsVirtual = true; - - } else { - userService.getCurrentUser().then(function (userData) { - $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; - }); - } - } - - function setupViewModel() { - $scope.mediaItems = []; - $scope.ids = []; - - $scope.isMultiPicker = multiPicker; - - if ($scope.model.value) { - var ids = $scope.model.value.split(','); - - //NOTE: We need to use the entityResource NOT the mediaResource here because - // the mediaResource has server side auth configured for which the user must have - // access to the media section, if they don't they'll get auth errors. The entityResource - // acts differently in that it allows access if the user has access to any of the apps that - // might require it's use. Therefore we need to use the metaData property to get at the thumbnail - // value. - - entityResource.getByIds(ids, "Media").then(function(medias) { - - // The service only returns item results for ids that exist (deleted items are silently ignored). - // This results in the picked items value to be set to contain only ids of picked items that could actually be found. - // Since a referenced item could potentially be restored later on, instead of changing the selected values here based - // on whether the items exist during a save event - we should keep "placeholder" items for picked items that currently - // could not be fetched. This will preserve references and ensure that the state of an item does not differ depending - // on whether it is simply resaved or not. - // This is done by remapping the int/guid ids into a new array of items, where we create "Deleted item" placeholders - // when there is no match for a selected id. This will ensure that the values being set on save, are the same as before. - - medias = _.map(ids, - function(id) { - var found = _.find(medias, - function(m) { - // We could use coercion (two ='s) here .. but not sure if this works equally well in all browsers and - // it's prone to someone "fixing" it at some point without knowing the effects. Rather use toString() - // compares and be completely sure it works. - return m.udi.toString() === id.toString() || m.id.toString() === id.toString(); - }); - if (found) { - return found; - } else { - return { - name: localizationService.dictionary.mediaPicker_deletedItem, - id: $scope.model.config.idType !== "udi" ? id : null, - udi: $scope.model.config.idType === "udi" ? id : null, - icon: "icon-picture", - thumbnail: null, - trashed: true - }; - } - }); - - _.each(medias, - function(media, i) { - // if there is no thumbnail, try getting one if the media is not a placeholder item - if (!media.thumbnail && media.id && media.metaData) { - media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); - } - - $scope.mediaItems.push(media); - - if ($scope.model.config.idType === "udi") { - $scope.ids.push(media.udi); - } else { - $scope.ids.push(media.id); - } - }); - - $scope.sync(); - }); - } - } - - setupViewModel(); - - $scope.remove = function(index) { - $scope.mediaItems.splice(index, 1); - $scope.ids.splice(index, 1); - $scope.sync(); - }; - - $scope.goToItem = function(item) { - $location.path('media/media/edit/' + item.id); - }; - - $scope.add = function() { - - $scope.mediaPickerOverlay = { - view: "mediapicker", - title: "Select media", - startNodeId: $scope.model.config.startNodeId, - startNodeIsVirtual: $scope.model.config.startNodeIsVirtual, - ignoreUserStartNodes: ignoreUserStartNodes, - multiPicker: multiPicker, - onlyImages: onlyImages, - disableFolderSelect: disableFolderSelect, - show: true, - submit: function(model) { - - _.each(model.selectedImages, function(media, i) { - // if there is no thumbnail, try getting one if the media is not a placeholder item - if (!media.thumbnail && media.id && media.metaData) { - media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); - } - - $scope.mediaItems.push(media); - - if ($scope.model.config.idType === "udi") { - $scope.ids.push(media.udi); - } - else { - $scope.ids.push(media.id); - } - }); - - $scope.sync(); - - $scope.mediaPickerOverlay.show = false; - $scope.mediaPickerOverlay = null; - } - }; - }; - - $scope.sortableOptions = { - disabled: !$scope.isMultiPicker, - items: "li:not(.add-wrapper)", - cancel: ".unsortable", - 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 - // watch do all the rest. - $timeout(function(){ - angular.forEach($scope.mediaItems, function(value, key) { - r.push($scope.model.config.idType === "udi" ? value.udi : value.id); - }); - $scope.ids = r; - $scope.sync(); - }, 500, false); - } - }; - - $scope.sync = function() { - $scope.model.value = $scope.ids.join(); - }; - - $scope.showAdd = function () { - if (!multiPicker) { - if ($scope.model.value && $scope.model.value !== "") { - return false; - } - } - return true; - }; - - //here we declare a special method which will be called whenever the value has changed from the server - //this is instead of doing a watch on the model.value = faster - $scope.model.onValueChanged = function (newVal, oldVal) { - //update the display val again if it has changed from the server - setupViewModel(); - }; - }); +//this controller simply tells the dialogs service to open a mediaPicker window +//with a specified callback, this callback will receive an object with a selection on it +angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerController", + function ($rootScope, $scope, dialogService, entityResource, mediaResource, mediaHelper, $timeout, userService, $location, localizationService) { + + //check the pre-values for multi-picker + var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false; + var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false; + var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false; + + if (!$scope.model.config.startNodeId) { + + + if ( $scope.model.config.ignoreUserStartNodes === "1") { + $scope.model.config.startNodeId = -1; + $scope.model.config.startNodeIsVirtual = true; + + } else { + userService.getCurrentUser().then(function (userData) { + $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; + }); + } + } + + function setupViewModel() { + $scope.mediaItems = []; + $scope.ids = []; + + $scope.isMultiPicker = multiPicker; + + if ($scope.model.value) { + var ids = $scope.model.value.split(','); + + //NOTE: We need to use the entityResource NOT the mediaResource here because + // the mediaResource has server side auth configured for which the user must have + // access to the media section, if they don't they'll get auth errors. The entityResource + // acts differently in that it allows access if the user has access to any of the apps that + // might require it's use. Therefore we need to use the metaData property to get at the thumbnail + // value. + + entityResource.getByIds(ids, "Media").then(function(medias) { + + // The service only returns item results for ids that exist (deleted items are silently ignored). + // This results in the picked items value to be set to contain only ids of picked items that could actually be found. + // Since a referenced item could potentially be restored later on, instead of changing the selected values here based + // on whether the items exist during a save event - we should keep "placeholder" items for picked items that currently + // could not be fetched. This will preserve references and ensure that the state of an item does not differ depending + // on whether it is simply resaved or not. + // This is done by remapping the int/guid ids into a new array of items, where we create "Deleted item" placeholders + // when there is no match for a selected id. This will ensure that the values being set on save, are the same as before. + + medias = _.map(ids, + function(id) { + var found = _.find(medias, + function(m) { + // We could use coercion (two ='s) here .. but not sure if this works equally well in all browsers and + // it's prone to someone "fixing" it at some point without knowing the effects. Rather use toString() + // compares and be completely sure it works. + return m.udi.toString() === id.toString() || m.id.toString() === id.toString(); + }); + if (found) { + return found; + } else { + return { + name: localizationService.dictionary.mediaPicker_deletedItem, + id: $scope.model.config.idType !== "udi" ? id : null, + udi: $scope.model.config.idType === "udi" ? id : null, + icon: "icon-picture", + thumbnail: null, + trashed: true + }; + } + }); + + _.each(medias, + function(media, i) { + // if there is no thumbnail, try getting one if the media is not a placeholder item + if (!media.thumbnail && media.id && media.metaData) { + media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); + } + + $scope.mediaItems.push(media); + + if ($scope.model.config.idType === "udi") { + $scope.ids.push(media.udi); + } else { + $scope.ids.push(media.id); + } + }); + + $scope.sync(); + }); + } + } + + setupViewModel(); + + $scope.remove = function(index) { + $scope.mediaItems.splice(index, 1); + $scope.ids.splice(index, 1); + $scope.sync(); + }; + + $scope.goToItem = function(item) { + $location.path('media/media/edit/' + item.id); + }; + + $scope.add = function() { + + $scope.mediaPickerOverlay = { + view: "mediapicker", + title: "Select media", + startNodeId: $scope.model.config.startNodeId, + startNodeIsVirtual: $scope.model.config.startNodeIsVirtual, + dataTypeId: $scope.model.dataTypeId, + multiPicker: multiPicker, + onlyImages: onlyImages, + disableFolderSelect: disableFolderSelect, + show: true, + submit: function(model) { + + _.each(model.selectedImages, function(media, i) { + // if there is no thumbnail, try getting one if the media is not a placeholder item + if (!media.thumbnail && media.id && media.metaData) { + media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); + } + + $scope.mediaItems.push(media); + + if ($scope.model.config.idType === "udi") { + $scope.ids.push(media.udi); + } + else { + $scope.ids.push(media.id); + } + }); + + $scope.sync(); + + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + } + }; + }; + + $scope.sortableOptions = { + disabled: !$scope.isMultiPicker, + items: "li:not(.add-wrapper)", + cancel: ".unsortable", + 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 + // watch do all the rest. + $timeout(function(){ + angular.forEach($scope.mediaItems, function(value, key) { + r.push($scope.model.config.idType === "udi" ? value.udi : value.id); + }); + $scope.ids = r; + $scope.sync(); + }, 500, false); + } + }; + + $scope.sync = function() { + $scope.model.value = $scope.ids.join(); + }; + + $scope.showAdd = function () { + if (!multiPicker) { + if ($scope.model.value && $scope.model.value !== "") { + return false; + } + } + return true; + }; + + //here we declare a special method which will be called whenever the value has changed from the server + //this is instead of doing a watch on the model.value = faster + $scope.model.onValueChanged = function (newVal, oldVal) { + //update the display val again if it has changed from the server + setupViewModel(); + }; + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js index c7e67a0a42..124f342741 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js @@ -67,11 +67,12 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en url: link.url, target: link.target } : null; - + $scope.linkPickerOverlay = { view: "linkpicker", currentTarget: target, - ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1", + dataTypeId: $scope.model.dataTypeId, + ignoreUserStartNodes : $scope.model.config.ignoreUserStartNodes, show: true, submit: function (model) { if (model.target.url || model.target.anchor) { 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 6047169c54..26c6a0c051 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,242 +1,242 @@ -angular.module("umbraco") - .controller("Umbraco.PropertyEditors.RelatedLinksController", - function ($rootScope, $scope, dialogService, iconHelper) { - - if (!$scope.model.value) { - $scope.model.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://'; - $scope.newNewWindow = false; - $scope.newInternal = null; - $scope.newInternalName = ''; - $scope.newInternalIcon = null; - $scope.addExternal = true; - $scope.currentEditLink = null; - $scope.hasError = false; - - $scope.internal = function($event) { - $scope.currentEditLink = null; - - $scope.contentPickerOverlay = {}; - $scope.contentPickerOverlay.view = "contentpicker"; - $scope.contentPickerOverlay.multiPicker = false; - $scope.contentPickerOverlay.show = true; - $scope.contentPickerOverlay.ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true: false; - $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; - - $scope.contentPickerOverlay.submit = function(model) { - - select(model.selection[0]); - - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - }; - - $scope.contentPickerOverlay.close = function(oldModel) { - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - }; - - $event.preventDefault(); - }; - - $scope.selectInternal = function ($event, link) { - $scope.currentEditLink = link; - - $scope.contentPickerOverlay = {}; - $scope.contentPickerOverlay.view = "contentpicker"; - $scope.contentPickerOverlay.multiPicker = false; - $scope.contentPickerOverlay.show = true; - $scope.contentPickerOverlay.ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false; - $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; - - $scope.contentPickerOverlay.submit = function(model) { - - select(model.selection[0]); - - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - }; - - $scope.contentPickerOverlay.close = function(oldModel) { - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - }; - - $event.preventDefault(); - - }; - - $scope.edit = function (idx) { - for (var i = 0; i < $scope.model.value.length; i++) { - $scope.model.value[i].edit = false; - } - $scope.model.value[idx].edit = true; - }; - - $scope.saveEdit = function (idx) { - $scope.model.value[idx].title = $scope.model.value[idx].caption; - $scope.model.value[idx].edit = false; - }; - - $scope.delete = function (idx) { - $scope.model.value.splice(idx, 1); - }; - - $scope.add = function ($event) { - if (!angular.isArray($scope.model.value)) { - $scope.model.value = []; - } - - if ($scope.newCaption == "") { - $scope.hasError = true; - } else { - if ($scope.addExternal) { - var newExtLink = new function() { - this.caption = $scope.newCaption; - this.link = $scope.newLink; - this.newWindow = $scope.newNewWindow; - this.edit = false; - this.isInternal = false; - this.type = "external"; - this.title = $scope.newCaption; - }; - $scope.model.value.push(newExtLink); - } else { - var newIntLink = new function() { - this.caption = $scope.newCaption; - this.link = $scope.newInternal; - this.newWindow = $scope.newNewWindow; - this.internal = $scope.newInternal; - this.edit = false; - this.isInternal = true; - this.internalName = $scope.newInternalName; - this.internalIcon = $scope.newInternalIcon; - this.type = "internal"; - this.title = $scope.newCaption; - }; - $scope.model.value.push(newIntLink); - } - $scope.newCaption = ''; - $scope.newLink = 'http://'; - $scope.newNewWindow = false; - $scope.newInternal = null; - $scope.newInternalName = ''; - $scope.newInternalIcon = null; - } - $event.preventDefault(); - }; - - $scope.switch = function ($event) { - $scope.addExternal = !$scope.addExternal; - $event.preventDefault(); - }; - - $scope.switchLinkType = function ($event, link) { - link.isInternal = !link.isInternal; - link.type = link.isInternal ? "internal" : "external"; - if (!link.isInternal) - link.link = $scope.newLink; - $event.preventDefault(); - }; - - $scope.move = function (index, direction) { - var temp = $scope.model.value[index]; - $scope.model.value[index] = $scope.model.value[index + direction]; - $scope.model.value[index + direction] = temp; - }; - - //helper for determining if a user can add items - $scope.canAdd = function () { - return $scope.model.config.max <= 0 || $scope.model.config.max > countVisible(); - } - - //helper that returns if an item can be sorted - $scope.canSort = function () { - return countVisible() > 1; - } - - $scope.sortableOptions = { - axis: 'y', - handle: '.handle', - 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: https://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/ - ui.children().each(function () { - $(this).width($(this).width()); - }); - return ui; - }, - 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) - var newIndex = ui.item.index(); - var movedLinkUrl = ui.item.attr('data-link'); - var originalIndex = getElementIndexByUrl(movedLinkUrl); - - // Move the element in the model - 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: https://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 - and set height of tr - ui.placeholder.html('').height(ui.item.height()); - } - }; - - //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) { - return i; - } - } - - return -1; - } - - function select(data) { - if ($scope.currentEditLink != null) { - $scope.currentEditLink.internal = $scope.model.config.idType === "udi" ? data.udi : data.id; - $scope.currentEditLink.internalName = data.name; - $scope.currentEditLink.internalIcon = iconHelper.convertFromLegacyIcon(data.icon); - $scope.currentEditLink.link = $scope.model.config.idType === "udi" ? data.udi : data.id; - } else { - $scope.newInternal = $scope.model.config.idType === "udi" ? data.udi : data.id; - $scope.newInternalName = data.name; - $scope.newInternalIcon = iconHelper.convertFromLegacyIcon(data.icon); - } - } - }); +angular.module("umbraco") + .controller("Umbraco.PropertyEditors.RelatedLinksController", + function ($rootScope, $scope, dialogService, iconHelper) { + + if (!$scope.model.value) { + $scope.model.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://'; + $scope.newNewWindow = false; + $scope.newInternal = null; + $scope.newInternalName = ''; + $scope.newInternalIcon = null; + $scope.addExternal = true; + $scope.currentEditLink = null; + $scope.hasError = false; + + $scope.internal = function($event) { + $scope.currentEditLink = null; + + $scope.contentPickerOverlay = {}; + $scope.contentPickerOverlay.view = "contentpicker"; + $scope.contentPickerOverlay.multiPicker = false; + $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.dataTypeId = $scope.model.dataTypeId; + $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; + + $scope.contentPickerOverlay.submit = function(model) { + + select(model.selection[0]); + + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + + $scope.contentPickerOverlay.close = function(oldModel) { + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + + $event.preventDefault(); + }; + + $scope.selectInternal = function ($event, link) { + $scope.currentEditLink = link; + + $scope.contentPickerOverlay = {}; + $scope.contentPickerOverlay.view = "contentpicker"; + $scope.contentPickerOverlay.multiPicker = false; + $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.dataTypeId = $scope.model.dataTypeId; + $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; + + $scope.contentPickerOverlay.submit = function(model) { + + select(model.selection[0]); + + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + + $scope.contentPickerOverlay.close = function(oldModel) { + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + }; + + $event.preventDefault(); + + }; + + $scope.edit = function (idx) { + for (var i = 0; i < $scope.model.value.length; i++) { + $scope.model.value[i].edit = false; + } + $scope.model.value[idx].edit = true; + }; + + $scope.saveEdit = function (idx) { + $scope.model.value[idx].title = $scope.model.value[idx].caption; + $scope.model.value[idx].edit = false; + }; + + $scope.delete = function (idx) { + $scope.model.value.splice(idx, 1); + }; + + $scope.add = function ($event) { + if (!angular.isArray($scope.model.value)) { + $scope.model.value = []; + } + + if ($scope.newCaption == "") { + $scope.hasError = true; + } else { + if ($scope.addExternal) { + var newExtLink = new function() { + this.caption = $scope.newCaption; + this.link = $scope.newLink; + this.newWindow = $scope.newNewWindow; + this.edit = false; + this.isInternal = false; + this.type = "external"; + this.title = $scope.newCaption; + }; + $scope.model.value.push(newExtLink); + } else { + var newIntLink = new function() { + this.caption = $scope.newCaption; + this.link = $scope.newInternal; + this.newWindow = $scope.newNewWindow; + this.internal = $scope.newInternal; + this.edit = false; + this.isInternal = true; + this.internalName = $scope.newInternalName; + this.internalIcon = $scope.newInternalIcon; + this.type = "internal"; + this.title = $scope.newCaption; + }; + $scope.model.value.push(newIntLink); + } + $scope.newCaption = ''; + $scope.newLink = 'http://'; + $scope.newNewWindow = false; + $scope.newInternal = null; + $scope.newInternalName = ''; + $scope.newInternalIcon = null; + } + $event.preventDefault(); + }; + + $scope.switch = function ($event) { + $scope.addExternal = !$scope.addExternal; + $event.preventDefault(); + }; + + $scope.switchLinkType = function ($event, link) { + link.isInternal = !link.isInternal; + link.type = link.isInternal ? "internal" : "external"; + if (!link.isInternal) + link.link = $scope.newLink; + $event.preventDefault(); + }; + + $scope.move = function (index, direction) { + var temp = $scope.model.value[index]; + $scope.model.value[index] = $scope.model.value[index + direction]; + $scope.model.value[index + direction] = temp; + }; + + //helper for determining if a user can add items + $scope.canAdd = function () { + return $scope.model.config.max <= 0 || $scope.model.config.max > countVisible(); + } + + //helper that returns if an item can be sorted + $scope.canSort = function () { + return countVisible() > 1; + } + + $scope.sortableOptions = { + axis: 'y', + handle: '.handle', + 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: https://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/ + ui.children().each(function () { + $(this).width($(this).width()); + }); + return ui; + }, + 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) + var newIndex = ui.item.index(); + var movedLinkUrl = ui.item.attr('data-link'); + var originalIndex = getElementIndexByUrl(movedLinkUrl); + + // Move the element in the model + 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: https://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 - and set height of tr + ui.placeholder.html('').height(ui.item.height()); + } + }; + + //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) { + return i; + } + } + + return -1; + } + + function select(data) { + if ($scope.currentEditLink != null) { + $scope.currentEditLink.internal = $scope.model.config.idType === "udi" ? data.udi : data.id; + $scope.currentEditLink.internalName = data.name; + $scope.currentEditLink.internalIcon = iconHelper.convertFromLegacyIcon(data.icon); + $scope.currentEditLink.link = $scope.model.config.idType === "udi" ? data.udi : data.id; + } else { + $scope.newInternal = $scope.model.config.idType === "udi" ? data.udi : data.id; + $scope.newInternalName = data.name; + $scope.newInternalIcon = iconHelper.convertFromLegacyIcon(data.icon); + } + } + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index f8dea2ee8b..cadbc5f57a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -1,403 +1,402 @@ -angular.module("umbraco") - .controller("Umbraco.PropertyEditors.RTEController", - function ($rootScope, $scope, $q, $locale, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService, editorState) { - - $scope.isLoading = true; - - //To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias - // because now we have to support having 2x (maybe more at some stage) content editors being displayed at once. This is because - // we have this mini content editor panel that can be launched with MNTP. - var d = new Date(); - var n = d.getTime(); - $scope.textAreaHtmlId = $scope.model.alias + "_" + n + "_rte"; - - function syncContent(editor){ - editor.save(); - angularHelper.safeApply($scope, function () { - $scope.model.value = editor.getContent(); - }); - - //make the form dirty manually so that the track changes works, setting our model doesn't trigger - // the angular bits because tinymce replaces the textarea. - angularHelper.getCurrentForm($scope).$setDirty(); - } - - tinyMceService.configuration().then(function (tinyMceConfig) { - - //config value from general tinymce.config file - var validElements = tinyMceConfig.validElements; - - //These are absolutely required in order for the macros to render inline - //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce - var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],span[id|class|style]"; - - var invalidElements = tinyMceConfig.inValidElements; - var plugins = _.map(tinyMceConfig.plugins, function (plugin) { - if (plugin.useOnFrontend) { - return plugin.name; - } - }).join(" "); - - var editorConfig = $scope.model.config.editor; - if (!editorConfig || angular.isString(editorConfig)) { - editorConfig = tinyMceService.defaultPrevalues(); - } - - //config value on the data type - var toolbar = editorConfig.toolbar.join(" | "); - var stylesheets = []; - var styleFormats = []; - var await = []; - if (!editorConfig.maxImageSize && editorConfig.maxImageSize != 0) { - editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize; - } - - //queue file loading - if (typeof tinymce === "undefined") { // Don't reload tinymce if already loaded - await.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", $scope)); - } - - //queue rules loading - angular.forEach(editorConfig.stylesheets, function (val, key) { - stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/" + val + ".css?" + new Date().getTime()); - await.push(stylesheetResource.getRulesByName(val).then(function (rules) { - angular.forEach(rules, function (rule) { - var r = {}; - r.title = rule.name; - if (rule.selector[0] == ".") { - r.inline = "span"; - r.classes = rule.selector.substring(1); - } - else if (rule.selector[0] == "#") { - r.inline = "span"; - r.attributes = { id: rule.selector.substring(1) }; - } - else if (rule.selector[0] != "." && rule.selector.indexOf(".") > -1) { - var split = rule.selector.split("."); - r.block = split[0]; - r.classes = rule.selector.substring(rule.selector.indexOf(".") + 1).replace(".", " "); - } - else if (rule.selector[0] != "#" && rule.selector.indexOf("#") > -1) { - var split = rule.selector.split("#"); - r.block = split[0]; - r.classes = rule.selector.substring(rule.selector.indexOf("#") + 1); - } - else { - r.block = rule.selector; - } - - styleFormats.push(r); - }); - })); - }); - - - //stores a reference to the editor - var tinyMceEditor = null; - - // these languages are available for localization - var availableLanguages = [ - 'da', - 'de', - 'en', - 'en_us', - 'fi', - 'fr', - 'he', - 'it', - 'ja', - 'nl', - 'no', - 'pl', - 'pt', - 'ru', - 'sv', - 'zh' - ]; - - //define fallback language - var language = 'en_us'; - //get locale from angular and match tinymce format. Angular localization is always in the format of ru-ru, de-de, en-gb, etc. - //wheras tinymce is in the format of ru, de, en, en_us, etc. - var localeId = $locale.id.replace('-', '_'); - //try matching the language using full locale format - var languageMatch = _.find(availableLanguages, function(o) { return o === localeId; }); - //if no matches, try matching using only the language - if (languageMatch === undefined) { - var localeParts = localeId.split('_'); - languageMatch = _.find(availableLanguages, function(o) { return o === localeParts[0]; }); - } - //if a match was found - set the language - if (languageMatch !== undefined) { - language = languageMatch; - } - - //wait for queue to end - $q.all(await).then(function () { - - //create a baseline Config to exten upon - var baseLineConfigObj = { - mode: "exact", - skin: "umbraco", - plugins: plugins, - valid_elements: validElements, - invalid_elements: invalidElements, - extended_valid_elements: extendedValidElements, - menubar: false, - statusbar: false, - relative_urls: false, - height: editorConfig.dimensions.height, - width: editorConfig.dimensions.width, - maxImageSize: editorConfig.maxImageSize, - toolbar: toolbar, - content_css: stylesheets, - style_formats: styleFormats, - language: language, - //see http://archive.tinymce.com/wiki.php/Configuration:cache_suffix - cache_suffix: "?umb__rnd=" + Umbraco.Sys.ServerVariables.application.cacheBuster - }; - - if (tinyMceConfig.customConfig) { - - //if there is some custom config, we need to see if the string value of each item might actually be json and if so, we need to - // convert it to json instead of having it as a string since this is what tinymce requires - for (var i in tinyMceConfig.customConfig) { - var val = tinyMceConfig.customConfig[i]; - if (val) { - val = val.toString().trim(); - if (val.detectIsJson()) { - try { - tinyMceConfig.customConfig[i] = JSON.parse(val); - //now we need to check if this custom config key is defined in our baseline, if it is we don't want to - //overwrite the baseline config item if it is an array, we want to concat the items in the array, otherwise - //if it's an object it will overwrite the baseline - if (angular.isArray(baseLineConfigObj[i]) && angular.isArray(tinyMceConfig.customConfig[i])) { - //concat it and below this concat'd array will overwrite the baseline in angular.extend - tinyMceConfig.customConfig[i] = baseLineConfigObj[i].concat(tinyMceConfig.customConfig[i]); - } - } - catch (e) { - //cannot parse, we'll just leave it - } - } - if (val === "true") { - tinyMceConfig.customConfig[i] = true; - } - if (val === "false") { - tinyMceConfig.customConfig[i] = false; - } - } - } - - angular.extend(baseLineConfigObj, tinyMceConfig.customConfig); - } - - //set all the things that user configs should not be able to override - baseLineConfigObj.elements = $scope.textAreaHtmlId; //this is the exact textarea id to replace! - baseLineConfigObj.setup = function (editor) { - - //set the reference - tinyMceEditor = editor; - - //enable browser based spell checking - editor.on('init', function (e) { - editor.getBody().setAttribute('spellcheck', true); - }); - - //We need to listen on multiple things here because of the nature of tinymce, it doesn't - //fire events when you think! - //The change event doesn't fire when content changes, only when cursor points are changed and undo points - //are created. the blur event doesn't fire if you insert content into the editor with a button and then - //press save. - //We have a couple of options, one is to do a set timeout and check for isDirty on the editor, or we can - //listen to both change and blur and also on our own 'saving' event. I think this will be best because a - //timer might end up using unwanted cpu and we'd still have to listen to our saving event in case they clicked - //save before the timeout elapsed. - - //TODO: We need to re-enable something like this to ensure the track changes is working with tinymce - // so we can detect if the form is dirty or not, Per has some better events to use as this one triggers - // even if you just enter/exit with mouse cursuor which doesn't really mean it's changed. - // see: http://issues.umbraco.org/issue/U4-4485 - //var alreadyDirty = false; - //editor.on('change', function (e) { - // angularHelper.safeApply($scope, function () { - // $scope.model.value = editor.getContent(); - - // if (!alreadyDirty) { - // //make the form dirty manually so that the track changes works, setting our model doesn't trigger - // // the angular bits because tinymce replaces the textarea. - // var currForm = angularHelper.getCurrentForm($scope); - // currForm.$setDirty(); - // alreadyDirty = true; - // } - - // }); - //}); - - //when we leave the editor (maybe) - editor.on('blur', function (e) { - editor.save(); - angularHelper.safeApply($scope, function () { - $scope.model.value = editor.getContent(); - }); - }); - - //when buttons modify content - editor.on('ExecCommand', function (e) { - syncContent(editor); - }); - - // Update model on keypress - editor.on('KeyUp', function (e) { - syncContent(editor); - }); - - // Update model on change, i.e. copy/pasted text, plugins altering content - editor.on('SetContent', function (e) { - if (!e.initial) { - syncContent(editor); - } - }); - - - editor.on('ObjectResized', function (e) { - var qs = "?width=" + e.width + "&height=" + e.height + "&mode=max"; - var srcAttr = $(e.target).attr("src"); - var path = srcAttr.split("?")[0]; - $(e.target).attr("data-mce-src", path + qs); - - syncContent(editor); - }); - - tinyMceService.createLinkPicker(editor, $scope, function(currentTarget, anchorElement) { - $scope.linkPickerOverlay = { - view: "linkpicker", - currentTarget: currentTarget, - anchors: editorState.current ? tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)) : [], - ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1", - show: true, - submit: function(model) { - tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); - $scope.linkPickerOverlay.show = false; - $scope.linkPickerOverlay = null; - } - }; - }); - - //Create the insert media plugin - tinyMceService.createMediaPicker(editor, $scope, function(currentTarget, userData){ - var ignoreUserStartNodes = false; - var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - var startNodeIsVirtual = userData.startMediaIds.length !== 1; - - if ($scope.model.config.ignoreUserStartNodes === "1") { - ignoreUserStartNodes = true; - startNodeId = -1; - startNodeIsVirtual = true; - } - - $scope.mediaPickerOverlay = { - currentTarget: currentTarget, - onlyImages: true, - showDetails: true, - disableFolderSelect: true, - startNodeId: startNodeId, - startNodeIsVirtual: startNodeIsVirtual, - ignoreUserStartNodes: ignoreUserStartNodes, - view: "mediapicker", - show: true, - submit: function(model) { - tinyMceService.insertMediaInEditor(editor, model.selectedImages[0]); - $scope.mediaPickerOverlay.show = false; - $scope.mediaPickerOverlay = null; - } - }; - - }); - - //Create the embedded plugin - tinyMceService.createInsertEmbeddedMedia(editor, $scope, function() { - - $scope.embedOverlay = { - view: "embed", - show: true, - submit: function(model) { - tinyMceService.insertEmbeddedMediaInEditor(editor, model.embed.preview); - $scope.embedOverlay.show = false; - $scope.embedOverlay = null; - } - }; - - }); - - - //Create the insert macro plugin - tinyMceService.createInsertMacro(editor, $scope, function(dialogData) { - - $scope.macroPickerOverlay = { - view: "macropicker", - dialogData: dialogData, - show: true, - submit: function(model) { - var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine); - tinyMceService.insertMacroInEditor(editor, macroObject, $scope); - $scope.macroPickerOverlay.show = false; - $scope.macroPickerOverlay = null; - } - }; - - }); - }; - - /** Loads in the editor */ - function loadTinyMce() { - - //we need to add a timeout here, to force a redraw so TinyMCE can find - //the elements needed - $timeout(function () { - tinymce.DOM.events.domLoaded = true; - tinymce.init(baseLineConfigObj); - - $scope.isLoading = false; - - }, 200, false); - } - - - - - loadTinyMce(); - - //here we declare a special method which will be called whenever the value has changed from the server - //this is instead of doing a watch on the model.value = faster - $scope.model.onValueChanged = function (newVal, oldVal) { - //update the display val again if it has changed from the server; - //uses an empty string in the editor when the value is null - tinyMceEditor.setContent(newVal || "", { format: 'raw' }); - //we need to manually fire this event since it is only ever fired based on loading from the DOM, this - // is required for our plugins listening to this event to execute - tinyMceEditor.fire('LoadContent', null); - }; - - //listen for formSubmitting event (the result is callback used to remove the event subscription) - var unsubscribe = $scope.$on("formSubmitting", function () { - //TODO: Here we should parse out the macro rendered content so we can save on a lot of bytes in data xfer - // we do parse it out on the server side but would be nice to do that on the client side before as well. - if (tinyMceEditor !== undefined && tinyMceEditor != null && !$scope.isLoading) { - $scope.model.value = tinyMceEditor.getContent(); - } - }); - - //when the element is disposed we need to unsubscribe! - // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom - // element might still be there even after the modal has been hidden. - $scope.$on('$destroy', function () { - unsubscribe(); - if (tinyMceEditor !== undefined && tinyMceEditor != null) { - tinyMceEditor.destroy(); - } - }); - }); - }); - - }); +angular.module("umbraco") + .controller("Umbraco.PropertyEditors.RTEController", + function ($rootScope, $scope, $q, $locale, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService, editorState) { + + $scope.isLoading = true; + + //To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias + // because now we have to support having 2x (maybe more at some stage) content editors being displayed at once. This is because + // we have this mini content editor panel that can be launched with MNTP. + var d = new Date(); + var n = d.getTime(); + $scope.textAreaHtmlId = $scope.model.alias + "_" + n + "_rte"; + + function syncContent(editor){ + editor.save(); + angularHelper.safeApply($scope, function () { + $scope.model.value = editor.getContent(); + }); + + //make the form dirty manually so that the track changes works, setting our model doesn't trigger + // the angular bits because tinymce replaces the textarea. + angularHelper.getCurrentForm($scope).$setDirty(); + } + + tinyMceService.configuration().then(function (tinyMceConfig) { + + //config value from general tinymce.config file + var validElements = tinyMceConfig.validElements; + + //These are absolutely required in order for the macros to render inline + //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce + var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],span[id|class|style]"; + + var invalidElements = tinyMceConfig.inValidElements; + var plugins = _.map(tinyMceConfig.plugins, function (plugin) { + if (plugin.useOnFrontend) { + return plugin.name; + } + }).join(" "); + + var editorConfig = $scope.model.config.editor; + if (!editorConfig || angular.isString(editorConfig)) { + editorConfig = tinyMceService.defaultPrevalues(); + } + + //config value on the data type + var toolbar = editorConfig.toolbar.join(" | "); + var stylesheets = []; + var styleFormats = []; + var await = []; + if (!editorConfig.maxImageSize && editorConfig.maxImageSize != 0) { + editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize; + } + + //queue file loading + if (typeof tinymce === "undefined") { // Don't reload tinymce if already loaded + await.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", $scope)); + } + + //queue rules loading + angular.forEach(editorConfig.stylesheets, function (val, key) { + stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + "/" + val + ".css?" + new Date().getTime()); + await.push(stylesheetResource.getRulesByName(val).then(function (rules) { + angular.forEach(rules, function (rule) { + var r = {}; + r.title = rule.name; + if (rule.selector[0] == ".") { + r.inline = "span"; + r.classes = rule.selector.substring(1); + } + else if (rule.selector[0] == "#") { + r.inline = "span"; + r.attributes = { id: rule.selector.substring(1) }; + } + else if (rule.selector[0] != "." && rule.selector.indexOf(".") > -1) { + var split = rule.selector.split("."); + r.block = split[0]; + r.classes = rule.selector.substring(rule.selector.indexOf(".") + 1).replace(".", " "); + } + else if (rule.selector[0] != "#" && rule.selector.indexOf("#") > -1) { + var split = rule.selector.split("#"); + r.block = split[0]; + r.classes = rule.selector.substring(rule.selector.indexOf("#") + 1); + } + else { + r.block = rule.selector; + } + + styleFormats.push(r); + }); + })); + }); + + + //stores a reference to the editor + var tinyMceEditor = null; + + // these languages are available for localization + var availableLanguages = [ + 'da', + 'de', + 'en', + 'en_us', + 'fi', + 'fr', + 'he', + 'it', + 'ja', + 'nl', + 'no', + 'pl', + 'pt', + 'ru', + 'sv', + 'zh' + ]; + + //define fallback language + var language = 'en_us'; + //get locale from angular and match tinymce format. Angular localization is always in the format of ru-ru, de-de, en-gb, etc. + //wheras tinymce is in the format of ru, de, en, en_us, etc. + var localeId = $locale.id.replace('-', '_'); + //try matching the language using full locale format + var languageMatch = _.find(availableLanguages, function(o) { return o === localeId; }); + //if no matches, try matching using only the language + if (languageMatch === undefined) { + var localeParts = localeId.split('_'); + languageMatch = _.find(availableLanguages, function(o) { return o === localeParts[0]; }); + } + //if a match was found - set the language + if (languageMatch !== undefined) { + language = languageMatch; + } + + //wait for queue to end + $q.all(await).then(function () { + + //create a baseline Config to exten upon + var baseLineConfigObj = { + mode: "exact", + skin: "umbraco", + plugins: plugins, + valid_elements: validElements, + invalid_elements: invalidElements, + extended_valid_elements: extendedValidElements, + menubar: false, + statusbar: false, + relative_urls: false, + height: editorConfig.dimensions.height, + width: editorConfig.dimensions.width, + maxImageSize: editorConfig.maxImageSize, + toolbar: toolbar, + content_css: stylesheets, + style_formats: styleFormats, + language: language, + //see http://archive.tinymce.com/wiki.php/Configuration:cache_suffix + cache_suffix: "?umb__rnd=" + Umbraco.Sys.ServerVariables.application.cacheBuster + }; + + if (tinyMceConfig.customConfig) { + + //if there is some custom config, we need to see if the string value of each item might actually be json and if so, we need to + // convert it to json instead of having it as a string since this is what tinymce requires + for (var i in tinyMceConfig.customConfig) { + var val = tinyMceConfig.customConfig[i]; + if (val) { + val = val.toString().trim(); + if (val.detectIsJson()) { + try { + tinyMceConfig.customConfig[i] = JSON.parse(val); + //now we need to check if this custom config key is defined in our baseline, if it is we don't want to + //overwrite the baseline config item if it is an array, we want to concat the items in the array, otherwise + //if it's an object it will overwrite the baseline + if (angular.isArray(baseLineConfigObj[i]) && angular.isArray(tinyMceConfig.customConfig[i])) { + //concat it and below this concat'd array will overwrite the baseline in angular.extend + tinyMceConfig.customConfig[i] = baseLineConfigObj[i].concat(tinyMceConfig.customConfig[i]); + } + } + catch (e) { + //cannot parse, we'll just leave it + } + } + if (val === "true") { + tinyMceConfig.customConfig[i] = true; + } + if (val === "false") { + tinyMceConfig.customConfig[i] = false; + } + } + } + + angular.extend(baseLineConfigObj, tinyMceConfig.customConfig); + } + + //set all the things that user configs should not be able to override + baseLineConfigObj.elements = $scope.textAreaHtmlId; //this is the exact textarea id to replace! + baseLineConfigObj.setup = function (editor) { + + //set the reference + tinyMceEditor = editor; + + //enable browser based spell checking + editor.on('init', function (e) { + editor.getBody().setAttribute('spellcheck', true); + }); + + //We need to listen on multiple things here because of the nature of tinymce, it doesn't + //fire events when you think! + //The change event doesn't fire when content changes, only when cursor points are changed and undo points + //are created. the blur event doesn't fire if you insert content into the editor with a button and then + //press save. + //We have a couple of options, one is to do a set timeout and check for isDirty on the editor, or we can + //listen to both change and blur and also on our own 'saving' event. I think this will be best because a + //timer might end up using unwanted cpu and we'd still have to listen to our saving event in case they clicked + //save before the timeout elapsed. + + //TODO: We need to re-enable something like this to ensure the track changes is working with tinymce + // so we can detect if the form is dirty or not, Per has some better events to use as this one triggers + // even if you just enter/exit with mouse cursuor which doesn't really mean it's changed. + // see: http://issues.umbraco.org/issue/U4-4485 + //var alreadyDirty = false; + //editor.on('change', function (e) { + // angularHelper.safeApply($scope, function () { + // $scope.model.value = editor.getContent(); + + // if (!alreadyDirty) { + // //make the form dirty manually so that the track changes works, setting our model doesn't trigger + // // the angular bits because tinymce replaces the textarea. + // var currForm = angularHelper.getCurrentForm($scope); + // currForm.$setDirty(); + // alreadyDirty = true; + // } + + // }); + //}); + + //when we leave the editor (maybe) + editor.on('blur', function (e) { + editor.save(); + angularHelper.safeApply($scope, function () { + $scope.model.value = editor.getContent(); + }); + }); + + //when buttons modify content + editor.on('ExecCommand', function (e) { + syncContent(editor); + }); + + // Update model on keypress + editor.on('KeyUp', function (e) { + syncContent(editor); + }); + + // Update model on change, i.e. copy/pasted text, plugins altering content + editor.on('SetContent', function (e) { + if (!e.initial) { + syncContent(editor); + } + }); + + + editor.on('ObjectResized', function (e) { + var qs = "?width=" + e.width + "&height=" + e.height + "&mode=max"; + var srcAttr = $(e.target).attr("src"); + var path = srcAttr.split("?")[0]; + $(e.target).attr("data-mce-src", path + qs); + + syncContent(editor); + }); + + tinyMceService.createLinkPicker(editor, $scope, function(currentTarget, anchorElement) { + $scope.linkPickerOverlay = { + view: "linkpicker", + currentTarget: currentTarget, + anchors: editorState.current ? tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)) : [], + dataTypeId: $scope.model.dataTypeId, + ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes, + show: true, + submit: function(model) { + tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); + $scope.linkPickerOverlay.show = false; + $scope.linkPickerOverlay = null; + } + }; + }); + + //Create the insert media plugin + tinyMceService.createMediaPicker(editor, $scope, function(currentTarget, userData){ + var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + var startNodeIsVirtual = userData.startMediaIds.length !== 1; + + if ($scope.model.config.ignoreUserStartNodes === "1") { + startNodeId = -1; + startNodeIsVirtual = true; + } + + $scope.mediaPickerOverlay = { + currentTarget: currentTarget, + onlyImages: true, + showDetails: true, + disableFolderSelect: true, + startNodeId: startNodeId, + startNodeIsVirtual: startNodeIsVirtual, + dataTypeId: $scope.model.dataTypeId, + view: "mediapicker", + show: true, + submit: function(model) { + tinyMceService.insertMediaInEditor(editor, model.selectedImages[0]); + $scope.mediaPickerOverlay.show = false; + $scope.mediaPickerOverlay = null; + } + }; + + }); + + //Create the embedded plugin + tinyMceService.createInsertEmbeddedMedia(editor, $scope, function() { + + $scope.embedOverlay = { + view: "embed", + show: true, + submit: function(model) { + tinyMceService.insertEmbeddedMediaInEditor(editor, model.embed.preview); + $scope.embedOverlay.show = false; + $scope.embedOverlay = null; + } + }; + + }); + + + //Create the insert macro plugin + tinyMceService.createInsertMacro(editor, $scope, function(dialogData) { + + $scope.macroPickerOverlay = { + view: "macropicker", + dialogData: dialogData, + show: true, + submit: function(model) { + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine); + tinyMceService.insertMacroInEditor(editor, macroObject, $scope); + $scope.macroPickerOverlay.show = false; + $scope.macroPickerOverlay = null; + } + }; + + }); + }; + + /** Loads in the editor */ + function loadTinyMce() { + + //we need to add a timeout here, to force a redraw so TinyMCE can find + //the elements needed + $timeout(function () { + tinymce.DOM.events.domLoaded = true; + tinymce.init(baseLineConfigObj); + + $scope.isLoading = false; + + }, 200, false); + } + + + + + loadTinyMce(); + + //here we declare a special method which will be called whenever the value has changed from the server + //this is instead of doing a watch on the model.value = faster + $scope.model.onValueChanged = function (newVal, oldVal) { + //update the display val again if it has changed from the server; + //uses an empty string in the editor when the value is null + tinyMceEditor.setContent(newVal || "", { format: 'raw' }); + //we need to manually fire this event since it is only ever fired based on loading from the DOM, this + // is required for our plugins listening to this event to execute + tinyMceEditor.fire('LoadContent', null); + }; + + //listen for formSubmitting event (the result is callback used to remove the event subscription) + var unsubscribe = $scope.$on("formSubmitting", function () { + //TODO: Here we should parse out the macro rendered content so we can save on a lot of bytes in data xfer + // we do parse it out on the server side but would be nice to do that on the client side before as well. + if (tinyMceEditor !== undefined && tinyMceEditor != null && !$scope.isLoading) { + $scope.model.value = tinyMceEditor.getContent(); + } + }); + + //when the element is disposed we need to unsubscribe! + // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom + // element might still be there even after the modal has been hidden. + $scope.$on('$destroy', function () { + unsubscribe(); + if (tinyMceEditor !== undefined && tinyMceEditor != null) { + tinyMceEditor.destroy(); + } + }); + }); + }); + + }); diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index b8d9c8bcb6..c909c1eb84 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1,1227 +1,1227 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Web.Http; -using System.Web.Http.Controllers; -using System.Web.Http.ModelBinding; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Publishing; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Models.Mapping; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Binders; -using Umbraco.Web.WebApi.Filters; -using Umbraco.Core.Events; -using Constants = Umbraco.Core.Constants; -using umbraco.cms.businesslogic; -using System.Collections; -using umbraco; - -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")] - [UmbracoApplicationAuthorize(Constants.Applications.Content)] - [ContentControllerConfiguration] - public class ContentController : ContentControllerBase - { - /// - /// Constructor - /// - public ContentController() - : this(UmbracoContext.Current) - { - } - - /// - /// Constructor - /// - /// - public ContentController(UmbracoContext umbracoContext) - : base(umbracoContext) - { - } - - /// - /// Configures this controller with a custom action selector - /// - private class ContentControllerConfigurationAttribute : Attribute, IControllerConfiguration - { - public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) - { - controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetNiceUrl", "id", typeof(int), typeof(Guid), typeof(Udi)), - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)) - )); - } - } - - /// - /// Return content for the specified ids - /// - /// - /// - [FilterAllowedOutgoingContent(typeof(IEnumerable))] - public IEnumerable GetByIds([FromUri]int[] ids) - { - var foundContent = Services.ContentService.GetByIds(ids); - return foundContent.Select(content => AutoMapperExtensions.MapWithUmbracoContext(content, UmbracoContext)); - } - - /// - /// Updates the permissions for a content item for a particular user group - /// - /// - /// - /// - /// Permission check is done for letter 'R' which is for which the user must have access to to update - /// - [EnsureUserPermissionForContent("saveModel.ContentId", 'R')] - public IEnumerable PostSaveUserGroupPermissions(UserGroupPermissionsSave saveModel) - { - if (saveModel.ContentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - //TODO: Should non-admins be alowed to set granular permissions? - - var content = Services.ContentService.GetById(saveModel.ContentId); - if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - //current permissions explicitly assigned to this content item - var contentPermissions = Services.ContentService.GetPermissionsForEntity(content) - .ToDictionary(x => x.UserGroupId, x => x); - - var allUserGroups = Services.UserService.GetAllUserGroups().ToArray(); - - //loop through each user group - foreach (var userGroup in allUserGroups) - { - //check if there's a permission set posted up for this user group - IEnumerable groupPermissions; - if (saveModel.AssignedPermissions.TryGetValue(userGroup.Id, out groupPermissions)) - { - //create a string collection of the assigned letters - var groupPermissionCodes = groupPermissions.ToArray(); - - //check if there are no permissions assigned for this group save model, if that is the case we want to reset the permissions - //for this group/node which will go back to the defaults - if (groupPermissionCodes.Length == 0) - { - Services.UserService.RemoveUserGroupPermissions(userGroup.Id, content.Id); - } - //check if they are the defaults, if so we should just remove them if they exist since it's more overhead having them stored - else if (userGroup.Permissions.UnsortedSequenceEqual(groupPermissionCodes)) - { - //only remove them if they are actually currently assigned - if (contentPermissions.ContainsKey(userGroup.Id)) - { - //remove these permissions from this node for this group since the ones being assigned are the same as the defaults - Services.UserService.RemoveUserGroupPermissions(userGroup.Id, content.Id); - } - } - //if they are different we need to update, otherwise there's nothing to update - else if (contentPermissions.ContainsKey(userGroup.Id) == false || contentPermissions[userGroup.Id].AssignedPermissions.UnsortedSequenceEqual(groupPermissionCodes) == false) - { - - Services.UserService.ReplaceUserGroupPermissions(userGroup.Id, groupPermissionCodes.Select(x => x[0]), content.Id); - } - } - } - - return GetDetailedPermissions(content, allUserGroups); - } - - /// - /// Returns the user group permissions for user groups assigned to this node - /// - /// - /// - /// - /// Permission check is done for letter 'R' which is for which the user must have access to to view - /// - [EnsureUserPermissionForContent("contentId", 'R')] - public IEnumerable GetDetailedPermissions(int contentId) - { - if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - var content = Services.ContentService.GetById(contentId); - if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - //TODO: Should non-admins be able to see detailed permissions? - - var allUserGroups = Services.UserService.GetAllUserGroups(); - - return GetDetailedPermissions(content, allUserGroups); - } - - private IEnumerable GetDetailedPermissions(IContent content, IEnumerable allUserGroups) - { - //get all user groups and map their default permissions to the AssignedUserGroupPermissions model. - //we do this because not all groups will have true assigned permissions for this node so if they don't have assigned permissions, we need to show the defaults. - - var defaultPermissionsByGroup = Mapper.Map>(allUserGroups).ToArray(); - - var defaultPermissionsAsDictionary = defaultPermissionsByGroup - .ToDictionary(x => Convert.ToInt32(x.Id), x => x); - - //get the actual assigned permissions - var assignedPermissionsByGroup = Services.ContentService.GetPermissionsForEntity(content).ToArray(); - - //iterate over assigned and update the defaults with the real values - foreach (var assignedGroupPermission in assignedPermissionsByGroup) - { - var defaultUserGroupPermissions = defaultPermissionsAsDictionary[assignedGroupPermission.UserGroupId]; - - //clone the default permissions model to the assigned ones - defaultUserGroupPermissions.AssignedPermissions = AssignedUserGroupPermissions.ClonePermissions(defaultUserGroupPermissions.DefaultPermissions); - - //since there is custom permissions assigned to this node for this group, we need to clear all of the default permissions - //and we'll re-check it if it's one of the explicitly assigned ones - foreach (var permission in defaultUserGroupPermissions.AssignedPermissions.SelectMany(x => x.Value)) - { - permission.Checked = false; - permission.Checked = assignedGroupPermission.AssignedPermissions.Contains(permission.PermissionCode, StringComparer.InvariantCulture); - } - - } - - return defaultPermissionsByGroup; - } - - /// - /// 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; - } - - public ContentItemDisplay GetBlueprintById(int id) - { - var foundContent = Services.ContentService.GetBlueprintById(id); - if (foundContent == null) - { - HandleContentNotFound(id); - } - - var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - - SetupBlueprint(content, foundContent); - - return content; - } - - private static void SetupBlueprint(ContentItemDisplay content, IContent persistedContent) - { - content.AllowPreview = false; - - //set a custom path since the tree that renders this has the content type id as the parent - content.Path = string.Format("-1,{0},{1}", persistedContent.ContentTypeId, content.Id); - - content.AllowedActions = new[] { "A" }; - content.IsBlueprint = true; - - var excludeProps = new[] { "_umb_urls", "_umb_releasedate", "_umb_expiredate", "_umb_template" }; - var propsTab = content.Tabs.Last(); - propsTab.Properties = propsTab.Properties - .Where(p => excludeProps.Contains(p.Alias) == false); - } - - /// - /// Gets the content json for the content id - /// - /// - /// If set to true, user and group start node permissions will be ignored. - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetById(int id, [FromUri]bool ignoreUserStartNodes = false) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - if (foundContent == null) - { - HandleContentNotFound(id); - } - - var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - return content; - } - - /// - /// Gets the content json for the content id - /// - /// - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetById(Guid id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - if (foundContent == null) - { - HandleContentNotFound(id); - } - - var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - return content; - } - - /// - /// Gets the content json for the content id - /// - /// - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetById(Udi id) - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - return GetById(guidUdi.Guid); - } - - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetWithTreeDefinition(int id) - { - var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); - if (foundContent == null) - { - HandleContentNotFound(id); - } - - var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - 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 = AutoMapperExtensions.MapWithUmbracoContext(emptyContent, UmbracoContext); - // translate the content type name if applicable - mapped.ContentTypeName = Services.TextService.UmbracoDictionaryTranslate(mapped.ContentTypeName); - // if your user type doesn't have access to the Settings section it would not get this property mapped - if(mapped.DocumentType != null) - mapped.DocumentType.Name = Services.TextService.UmbracoDictionaryTranslate(mapped.DocumentType.Name); - - //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; - } - - [OutgoingEditorModelEvent] - public ContentItemDisplay GetEmpty(int blueprintId, int parentId) - { - var blueprint = Services.ContentService.GetBlueprintById(blueprintId); - if (blueprint == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - blueprint.Id = 0; - blueprint.Name = string.Empty; - blueprint.ParentId = parentId; - - var mapped = Mapper.Map(blueprint); - - //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 Url for a given node ID - /// - /// - /// - public HttpResponseMessage GetNiceUrl(Guid id) - { - var url = Umbraco.UrlProvider.GetUrl(id); - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(url, Encoding.UTF8, "application/json"); - return response; - } - - /// - /// Gets the Url for a given node ID - /// - /// - /// - public HttpResponseMessage GetNiceUrl(Udi id) - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - return GetNiceUrl(guidUdi.Guid); - } - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - /// - /// 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 = "") - { - return GetChildren(id, null, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); - } - - /// - /// Gets the children for the content id passed in - /// - /// - [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren( - int id, - string includeProperties, - 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; - } - - if (totalChildren == 0) - { - return new PagedResult>(0, 0, 0); - } - - var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); - pagedResult.Items = children.Select(content => - Mapper.Map>(content, - opts => - { - // if there's a list of property aliases to map - we will make sure to store this in the mapping context. - if (String.IsNullOrWhiteSpace(includeProperties) == false) - { - opts.Items["IncludeProperties"] = includeProperties.Split(new[] { ", ", "," }, StringSplitOptions.RemoveEmptyEntries); - } - })); - - 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 - /// TODO: This should be moved to the CurrentUserController? - /// - /// - /// - [HttpPost] - public Dictionary GetPermissions(int[] nodeIds) - { - var permissions = Services.UserService - .GetPermissions(Security.CurrentUser, nodeIds); - - var permissionsDictionary = new Dictionary(); - foreach (var nodeId in nodeIds) - { - var aggregatePerms = permissions.GetAllPermissions(nodeId).ToArray(); - permissionsDictionary.Add(nodeId, aggregatePerms); - } - - return permissionsDictionary; - } - - /// - /// Checks a nodes permission for the current user - /// TODO: This should be moved to the CurrentUserController? - /// - /// - /// - /// - [HttpGet] - public bool HasPermission(string permissionToCheck, int nodeId) - { - var p = Services.UserService.GetPermissions(Security.CurrentUser, nodeId).GetAllPermissions(); - if (p.Contains(permissionToCheck.ToString(CultureInfo.InvariantCulture))) - { - return true; - } - - return false; - } - - /// - /// Creates a blueprint from a content item - /// - /// The content id to copy - /// The name of the blueprint - /// - [HttpPost] - public SimpleNotificationModel CreateBlueprintFromContent([FromUri]int contentId, [FromUri]string name) - { - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); - - var content = Services.ContentService.GetById(contentId); - if (content == null) - throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - EnsureUniqueName(name, content, "name"); - - var blueprint = Services.ContentService.CreateContentFromBlueprint(content, name, Security.CurrentUser.Id); - - Services.ContentService.SaveBlueprint(blueprint, Security.CurrentUser.Id); - - var notificationModel = new SimpleNotificationModel(); - notificationModel.AddSuccessNotification( - Services.TextService.Localize("blueprints/createdBlueprintHeading"), - Services.TextService.Localize("blueprints/createdBlueprintMessage", new[] { content.Name }) - ); - - return notificationModel; - } - - private void EnsureUniqueName(string name, IContent content, string modelName) - { - var existing = Services.ContentService.GetBlueprintsForContentTypes(content.ContentTypeId); - if (existing.Any(x => x.Name == name && x.Id != content.Id)) - { - ModelState.AddModelError(modelName, Services.TextService.Localize("blueprints/duplicateBlueprintMessage")); - throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); - } - } - - /// - /// Saves content - /// - /// - [FileUploadCleanupFilter] - [ContentPostValidate] - public ContentItemDisplay PostSaveBlueprint( - [ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem) - { - var contentItemDisplay = PostSaveInternal(contentItem, - content => - { - EnsureUniqueName(content.Name, content, "Name"); - - Services.ContentService.SaveBlueprint(contentItem.PersistedContent, Security.CurrentUser.Id); - //we need to reuse the underlying logic so return the result that it wants - return Attempt.Succeed(new OperationStatus(OperationStatusType.Success, new EventMessages())); - }); - SetupBlueprint(contentItemDisplay, contentItemDisplay.PersistedContent); - - return contentItemDisplay; - } - - /// - /// Saves content - /// - /// - [FileUploadCleanupFilter] - [ContentPostValidate] - [OutgoingEditorModelEvent] - public ContentItemDisplay PostSave( - [ModelBinder(typeof(ContentItemBinder))] - ContentItemSave contentItem) - { - return PostSaveInternal(contentItem, - content => Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id)); - } - - private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func> saveMethod) - { - //Recent versions of IE/Edge may send in the full clientside file path instead of just the file name. - //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all - //uploaded files to being *only* the actual file name (as it should be). - if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any()) - { - foreach (var file in contentItem.UploadedFiles) - { - file.FileName = Path.GetFileName(file.FileName); - } - } - - //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 = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); - 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 = saveMethod(contentItem.PersistedContent); - - 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 = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); - - //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"), - contentItem.ReleaseDate.HasValue - ? Services.TextService.Localize("speechBubbles/editContentSavedWithReleaseDateText", new [] { contentItem.ReleaseDate.Value.ToLongDateString(), contentItem.ReleaseDate.Value.ToShortTimeString() }) - : 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, contentItem.ExpireDate); - break; - } - - //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)); - } - - display.PersistedContent = contentItem.PersistedContent; - - 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.CurrentUser.Id); - if (publishResult.Success == false) - { - var notificationModel = new SimpleNotificationModel(); - ShowMessageForPublishStatus(publishResult.Result, notificationModel, foundContent.ExpireDate); - return Request.CreateValidationErrorResponse(notificationModel); - } - - //return ok - return Request.CreateResponse(HttpStatusCode.OK); - - } - - [HttpDelete] - [HttpPost] - public HttpResponseMessage DeleteBlueprint(int id) - { - var found = Services.ContentService.GetBlueprintById(id); - - if (found == null) - { - return HandleContentNotFound(id, false); - } - - Services.ContentService.DeleteBlueprint(found); - - 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.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.ContentService.WithResult().Delete(foundContent, 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); - } - - /// - /// Empties the recycle bin - /// - /// - /// - /// attributed with EnsureUserPermissionForContent to verify the user has access to the recycle bin - /// - [HttpDelete] - [HttpPost] - [EnsureUserPermissionForContent(Constants.System.RecycleBinContent, 'D')] - public HttpResponseMessage EmptyRecycleBin() - { - Services.ContentService.EmptyRecycleBin(Security.CurrentUser.Id); - - return Request.CreateNotificationSuccessResponse(Services.TextService.Localize("defaultdialogs/recycleBinIsEmpty")); - } - - /// - /// Change the sort order for content - /// - /// - /// - [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); - } - - try - { - var contentService = Services.ContentService; - - // Save content with new sort order and update content xml in db accordingly - if (contentService.Sort(sorted.IdSortOrder, Security.CurrentUser.Id) == false) - { - 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, Security.CurrentUser.Id); - - 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, Security.CurrentUser.Id); - - 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')] - [OutgoingEditorModelEvent] - 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 = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - - 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; - } - } - - /// - /// 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, DateTime? expireDate) - { - switch (status.StatusType) - { - case PublishStatusType.Success: - case PublishStatusType.SuccessAlreadyPublished: - display.AddSuccessNotification( - Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), - expireDate.HasValue - ? Services.TextService.Localize("speechBubbles/editContentPublishedWithExpireDateText", new [] { expireDate.Value.ToLongDateString(), expireDate.Value.ToShortTimeString() }) - : 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 - /// If set to true, user and group start node permissions will be ignored. - /// - internal static bool CheckPermissions( - IDictionary storage, - IUser user, - IUserService userService, - IContentService contentService, - IEntityService entityService, - int nodeId, - char[] permissionsToCheck = null, - IContent contentItem = null, - bool ignoreUserStartNodes = false) - { - if (storage == null) throw new ArgumentNullException("storage"); - if (user == null) throw new ArgumentNullException("user"); - if (userService == null) throw new ArgumentNullException("userService"); - if (contentService == null) throw new ArgumentNullException("contentService"); - if (entityService == null) throw new ArgumentNullException("entityService"); - - 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); - } - - if(ignoreUserStartNodes) - { - return true; - } - - var hasPathAccess = (nodeId == Constants.System.Root) - ? user.HasContentRootAccess(entityService) - : (nodeId == Constants.System.RecycleBinContent) - ? user.HasContentBinAccess(entityService) - : user.HasPathAccess(contentItem, entityService); - - if (hasPathAccess == false) - { - return false; - } - - if (permissionsToCheck == null || permissionsToCheck.Length == 0) - { - return true; - } - - //get the implicit/inherited permissions for the user for this path, - //if there is no content item for this id, than just use the id as the path (i.e. -1 or -20) - var path = contentItem != null ? contentItem.Path : nodeId.ToString(); - var permission = userService.GetPermissionsForPath(user, path); - - var allowed = true; - foreach (var p in permissionsToCheck) - { - if (permission == null - || permission.GetAllPermissions().Contains(p.ToString(CultureInfo.InvariantCulture)) == false) - { - allowed = false; - } - } - return allowed; - } - - [EnsureUserPermissionForContent("contentId", 'F')] - public IEnumerable GetNotificationOptions(int contentId) - { - var notifications = new List(); - if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - var content = Services.ContentService.GetById(contentId); - if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - - var actionList = ActionsResolver.Current.Actions; - foreach (var a in actionList) - { - if (a.ShowInNotifier) - { - NotifySetting n = new NotifySetting - { - Name = ui.Text("actions", a.Alias), - Checked = (UmbracoUser.GetNotifications(content.Path).IndexOf(a.Letter) > -1), - NotifyCode = a.Letter.ToString() - }; - notifications.Add(n); - } - } - return notifications; - } - - public void PostNotificationOptions(int contentId, string notifyOptions = "") - { - if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - var content = Services.ContentService.GetById(contentId); - - if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - var node = new CMSNode(contentId); - - global::umbraco.cms.businesslogic.workflow.Notification.UpdateNotifications(UmbracoUser, node, notifyOptions ?? ""); - } - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.ModelBinding; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Publishing; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Models.Mapping; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi.Binders; +using Umbraco.Web.WebApi.Filters; +using Umbraco.Core.Events; +using Constants = Umbraco.Core.Constants; +using umbraco.cms.businesslogic; +using System.Collections; +using umbraco; + +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")] + [UmbracoApplicationAuthorize(Constants.Applications.Content)] + [ContentControllerConfiguration] + public class ContentController : ContentControllerBase + { + /// + /// Constructor + /// + public ContentController() + : this(UmbracoContext.Current) + { + } + + /// + /// Constructor + /// + /// + public ContentController(UmbracoContext umbracoContext) + : base(umbracoContext) + { + } + + /// + /// Configures this controller with a custom action selector + /// + private class ContentControllerConfigurationAttribute : Attribute, IControllerConfiguration + { + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetNiceUrl", "id", typeof(int), typeof(Guid), typeof(Udi)), + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)) + )); + } + } + + /// + /// Return content for the specified ids + /// + /// + /// + [FilterAllowedOutgoingContent(typeof(IEnumerable))] + public IEnumerable GetByIds([FromUri]int[] ids) + { + var foundContent = Services.ContentService.GetByIds(ids); + return foundContent.Select(content => AutoMapperExtensions.MapWithUmbracoContext(content, UmbracoContext)); + } + + /// + /// Updates the permissions for a content item for a particular user group + /// + /// + /// + /// + /// Permission check is done for letter 'R' which is for which the user must have access to to update + /// + [EnsureUserPermissionForContent("saveModel.ContentId", 'R')] + public IEnumerable PostSaveUserGroupPermissions(UserGroupPermissionsSave saveModel) + { + if (saveModel.ContentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + //TODO: Should non-admins be alowed to set granular permissions? + + var content = Services.ContentService.GetById(saveModel.ContentId); + if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + //current permissions explicitly assigned to this content item + var contentPermissions = Services.ContentService.GetPermissionsForEntity(content) + .ToDictionary(x => x.UserGroupId, x => x); + + var allUserGroups = Services.UserService.GetAllUserGroups().ToArray(); + + //loop through each user group + foreach (var userGroup in allUserGroups) + { + //check if there's a permission set posted up for this user group + IEnumerable groupPermissions; + if (saveModel.AssignedPermissions.TryGetValue(userGroup.Id, out groupPermissions)) + { + //create a string collection of the assigned letters + var groupPermissionCodes = groupPermissions.ToArray(); + + //check if there are no permissions assigned for this group save model, if that is the case we want to reset the permissions + //for this group/node which will go back to the defaults + if (groupPermissionCodes.Length == 0) + { + Services.UserService.RemoveUserGroupPermissions(userGroup.Id, content.Id); + } + //check if they are the defaults, if so we should just remove them if they exist since it's more overhead having them stored + else if (userGroup.Permissions.UnsortedSequenceEqual(groupPermissionCodes)) + { + //only remove them if they are actually currently assigned + if (contentPermissions.ContainsKey(userGroup.Id)) + { + //remove these permissions from this node for this group since the ones being assigned are the same as the defaults + Services.UserService.RemoveUserGroupPermissions(userGroup.Id, content.Id); + } + } + //if they are different we need to update, otherwise there's nothing to update + else if (contentPermissions.ContainsKey(userGroup.Id) == false || contentPermissions[userGroup.Id].AssignedPermissions.UnsortedSequenceEqual(groupPermissionCodes) == false) + { + + Services.UserService.ReplaceUserGroupPermissions(userGroup.Id, groupPermissionCodes.Select(x => x[0]), content.Id); + } + } + } + + return GetDetailedPermissions(content, allUserGroups); + } + + /// + /// Returns the user group permissions for user groups assigned to this node + /// + /// + /// + /// + /// Permission check is done for letter 'R' which is for which the user must have access to to view + /// + [EnsureUserPermissionForContent("contentId", 'R')] + public IEnumerable GetDetailedPermissions(int contentId) + { + if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + var content = Services.ContentService.GetById(contentId); + if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + //TODO: Should non-admins be able to see detailed permissions? + + var allUserGroups = Services.UserService.GetAllUserGroups(); + + return GetDetailedPermissions(content, allUserGroups); + } + + private IEnumerable GetDetailedPermissions(IContent content, IEnumerable allUserGroups) + { + //get all user groups and map their default permissions to the AssignedUserGroupPermissions model. + //we do this because not all groups will have true assigned permissions for this node so if they don't have assigned permissions, we need to show the defaults. + + var defaultPermissionsByGroup = Mapper.Map>(allUserGroups).ToArray(); + + var defaultPermissionsAsDictionary = defaultPermissionsByGroup + .ToDictionary(x => Convert.ToInt32(x.Id), x => x); + + //get the actual assigned permissions + var assignedPermissionsByGroup = Services.ContentService.GetPermissionsForEntity(content).ToArray(); + + //iterate over assigned and update the defaults with the real values + foreach (var assignedGroupPermission in assignedPermissionsByGroup) + { + var defaultUserGroupPermissions = defaultPermissionsAsDictionary[assignedGroupPermission.UserGroupId]; + + //clone the default permissions model to the assigned ones + defaultUserGroupPermissions.AssignedPermissions = AssignedUserGroupPermissions.ClonePermissions(defaultUserGroupPermissions.DefaultPermissions); + + //since there is custom permissions assigned to this node for this group, we need to clear all of the default permissions + //and we'll re-check it if it's one of the explicitly assigned ones + foreach (var permission in defaultUserGroupPermissions.AssignedPermissions.SelectMany(x => x.Value)) + { + permission.Checked = false; + permission.Checked = assignedGroupPermission.AssignedPermissions.Contains(permission.PermissionCode, StringComparer.InvariantCulture); + } + + } + + return defaultPermissionsByGroup; + } + + /// + /// 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; + } + + public ContentItemDisplay GetBlueprintById(int id) + { + var foundContent = Services.ContentService.GetBlueprintById(id); + if (foundContent == null) + { + HandleContentNotFound(id); + } + + var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + + SetupBlueprint(content, foundContent); + + return content; + } + + private static void SetupBlueprint(ContentItemDisplay content, IContent persistedContent) + { + content.AllowPreview = false; + + //set a custom path since the tree that renders this has the content type id as the parent + content.Path = string.Format("-1,{0},{1}", persistedContent.ContentTypeId, content.Id); + + content.AllowedActions = new[] { "A" }; + content.IsBlueprint = true; + + var excludeProps = new[] { "_umb_urls", "_umb_releasedate", "_umb_expiredate", "_umb_template" }; + var propsTab = content.Tabs.Last(); + propsTab.Properties = propsTab.Properties + .Where(p => excludeProps.Contains(p.Alias) == false); + } + + /// + /// Gets the content json for the content id + /// + /// + /// If set used to lookup whether the user and group start node permissions should be ignored. + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForContent("id")] + public ContentItemDisplay GetById(int id, [FromUri]Guid? dataTypeId = null) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + if (foundContent == null) + { + HandleContentNotFound(id); + } + + var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + return content; + } + + /// + /// Gets the content json for the content id + /// + /// + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForContent("id")] + public ContentItemDisplay GetById(Guid id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + if (foundContent == null) + { + HandleContentNotFound(id); + } + + var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + return content; + } + + /// + /// Gets the content json for the content id + /// + /// + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForContent("id")] + public ContentItemDisplay GetById(Udi id) + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + return GetById(guidUdi.Guid); + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + [EnsureUserPermissionForContent("id")] + public ContentItemDisplay GetWithTreeDefinition(int id) + { + var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); + if (foundContent == null) + { + HandleContentNotFound(id); + } + + var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + 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 = AutoMapperExtensions.MapWithUmbracoContext(emptyContent, UmbracoContext); + // translate the content type name if applicable + mapped.ContentTypeName = Services.TextService.UmbracoDictionaryTranslate(mapped.ContentTypeName); + // if your user type doesn't have access to the Settings section it would not get this property mapped + if(mapped.DocumentType != null) + mapped.DocumentType.Name = Services.TextService.UmbracoDictionaryTranslate(mapped.DocumentType.Name); + + //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; + } + + [OutgoingEditorModelEvent] + public ContentItemDisplay GetEmpty(int blueprintId, int parentId) + { + var blueprint = Services.ContentService.GetBlueprintById(blueprintId); + if (blueprint == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + blueprint.Id = 0; + blueprint.Name = string.Empty; + blueprint.ParentId = parentId; + + var mapped = Mapper.Map(blueprint); + + //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 Url for a given node ID + /// + /// + /// + public HttpResponseMessage GetNiceUrl(Guid id) + { + var url = Umbraco.UrlProvider.GetUrl(id); + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(url, Encoding.UTF8, "application/json"); + return response; + } + + /// + /// Gets the Url for a given node ID + /// + /// + /// + public HttpResponseMessage GetNiceUrl(Udi id) + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + return GetNiceUrl(guidUdi.Guid); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + /// + /// 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 = "") + { + return GetChildren(id, null, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + } + + /// + /// Gets the children for the content id passed in + /// + /// + [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren( + int id, + string includeProperties, + 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; + } + + if (totalChildren == 0) + { + return new PagedResult>(0, 0, 0); + } + + var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); + pagedResult.Items = children.Select(content => + Mapper.Map>(content, + opts => + { + // if there's a list of property aliases to map - we will make sure to store this in the mapping context. + if (String.IsNullOrWhiteSpace(includeProperties) == false) + { + opts.Items["IncludeProperties"] = includeProperties.Split(new[] { ", ", "," }, StringSplitOptions.RemoveEmptyEntries); + } + })); + + 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 + /// TODO: This should be moved to the CurrentUserController? + /// + /// + /// + [HttpPost] + public Dictionary GetPermissions(int[] nodeIds) + { + var permissions = Services.UserService + .GetPermissions(Security.CurrentUser, nodeIds); + + var permissionsDictionary = new Dictionary(); + foreach (var nodeId in nodeIds) + { + var aggregatePerms = permissions.GetAllPermissions(nodeId).ToArray(); + permissionsDictionary.Add(nodeId, aggregatePerms); + } + + return permissionsDictionary; + } + + /// + /// Checks a nodes permission for the current user + /// TODO: This should be moved to the CurrentUserController? + /// + /// + /// + /// + [HttpGet] + public bool HasPermission(string permissionToCheck, int nodeId) + { + var p = Services.UserService.GetPermissions(Security.CurrentUser, nodeId).GetAllPermissions(); + if (p.Contains(permissionToCheck.ToString(CultureInfo.InvariantCulture))) + { + return true; + } + + return false; + } + + /// + /// Creates a blueprint from a content item + /// + /// The content id to copy + /// The name of the blueprint + /// + [HttpPost] + public SimpleNotificationModel CreateBlueprintFromContent([FromUri]int contentId, [FromUri]string name) + { + if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); + + var content = Services.ContentService.GetById(contentId); + if (content == null) + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + EnsureUniqueName(name, content, "name"); + + var blueprint = Services.ContentService.CreateContentFromBlueprint(content, name, Security.CurrentUser.Id); + + Services.ContentService.SaveBlueprint(blueprint, Security.CurrentUser.Id); + + var notificationModel = new SimpleNotificationModel(); + notificationModel.AddSuccessNotification( + Services.TextService.Localize("blueprints/createdBlueprintHeading"), + Services.TextService.Localize("blueprints/createdBlueprintMessage", new[] { content.Name }) + ); + + return notificationModel; + } + + private void EnsureUniqueName(string name, IContent content, string modelName) + { + var existing = Services.ContentService.GetBlueprintsForContentTypes(content.ContentTypeId); + if (existing.Any(x => x.Name == name && x.Id != content.Id)) + { + ModelState.AddModelError(modelName, Services.TextService.Localize("blueprints/duplicateBlueprintMessage")); + throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); + } + } + + /// + /// Saves content + /// + /// + [FileUploadCleanupFilter] + [ContentPostValidate] + public ContentItemDisplay PostSaveBlueprint( + [ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem) + { + var contentItemDisplay = PostSaveInternal(contentItem, + content => + { + EnsureUniqueName(content.Name, content, "Name"); + + Services.ContentService.SaveBlueprint(contentItem.PersistedContent, Security.CurrentUser.Id); + //we need to reuse the underlying logic so return the result that it wants + return Attempt.Succeed(new OperationStatus(OperationStatusType.Success, new EventMessages())); + }); + SetupBlueprint(contentItemDisplay, contentItemDisplay.PersistedContent); + + return contentItemDisplay; + } + + /// + /// Saves content + /// + /// + [FileUploadCleanupFilter] + [ContentPostValidate] + [OutgoingEditorModelEvent] + public ContentItemDisplay PostSave( + [ModelBinder(typeof(ContentItemBinder))] + ContentItemSave contentItem) + { + return PostSaveInternal(contentItem, + content => Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id)); + } + + private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func> saveMethod) + { + //Recent versions of IE/Edge may send in the full clientside file path instead of just the file name. + //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all + //uploaded files to being *only* the actual file name (as it should be). + if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any()) + { + foreach (var file in contentItem.UploadedFiles) + { + file.FileName = Path.GetFileName(file.FileName); + } + } + + //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 = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); + 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 = saveMethod(contentItem.PersistedContent); + + 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 = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); + + //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"), + contentItem.ReleaseDate.HasValue + ? Services.TextService.Localize("speechBubbles/editContentSavedWithReleaseDateText", new [] { contentItem.ReleaseDate.Value.ToLongDateString(), contentItem.ReleaseDate.Value.ToShortTimeString() }) + : 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, contentItem.ExpireDate); + break; + } + + //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)); + } + + display.PersistedContent = contentItem.PersistedContent; + + 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.CurrentUser.Id); + if (publishResult.Success == false) + { + var notificationModel = new SimpleNotificationModel(); + ShowMessageForPublishStatus(publishResult.Result, notificationModel, foundContent.ExpireDate); + return Request.CreateValidationErrorResponse(notificationModel); + } + + //return ok + return Request.CreateResponse(HttpStatusCode.OK); + + } + + [HttpDelete] + [HttpPost] + public HttpResponseMessage DeleteBlueprint(int id) + { + var found = Services.ContentService.GetBlueprintById(id); + + if (found == null) + { + return HandleContentNotFound(id, false); + } + + Services.ContentService.DeleteBlueprint(found); + + 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.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.ContentService.WithResult().Delete(foundContent, 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); + } + + /// + /// Empties the recycle bin + /// + /// + /// + /// attributed with EnsureUserPermissionForContent to verify the user has access to the recycle bin + /// + [HttpDelete] + [HttpPost] + [EnsureUserPermissionForContent(Constants.System.RecycleBinContent, 'D')] + public HttpResponseMessage EmptyRecycleBin() + { + Services.ContentService.EmptyRecycleBin(Security.CurrentUser.Id); + + return Request.CreateNotificationSuccessResponse(Services.TextService.Localize("defaultdialogs/recycleBinIsEmpty")); + } + + /// + /// Change the sort order for content + /// + /// + /// + [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); + } + + try + { + var contentService = Services.ContentService; + + // Save content with new sort order and update content xml in db accordingly + if (contentService.Sort(sorted.IdSortOrder, Security.CurrentUser.Id) == false) + { + 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, Security.CurrentUser.Id); + + 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, Security.CurrentUser.Id); + + 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')] + [OutgoingEditorModelEvent] + 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 = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + + 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; + } + } + + /// + /// 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, DateTime? expireDate) + { + switch (status.StatusType) + { + case PublishStatusType.Success: + case PublishStatusType.SuccessAlreadyPublished: + display.AddSuccessNotification( + Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), + expireDate.HasValue + ? Services.TextService.Localize("speechBubbles/editContentPublishedWithExpireDateText", new [] { expireDate.Value.ToLongDateString(), expireDate.Value.ToShortTimeString() }) + : 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 + /// If set to true, user and group start node permissions will be ignored. + /// + internal static bool CheckPermissions( + IDictionary storage, + IUser user, + IUserService userService, + IContentService contentService, + IEntityService entityService, + int nodeId, + char[] permissionsToCheck = null, + IContent contentItem = null, + bool ignoreUserStartNodes = false) + { + if (storage == null) throw new ArgumentNullException("storage"); + if (user == null) throw new ArgumentNullException("user"); + if (userService == null) throw new ArgumentNullException("userService"); + if (contentService == null) throw new ArgumentNullException("contentService"); + if (entityService == null) throw new ArgumentNullException("entityService"); + + 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); + } + + if(ignoreUserStartNodes) + { + return true; + } + + var hasPathAccess = (nodeId == Constants.System.Root) + ? user.HasContentRootAccess(entityService) + : (nodeId == Constants.System.RecycleBinContent) + ? user.HasContentBinAccess(entityService) + : user.HasPathAccess(contentItem, entityService); + + if (hasPathAccess == false) + { + return false; + } + + if (permissionsToCheck == null || permissionsToCheck.Length == 0) + { + return true; + } + + //get the implicit/inherited permissions for the user for this path, + //if there is no content item for this id, than just use the id as the path (i.e. -1 or -20) + var path = contentItem != null ? contentItem.Path : nodeId.ToString(); + var permission = userService.GetPermissionsForPath(user, path); + + var allowed = true; + foreach (var p in permissionsToCheck) + { + if (permission == null + || permission.GetAllPermissions().Contains(p.ToString(CultureInfo.InvariantCulture)) == false) + { + allowed = false; + } + } + return allowed; + } + + [EnsureUserPermissionForContent("contentId", 'F')] + public IEnumerable GetNotificationOptions(int contentId) + { + var notifications = new List(); + if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + var content = Services.ContentService.GetById(contentId); + if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + + var actionList = ActionsResolver.Current.Actions; + foreach (var a in actionList) + { + if (a.ShowInNotifier) + { + NotifySetting n = new NotifySetting + { + Name = ui.Text("actions", a.Alias), + Checked = (UmbracoUser.GetNotifications(content.Path).IndexOf(a.Letter) > -1), + NotifyCode = a.Letter.ToString() + }; + notifications.Add(n); + } + } + return notifications; + } + + public void PostNotificationOptions(int contentId, string notifyOptions = "") + { + if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + var content = Services.ContentService.GetById(contentId); + + if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + var node = new CMSNode(contentId); + + global::umbraco.cms.businesslogic.workflow.Notification.UpdateNotifications(UmbracoUser, node, notifyOptions ?? ""); + } + } +} diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index a3f76db4f2..36f4465e96 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -1,999 +1,1004 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Globalization; -using System.Net; -using System.Text; -using System.Web.Http; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Models.Membership; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Mvc; -using System.Linq; -using System.Net.Http; -using Umbraco.Core.Models; -using Constants = Umbraco.Core.Constants; -using Examine; -using Umbraco.Web.Dynamics; -using System.Text.RegularExpressions; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using System.Web.Http.Controllers; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Xml; -using Umbraco.Web.Search; -using Umbraco.Web.Trees; - -namespace Umbraco.Web.Editors -{ - /// - /// The API controller used for getting entity objects, basic name, icon, id representation of umbraco objects that are based on CMSNode - /// - /// - /// Some objects such as macros are not based on CMSNode - /// - [EntityControllerConfiguration] - [PluginController("UmbracoApi")] - public class EntityController : UmbracoAuthorizedJsonController - { - - /// - /// Configures this controller with a custom action selector - /// - private class EntityControllerConfigurationAttribute : Attribute, IControllerConfiguration - { - public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) - { - controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( - - //This is a special case, we'll accept a String here so that we can get page members when the special "all-members" - //id is passed in eventually we'll probably want to support GUID + Udi too - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPagedChildren", "id", typeof(int), typeof(string)), - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPath", "id", typeof(int), typeof(Guid), typeof(Udi)), - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)), - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetByIds", "ids", typeof(int[]), typeof(Guid[]), typeof(Udi[])))); - } - } - - private readonly UmbracoTreeSearcher _treeSearcher = new UmbracoTreeSearcher(); - - /// - /// Returns an Umbraco alias given a string - /// - /// - /// - /// - public dynamic GetSafeAlias(string value, bool camelCase = true) - { - var returnValue = (string.IsNullOrWhiteSpace(value)) ? string.Empty : value.ToSafeAlias(camelCase); - dynamic returnObj = new System.Dynamic.ExpandoObject(); - returnObj.alias = returnValue; - returnObj.original = value; - returnObj.camelCase = camelCase; - - return returnObj; - } - - /// - /// Searches for results based on the entity type - /// - /// - /// - /// - /// A starting point for the search, generally a node id, but for members this is a member type alias - /// - /// - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] - [HttpGet] - public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null) - { - return Search(query, type, false, searchFrom); - } - - /// - /// Searches for results based on the entity type - /// - /// - /// - /// - /// A starting point for the search, generally a node id, but for members this is a member type alias - /// - /// If set to true, user and group start node permissions will be ignored. - /// - [HttpGet] - public IEnumerable Search(string query, UmbracoEntityTypes type, bool? ignoreUserStartNodes, string searchFrom = null) - { - //TODO: Should we restrict search results based on what app the user has access to? - // - Theoretically you shouldn't be able to see member data if you don't have access to members right? - - if (string.IsNullOrEmpty(query)) - return Enumerable.Empty(); - - return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes != null && ignoreUserStartNodes.Value); - } - - /// - /// Searches for all content that the user is allowed to see (based on their allowed sections) - /// - /// - /// - /// - /// Even though a normal entity search will allow any user to search on a entity type that they may not have access to edit, we need - /// to filter these results to the sections they are allowed to edit since this search function is explicitly for the global search - /// so if we showed entities that they weren't allowed to edit they would get errors when clicking on the result. - /// - /// The reason a user is allowed to search individual entity types that they are not allowed to edit is because those search - /// methods might be used in things like pickers in the content editor. - /// - [HttpGet] - public IDictionary SearchAll(string query) - { - var result = new Dictionary(); - - if (string.IsNullOrEmpty(query)) - return result; - - var allowedSections = Security.CurrentUser.AllowedSections.ToArray(); - var searchableTrees = SearchableTreeResolver.Current.GetSearchableTrees(); - - foreach (var searchableTree in searchableTrees) - { - if (allowedSections.Contains(searchableTree.Value.AppAlias)) - { - var tree = Services.ApplicationTreeService.GetByAlias(searchableTree.Key); - if (tree == null) continue; //shouldn't occur - - var searchableTreeAttribute = searchableTree.Value.SearchableTree.GetType().GetCustomAttribute(false); - var treeAttribute = tree.GetTreeAttribute(); - - long total; - - result[treeAttribute.GetRootNodeDisplayName(Services.TextService)] = new TreeSearchResult - { - Results = searchableTree.Value.SearchableTree.Search(query, 200, 0, out total), - TreeAlias = searchableTree.Key, - AppAlias = searchableTree.Value.AppAlias, - JsFormatterService = searchableTreeAttribute == null ? "" : searchableTreeAttribute.ServiceName, - JsFormatterMethod = searchableTreeAttribute == null ? "" : searchableTreeAttribute.MethodName - }; - } - } - return result; - } - - /// - /// Gets the path for a given node ID - /// - /// - /// - /// - public IEnumerable GetPath(int id, UmbracoEntityTypes type) - { - var foundContent = GetResultForId(id, type); - - return foundContent.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); - } - - /// - /// Gets the path for a given node ID - /// - /// - /// - /// - public IEnumerable GetPath(Guid id, UmbracoEntityTypes type) - { - var foundContent = GetResultForKey(id, type); - - return foundContent.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); - } - - /// - /// Gets the path for a given node ID - /// - /// - /// - /// - public IEnumerable GetPath(Udi id, UmbracoEntityTypes type) - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - return GetPath(guidUdi.Guid, type); - } - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - /// - /// Gets the url of an entity - /// - /// Int id of the entity to fetch URL for - /// The tpye of entity such as Document, Media, Member - /// The URL or path to the item - public HttpResponseMessage GetUrl(int id, UmbracoEntityTypes type) - { - var returnUrl = string.Empty; - - if (type == UmbracoEntityTypes.Document) - { - var foundUrl = Umbraco.Url(id); - if (string.IsNullOrEmpty(foundUrl) == false && foundUrl != "#") - { - returnUrl = foundUrl; - - return new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(returnUrl) - }; - } - } - - var ancestors = GetAncestors(id, type); - - //if content, skip the first node for replicating NiceUrl defaults - if(type == UmbracoEntityTypes.Document) { - ancestors = ancestors.Skip(1); - } - - returnUrl = "/" + string.Join("/", ancestors.Select(x => x.Name)); - - return new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(returnUrl) - }; - } - - [Obsolete("Use GetyById instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public EntityBasic GetByKey(Guid id, UmbracoEntityTypes type) - { - return GetResultForKey(id, type); - } - - /// - /// Gets an entity by a xpath query - /// - /// - /// - /// - /// - public EntityBasic GetByQuery(string query, int nodeContextId, UmbracoEntityTypes type) - { - //TODO: Rename this!!! It's misleading, it should be GetByXPath - - - if (type != UmbracoEntityTypes.Document) - throw new ArgumentException("Get by query is only compatible with enitities of type Document"); - - - var q = ParseXPathQuery(query, nodeContextId); - var node = Umbraco.TypedContentSingleAtXPath(q); - - if (node == null) - return null; - - return GetById(node.Id, type); - } - - //PP: wip in progress on the query parser - private string ParseXPathQuery(string query, int id) - { - return UmbracoXPathPathSyntaxParser.ParseXPathQuery( - xpathExpression: query, - nodeContextId: id, - getPath: nodeid => - { - var ent = Services.EntityService.Get(nodeid); - return ent.Path.Split(',').Reverse(); - }, - publishedContentExists: i => Umbraco.TypedContent(i) != null); - } - - #region GetById - - /// - /// Gets an entity by it's id - /// - /// - /// - /// - public EntityBasic GetById(int id, UmbracoEntityTypes type) - { - return GetResultForId(id, type); - } - - /// - /// Gets an entity by it's key - /// - /// - /// - /// - public EntityBasic GetById(Guid id, UmbracoEntityTypes type) - { - return GetResultForKey(id, type); - } - - /// - /// Gets an entity by it's UDI - /// - /// - /// - /// - public EntityBasic GetById(Udi id, UmbracoEntityTypes type) - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - return GetResultForKey(guidUdi.Guid, type); - } - throw new HttpResponseException(HttpStatusCode.NotFound); - } - #endregion - - #region GetByIds - /// - /// Get entities by integer ids - /// - /// - /// - /// - /// - /// We allow for POST because there could be quite a lot of Ids - /// - [HttpGet] - [HttpPost] - public IEnumerable GetByIds([FromJsonPath]int[] ids, UmbracoEntityTypes type) - { - if (ids == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - return GetResultForIds(ids, type); - } - - /// - /// Get entities by GUID ids - /// - /// - /// - /// - /// - /// We allow for POST because there could be quite a lot of Ids - /// - [HttpGet] - [HttpPost] - public IEnumerable GetByIds([FromJsonPath]Guid[] ids, UmbracoEntityTypes type) - { - if (ids == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - return GetResultForKeys(ids, type); - } - - /// - /// Get entities by UDIs - /// - /// - /// A list of UDIs to lookup items by, all UDIs must be of the same UDI type! - /// - /// - /// - /// - /// We allow for POST because there could be quite a lot of Ids. - /// - [HttpGet] - [HttpPost] - public IEnumerable GetByIds([FromJsonPath]Udi[] ids, [FromUri]UmbracoEntityTypes type) - { - if (ids == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - if (ids.Length == 0) - { - return Enumerable.Empty(); - } - - //all udi types will need to be the same in this list so we'll determine by the first - //currently we only support GuidIdi for this method - - var guidUdi = ids[0] as GuidUdi; - if (guidUdi != null) - { - return GetResultForKeys(ids.Select(x => ((GuidUdi)x).Guid).ToArray(), type); - } - - throw new HttpResponseException(HttpStatusCode.NotFound); - } - #endregion - - [Obsolete("Use GetyByIds instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetByKeys([FromUri]Guid[] ids, UmbracoEntityTypes type) - { - if (ids == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - return GetResultForKeys(ids, type); - } - - public IEnumerable GetChildren(int id, UmbracoEntityTypes type) - { - return GetResultForChildren(id, type); - } - - /// - /// Get paged child entities by id - /// - /// - /// - /// - /// - /// - /// - /// - /// - public PagedResult GetPagedChildren( - string id, - UmbracoEntityTypes type, - int pageNumber, - int pageSize, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - string filter = "") - { - int intId; - - if (int.TryParse(id, out intId)) - { - return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter); - } - - Guid guidId; - if (Guid.TryParse(id, out guidId)) - { - //Not supported currently - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - Udi udiId; - if (Udi.TryParse(id, out udiId)) - { - //Not supported currently - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - //so we don't have an INT, GUID or UDI, it's just a string, so now need to check if it's a special id or a member type - if (id == Constants.Conventions.MemberTypes.AllMembersListId) - { - //the EntityService can search paged members from the root - - intId = -1; - return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter); - } - - //the EntityService cannot search members of a certain type, this is currently not supported and would require - //quite a bit of plumbing to do in the Services/Repository, we'll revert to a paged search - - long total; - var searchResult = _treeSearcher.ExamineSearch(Umbraco, filter ?? "", type, pageSize, pageNumber - 1, out total, id); - - return new PagedResult(total, pageNumber, pageSize) - { - Items = searchResult - }; - } - - /// - /// Get paged child entities by id - /// - /// - /// - /// - /// - /// - /// - /// - /// - public PagedResult GetPagedChildren( - int id, - UmbracoEntityTypes type, - int pageNumber, - int pageSize, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - string filter = "") - { - if (pageNumber <= 0) - throw new HttpResponseException(HttpStatusCode.NotFound); - if (pageSize <= 0) - throw new HttpResponseException(HttpStatusCode.NotFound); - - var objectType = ConvertToObjectType(type); - if (objectType.HasValue) - { - long totalRecords; - var entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); - - if (totalRecords == 0) - { - return new PagedResult(0, 0, 0); - } - - var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) - { - Items = entities.Select(Mapper.Map) - }; - - return pagedResult; - } - - //now we need to convert the unknown ones - switch (type) - { - case UmbracoEntityTypes.PropertyType: - case UmbracoEntityTypes.PropertyGroup: - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); - } - } - - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] - public PagedResult GetPagedDescendants( - int id, - UmbracoEntityTypes type, - int pageNumber, - int pageSize, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - string filter = "") - { - return GetPagedDescendants(id, type, pageNumber, pageSize, - false, orderBy, orderDirection, filter); - } - - public PagedResult GetPagedDescendants( - int id, - UmbracoEntityTypes type, - int pageNumber, - int pageSize, - bool ignoreUserStartNodes, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - string filter = "") - { - if (pageNumber <= 0) - throw new HttpResponseException(HttpStatusCode.NotFound); - if (pageSize <= 0) - throw new HttpResponseException(HttpStatusCode.NotFound); - - var objectType = ConvertToObjectType(type); - if (objectType.HasValue) - { - IEnumerable entities; - long totalRecords; - - if (id == Constants.System.Root) - { - // root is special: we reduce it to start nodes - - int[] aids = null; - switch (type) - { - case UmbracoEntityTypes.Document: - aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); - break; - case UmbracoEntityTypes.Media: - aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); - break; - } - - entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes - ? Services.EntityService.GetPagedDescendantsFromRoot(objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter, includeTrashed: false) - : Services.EntityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); - } - else - { - entities = Services.EntityService.GetPagedDescendants(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); - } - - if (totalRecords == 0) - { - return new PagedResult(0, 0, 0); - } - - var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) - { - Items = entities.Select(Mapper.Map) - }; - - return pagedResult; - } - - //now we need to convert the unknown ones - switch (type) - { - case UmbracoEntityTypes.PropertyType: - case UmbracoEntityTypes.PropertyGroup: - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); - } - } - - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] - public IEnumerable GetAncestors(int id, UmbracoEntityTypes type) - { - return GetResultForAncestors(id, type, false); - } - - public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, bool ignoreUserStartNodes) - { - return GetResultForAncestors(id, type, ignoreUserStartNodes); - } - - public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter, [FromUri]IDictionary postFilterParams) - { - return GetResultForAll(type, postFilter, postFilterParams); - } - - /// - /// Searches for results based on the entity type - /// - /// - /// - /// - /// If set to true, user and group start node permissions will be ignored. - /// - private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false) - { - long total; - return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, ignoreUserStartNodes, searchFrom); - } - - - private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes entityType) - { - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - //TODO: Need to check for Object types that support hierarchic here, some might not. - - return Services.EntityService.GetChildren(id, objectType.Value) - .WhereNotNull() - .Select(Mapper.Map); - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, bool ignoreUserStartNodes = false) - { - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - //TODO: Need to check for Object types that support hierarchic here, some might not. - - var ids = Services.EntityService.Get(id).Path.Split(',').Select(int.Parse).Distinct().ToArray(); - - if (ignoreUserStartNodes == false) - { - int[] aids = null; - switch (entityType) - { - case UmbracoEntityTypes.Document: - aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); - break; - case UmbracoEntityTypes.Media: - aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); - break; - } - - if (aids != null) - { - var lids = new List(); - var ok = false; - foreach (var i in ids) - { - if (ok) - { - lids.Add(i); - continue; - } - if (aids.Contains(i)) - { - lids.Add(i); - ok = true; - } - } - ids = lids.ToArray(); - } - } - - return ids.Length == 0 - ? Enumerable.Empty() - : Services.EntityService.GetAll(objectType.Value, ids) - .WhereNotNull() - .OrderBy(x => x.Level) - .Select(Mapper.Map); - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.PropertyType: - case UmbracoEntityTypes.PropertyGroup: - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - /// - /// Gets the result for the entity list based on the type - /// - /// - /// A string where filter that will filter the results dynamically with linq - optional - /// the parameters to fill in the string where filter - optional - /// - private IEnumerable GetResultForAll(UmbracoEntityTypes entityType, string postFilter = null, IDictionary postFilterParams = null) - { - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - //TODO: Should we order this by something ? - var entities = Services.EntityService.GetAll(objectType.Value).WhereNotNull().Select(Mapper.Map); - return ExecutePostFilter(entities, postFilter, postFilterParams); - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.Template: - var templates = Services.FileService.GetTemplates(); - var filteredTemplates = ExecutePostFilter(templates, postFilter, postFilterParams); - return filteredTemplates.Select(Mapper.Map); - - case UmbracoEntityTypes.Macro: - //Get all macros from the macro service - var macros = Services.MacroService.GetAll().WhereNotNull().OrderBy(x => x.Name); - var filteredMacros = ExecutePostFilter(macros, postFilter, postFilterParams); - return filteredMacros.Select(Mapper.Map); - - case UmbracoEntityTypes.PropertyType: - - //get all document types, then combine all property types into one list - var propertyTypes = Services.ContentTypeService.GetAllContentTypes().Cast() - .Concat(Services.ContentTypeService.GetAllMediaTypes()) - .ToArray() - .SelectMany(x => x.PropertyTypes) - .DistinctBy(composition => composition.Alias); - var filteredPropertyTypes = ExecutePostFilter(propertyTypes, postFilter, postFilterParams); - return Mapper.Map, IEnumerable>(filteredPropertyTypes); - - case UmbracoEntityTypes.PropertyGroup: - - //get all document types, then combine all property types into one list - var propertyGroups = Services.ContentTypeService.GetAllContentTypes().Cast() - .Concat(Services.ContentTypeService.GetAllMediaTypes()) - .ToArray() - .SelectMany(x => x.PropertyGroups) - .DistinctBy(composition => composition.Name); - var filteredpropertyGroups = ExecutePostFilter(propertyGroups, postFilter, postFilterParams); - return Mapper.Map, IEnumerable>(filteredpropertyGroups); - - case UmbracoEntityTypes.User: - - int total; - var users = Services.UserService.GetAll(0, int.MaxValue, out total); - var filteredUsers = ExecutePostFilter(users, postFilter, postFilterParams); - return Mapper.Map, IEnumerable>(filteredUsers); - - case UmbracoEntityTypes.Domain: - - case UmbracoEntityTypes.Language: - - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - private IEnumerable GetResultForKeys(Guid[] keys, UmbracoEntityTypes entityType) - { - if (keys.Length == 0) - return Enumerable.Empty(); - - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - var entities = Services.EntityService.GetAll(objectType.Value, keys) - .WhereNotNull() - .Select(Mapper.Map); - - // entities are in "some" order, put them back in order - var xref = entities.ToDictionary(x => x.Key); - var result = keys.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); - - return result; - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.PropertyType: - case UmbracoEntityTypes.PropertyGroup: - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - private IEnumerable GetResultForIds(int[] ids, UmbracoEntityTypes entityType) - { - if (ids.Length == 0) - return Enumerable.Empty(); - - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - var entities = Services.EntityService.GetAll(objectType.Value, ids) - .WhereNotNull() - .Select(Mapper.Map); - - // entities are in "some" order, put them back in order - var xref = entities.ToDictionary(x => x.Id); - var result = ids.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); - - return result; - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.PropertyType: - case UmbracoEntityTypes.PropertyGroup: - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - private EntityBasic GetResultForKey(Guid key, UmbracoEntityTypes entityType) - { - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - var found = Services.EntityService.GetByKey(key, objectType.Value); - if (found == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - return Mapper.Map(found); - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.PropertyType: - - case UmbracoEntityTypes.PropertyGroup: - - case UmbracoEntityTypes.Domain: - - case UmbracoEntityTypes.Language: - - case UmbracoEntityTypes.User: - - case UmbracoEntityTypes.Macro: - - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - private EntityBasic GetResultForId(int id, UmbracoEntityTypes entityType) - { - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - var found = Services.EntityService.Get(id, objectType.Value); - if (found == null) - { - throw new HttpResponseException(HttpStatusCode.NotFound); - } - return Mapper.Map(found); - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.PropertyType: - - case UmbracoEntityTypes.PropertyGroup: - - case UmbracoEntityTypes.Domain: - - case UmbracoEntityTypes.Language: - - case UmbracoEntityTypes.User: - - case UmbracoEntityTypes.Macro: - - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - - private static UmbracoObjectTypes? ConvertToObjectType(UmbracoEntityTypes entityType) - { - switch (entityType) - { - case UmbracoEntityTypes.Document: - return UmbracoObjectTypes.Document; - case UmbracoEntityTypes.Media: - return UmbracoObjectTypes.Media; - case UmbracoEntityTypes.MemberType: - return UmbracoObjectTypes.MediaType; - case UmbracoEntityTypes.MemberGroup: - return UmbracoObjectTypes.MemberGroup; - case UmbracoEntityTypes.ContentItem: - return UmbracoObjectTypes.ContentItem; - case UmbracoEntityTypes.MediaType: - return UmbracoObjectTypes.MediaType; - case UmbracoEntityTypes.DocumentType: - return UmbracoObjectTypes.DocumentType; - case UmbracoEntityTypes.Stylesheet: - return UmbracoObjectTypes.Stylesheet; - case UmbracoEntityTypes.Member: - return UmbracoObjectTypes.Member; - case UmbracoEntityTypes.DataType: - return UmbracoObjectTypes.DataType; - default: - //There is no UmbracoEntity conversion (things like Macros, Users, etc...) - return null; - } - } - - /// - /// Executes the post filter against a collection of objects - /// - /// - /// - /// - /// - /// - private IEnumerable ExecutePostFilter(IEnumerable entities, string postFilter, IDictionary postFilterParams) - { - //if a post filter is assigned then try to execute it - if (postFilter.IsNullOrWhiteSpace() == false) - { - return postFilterParams == null - ? entities.AsQueryable().Where(postFilter).ToArray() - : entities.AsQueryable().Where(postFilter, postFilterParams).ToArray(); - - } - return entities; - } - - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Net; +using System.Text; +using System.Web.Http; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models.Membership; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Mvc; +using System.Linq; +using System.Net.Http; +using Umbraco.Core.Models; +using Constants = Umbraco.Core.Constants; +using Examine; +using Umbraco.Web.Dynamics; +using System.Text.RegularExpressions; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using System.Web.Http.Controllers; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Xml; +using Umbraco.Web.Search; +using Umbraco.Web.Trees; + +namespace Umbraco.Web.Editors +{ + /// + /// The API controller used for getting entity objects, basic name, icon, id representation of umbraco objects that are based on CMSNode + /// + /// + /// Some objects such as macros are not based on CMSNode + /// + [EntityControllerConfiguration] + [PluginController("UmbracoApi")] + public class EntityController : UmbracoAuthorizedJsonController + { + + /// + /// Configures this controller with a custom action selector + /// + private class EntityControllerConfigurationAttribute : Attribute, IControllerConfiguration + { + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( + + //This is a special case, we'll accept a String here so that we can get page members when the special "all-members" + //id is passed in eventually we'll probably want to support GUID + Udi too + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPagedChildren", "id", typeof(int), typeof(string)), + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPath", "id", typeof(int), typeof(Guid), typeof(Udi)), + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)), + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetByIds", "ids", typeof(int[]), typeof(Guid[]), typeof(Udi[])))); + } + } + + private readonly UmbracoTreeSearcher _treeSearcher = new UmbracoTreeSearcher(); + + /// + /// Returns an Umbraco alias given a string + /// + /// + /// + /// + public dynamic GetSafeAlias(string value, bool camelCase = true) + { + var returnValue = (string.IsNullOrWhiteSpace(value)) ? string.Empty : value.ToSafeAlias(camelCase); + dynamic returnObj = new System.Dynamic.ExpandoObject(); + returnObj.alias = returnValue; + returnObj.original = value; + returnObj.camelCase = camelCase; + + return returnObj; + } + + /// + /// Searches for results based on the entity type + /// + /// + /// + /// + /// A starting point for the search, generally a node id, but for members this is a member type alias + /// + /// + [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] + [HttpGet] + public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null) + { + return Search(query, type, null, searchFrom); + } + + /// + /// Searches for results based on the entity type + /// + /// + /// + /// + /// A starting point for the search, generally a node id, but for members this is a member type alias + /// + /// If set used to look up whether user and group start node permissions will be ignored. + /// + [HttpGet] + public IEnumerable Search(string query, UmbracoEntityTypes type, Guid? dataTypeId, string searchFrom = null) + { + //TODO: Should we restrict search results based on what app the user has access to? + // - Theoretically you shouldn't be able to see member data if you don't have access to members right? + + if (string.IsNullOrEmpty(query)) + return Enumerable.Empty(); + + var ignoreUserStartNodes = dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); + return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes); + } + + /// + /// Searches for all content that the user is allowed to see (based on their allowed sections) + /// + /// + /// + /// + /// Even though a normal entity search will allow any user to search on a entity type that they may not have access to edit, we need + /// to filter these results to the sections they are allowed to edit since this search function is explicitly for the global search + /// so if we showed entities that they weren't allowed to edit they would get errors when clicking on the result. + /// + /// The reason a user is allowed to search individual entity types that they are not allowed to edit is because those search + /// methods might be used in things like pickers in the content editor. + /// + [HttpGet] + public IDictionary SearchAll(string query) + { + var result = new Dictionary(); + + if (string.IsNullOrEmpty(query)) + return result; + + var allowedSections = Security.CurrentUser.AllowedSections.ToArray(); + var searchableTrees = SearchableTreeResolver.Current.GetSearchableTrees(); + + foreach (var searchableTree in searchableTrees) + { + if (allowedSections.Contains(searchableTree.Value.AppAlias)) + { + var tree = Services.ApplicationTreeService.GetByAlias(searchableTree.Key); + if (tree == null) continue; //shouldn't occur + + var searchableTreeAttribute = searchableTree.Value.SearchableTree.GetType().GetCustomAttribute(false); + var treeAttribute = tree.GetTreeAttribute(); + + long total; + + result[treeAttribute.GetRootNodeDisplayName(Services.TextService)] = new TreeSearchResult + { + Results = searchableTree.Value.SearchableTree.Search(query, 200, 0, out total), + TreeAlias = searchableTree.Key, + AppAlias = searchableTree.Value.AppAlias, + JsFormatterService = searchableTreeAttribute == null ? "" : searchableTreeAttribute.ServiceName, + JsFormatterMethod = searchableTreeAttribute == null ? "" : searchableTreeAttribute.MethodName + }; + } + } + return result; + } + + /// + /// Gets the path for a given node ID + /// + /// + /// + /// + public IEnumerable GetPath(int id, UmbracoEntityTypes type) + { + var foundContent = GetResultForId(id, type); + + return foundContent.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); + } + + /// + /// Gets the path for a given node ID + /// + /// + /// + /// + public IEnumerable GetPath(Guid id, UmbracoEntityTypes type) + { + var foundContent = GetResultForKey(id, type); + + return foundContent.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); + } + + /// + /// Gets the path for a given node ID + /// + /// + /// + /// + public IEnumerable GetPath(Udi id, UmbracoEntityTypes type) + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + return GetPath(guidUdi.Guid, type); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + /// + /// Gets the url of an entity + /// + /// Int id of the entity to fetch URL for + /// The tpye of entity such as Document, Media, Member + /// The URL or path to the item + public HttpResponseMessage GetUrl(int id, UmbracoEntityTypes type) + { + var returnUrl = string.Empty; + + if (type == UmbracoEntityTypes.Document) + { + var foundUrl = Umbraco.Url(id); + if (string.IsNullOrEmpty(foundUrl) == false && foundUrl != "#") + { + returnUrl = foundUrl; + + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(returnUrl) + }; + } + } + + var ancestors = GetAncestors(id, type); + + //if content, skip the first node for replicating NiceUrl defaults + if(type == UmbracoEntityTypes.Document) { + ancestors = ancestors.Skip(1); + } + + returnUrl = "/" + string.Join("/", ancestors.Select(x => x.Name)); + + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(returnUrl) + }; + } + + [Obsolete("Use GetyById instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public EntityBasic GetByKey(Guid id, UmbracoEntityTypes type) + { + return GetResultForKey(id, type); + } + + /// + /// Gets an entity by a xpath query + /// + /// + /// + /// + /// + public EntityBasic GetByQuery(string query, int nodeContextId, UmbracoEntityTypes type) + { + //TODO: Rename this!!! It's misleading, it should be GetByXPath + + + if (type != UmbracoEntityTypes.Document) + throw new ArgumentException("Get by query is only compatible with enitities of type Document"); + + + var q = ParseXPathQuery(query, nodeContextId); + var node = Umbraco.TypedContentSingleAtXPath(q); + + if (node == null) + return null; + + return GetById(node.Id, type); + } + + //PP: wip in progress on the query parser + private string ParseXPathQuery(string query, int id) + { + return UmbracoXPathPathSyntaxParser.ParseXPathQuery( + xpathExpression: query, + nodeContextId: id, + getPath: nodeid => + { + var ent = Services.EntityService.Get(nodeid); + return ent.Path.Split(',').Reverse(); + }, + publishedContentExists: i => Umbraco.TypedContent(i) != null); + } + + #region GetById + + /// + /// Gets an entity by it's id + /// + /// + /// + /// + public EntityBasic GetById(int id, UmbracoEntityTypes type) + { + return GetResultForId(id, type); + } + + /// + /// Gets an entity by it's key + /// + /// + /// + /// + public EntityBasic GetById(Guid id, UmbracoEntityTypes type) + { + return GetResultForKey(id, type); + } + + /// + /// Gets an entity by it's UDI + /// + /// + /// + /// + public EntityBasic GetById(Udi id, UmbracoEntityTypes type) + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + return GetResultForKey(guidUdi.Guid, type); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + #endregion + + #region GetByIds + /// + /// Get entities by integer ids + /// + /// + /// + /// + /// + /// We allow for POST because there could be quite a lot of Ids + /// + [HttpGet] + [HttpPost] + public IEnumerable GetByIds([FromJsonPath]int[] ids, UmbracoEntityTypes type) + { + if (ids == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return GetResultForIds(ids, type); + } + + /// + /// Get entities by GUID ids + /// + /// + /// + /// + /// + /// We allow for POST because there could be quite a lot of Ids + /// + [HttpGet] + [HttpPost] + public IEnumerable GetByIds([FromJsonPath]Guid[] ids, UmbracoEntityTypes type) + { + if (ids == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return GetResultForKeys(ids, type); + } + + /// + /// Get entities by UDIs + /// + /// + /// A list of UDIs to lookup items by, all UDIs must be of the same UDI type! + /// + /// + /// + /// + /// We allow for POST because there could be quite a lot of Ids. + /// + [HttpGet] + [HttpPost] + public IEnumerable GetByIds([FromJsonPath]Udi[] ids, [FromUri]UmbracoEntityTypes type) + { + if (ids == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + if (ids.Length == 0) + { + return Enumerable.Empty(); + } + + //all udi types will need to be the same in this list so we'll determine by the first + //currently we only support GuidIdi for this method + + var guidUdi = ids[0] as GuidUdi; + if (guidUdi != null) + { + return GetResultForKeys(ids.Select(x => ((GuidUdi)x).Guid).ToArray(), type); + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + #endregion + + [Obsolete("Use GetyByIds instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetByKeys([FromUri]Guid[] ids, UmbracoEntityTypes type) + { + if (ids == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return GetResultForKeys(ids, type); + } + + public IEnumerable GetChildren(int id, UmbracoEntityTypes type) + { + return GetResultForChildren(id, type); + } + + /// + /// Get paged child entities by id + /// + /// + /// + /// + /// + /// + /// + /// + /// + public PagedResult GetPagedChildren( + string id, + UmbracoEntityTypes type, + int pageNumber, + int pageSize, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") + { + int intId; + + if (int.TryParse(id, out intId)) + { + return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter); + } + + Guid guidId; + if (Guid.TryParse(id, out guidId)) + { + //Not supported currently + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + Udi udiId; + if (Udi.TryParse(id, out udiId)) + { + //Not supported currently + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + //so we don't have an INT, GUID or UDI, it's just a string, so now need to check if it's a special id or a member type + if (id == Constants.Conventions.MemberTypes.AllMembersListId) + { + //the EntityService can search paged members from the root + + intId = -1; + return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter); + } + + //the EntityService cannot search members of a certain type, this is currently not supported and would require + //quite a bit of plumbing to do in the Services/Repository, we'll revert to a paged search + + long total; + var searchResult = _treeSearcher.ExamineSearch(Umbraco, filter ?? "", type, pageSize, pageNumber - 1, out total, id); + + return new PagedResult(total, pageNumber, pageSize) + { + Items = searchResult + }; + } + + /// + /// Get paged child entities by id + /// + /// + /// + /// + /// + /// + /// + /// + /// + public PagedResult GetPagedChildren( + int id, + UmbracoEntityTypes type, + int pageNumber, + int pageSize, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") + { + if (pageNumber <= 0) + throw new HttpResponseException(HttpStatusCode.NotFound); + if (pageSize <= 0) + throw new HttpResponseException(HttpStatusCode.NotFound); + + var objectType = ConvertToObjectType(type); + if (objectType.HasValue) + { + long totalRecords; + var entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + + if (totalRecords == 0) + { + return new PagedResult(0, 0, 0); + } + + var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) + { + Items = entities.Select(Mapper.Map) + }; + + return pagedResult; + } + + //now we need to convert the unknown ones + switch (type) + { + case UmbracoEntityTypes.PropertyType: + case UmbracoEntityTypes.PropertyGroup: + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); + } + } + + [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] + public PagedResult GetPagedDescendants( + int id, + UmbracoEntityTypes type, + int pageNumber, + int pageSize, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") + { + return GetPagedDescendants(id, type, pageNumber, pageSize, + null, orderBy, orderDirection, filter); + } + + public PagedResult GetPagedDescendants( + int id, + UmbracoEntityTypes type, + int pageNumber, + int pageSize, + Guid? dataTypeId, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") + { + if (pageNumber <= 0) + throw new HttpResponseException(HttpStatusCode.NotFound); + if (pageSize <= 0) + throw new HttpResponseException(HttpStatusCode.NotFound); + + var objectType = ConvertToObjectType(type); + if (objectType.HasValue) + { + IEnumerable entities; + long totalRecords; + + if (id == Constants.System.Root) + { + // root is special: we reduce it to start nodes + + int[] aids = null; + switch (type) + { + case UmbracoEntityTypes.Document: + aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + } + + var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); + entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes + ? Services.EntityService.GetPagedDescendantsFromRoot(objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter, includeTrashed: false) + : Services.EntityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + } + else + { + entities = Services.EntityService.GetPagedDescendants(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + } + + if (totalRecords == 0) + { + return new PagedResult(0, 0, 0); + } + + var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) + { + Items = entities.Select(Mapper.Map) + }; + + return pagedResult; + } + + //now we need to convert the unknown ones + switch (type) + { + case UmbracoEntityTypes.PropertyType: + case UmbracoEntityTypes.PropertyGroup: + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); + } + } + + private bool IsDataTypeIgnoringUserStartNodes(Guid? dataTypeId) => dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); + + [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] + public IEnumerable GetAncestors(int id, UmbracoEntityTypes type) + { + return GetResultForAncestors(id, type, null); + } + + public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, Guid? dataTypeId) + { + return GetResultForAncestors(id, type, dataTypeId); + } + + public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter, [FromUri]IDictionary postFilterParams) + { + return GetResultForAll(type, postFilter, postFilterParams); + } + + /// + /// Searches for results based on the entity type + /// + /// + /// + /// + /// If set to true, user and group start node permissions will be ignored. + /// + private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false) + { + long total; + return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, ignoreUserStartNodes, searchFrom); + } + + + private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes entityType) + { + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + //TODO: Need to check for Object types that support hierarchic here, some might not. + + return Services.EntityService.GetChildren(id, objectType.Value) + .WhereNotNull() + .Select(Mapper.Map); + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, Guid? dataTypeId = null) + { + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + //TODO: Need to check for Object types that support hierarchic here, some might not. + + var ids = Services.EntityService.Get(id).Path.Split(',').Select(int.Parse).Distinct().ToArray(); + + var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); + if (ignoreUserStartNodes == false) + { + int[] aids = null; + switch (entityType) + { + case UmbracoEntityTypes.Document: + aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + } + + if (aids != null) + { + var lids = new List(); + var ok = false; + foreach (var i in ids) + { + if (ok) + { + lids.Add(i); + continue; + } + if (aids.Contains(i)) + { + lids.Add(i); + ok = true; + } + } + ids = lids.ToArray(); + } + } + + return ids.Length == 0 + ? Enumerable.Empty() + : Services.EntityService.GetAll(objectType.Value, ids) + .WhereNotNull() + .OrderBy(x => x.Level) + .Select(Mapper.Map); + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.PropertyType: + case UmbracoEntityTypes.PropertyGroup: + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + /// + /// Gets the result for the entity list based on the type + /// + /// + /// A string where filter that will filter the results dynamically with linq - optional + /// the parameters to fill in the string where filter - optional + /// + private IEnumerable GetResultForAll(UmbracoEntityTypes entityType, string postFilter = null, IDictionary postFilterParams = null) + { + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + //TODO: Should we order this by something ? + var entities = Services.EntityService.GetAll(objectType.Value).WhereNotNull().Select(Mapper.Map); + return ExecutePostFilter(entities, postFilter, postFilterParams); + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.Template: + var templates = Services.FileService.GetTemplates(); + var filteredTemplates = ExecutePostFilter(templates, postFilter, postFilterParams); + return filteredTemplates.Select(Mapper.Map); + + case UmbracoEntityTypes.Macro: + //Get all macros from the macro service + var macros = Services.MacroService.GetAll().WhereNotNull().OrderBy(x => x.Name); + var filteredMacros = ExecutePostFilter(macros, postFilter, postFilterParams); + return filteredMacros.Select(Mapper.Map); + + case UmbracoEntityTypes.PropertyType: + + //get all document types, then combine all property types into one list + var propertyTypes = Services.ContentTypeService.GetAllContentTypes().Cast() + .Concat(Services.ContentTypeService.GetAllMediaTypes()) + .ToArray() + .SelectMany(x => x.PropertyTypes) + .DistinctBy(composition => composition.Alias); + var filteredPropertyTypes = ExecutePostFilter(propertyTypes, postFilter, postFilterParams); + return Mapper.Map, IEnumerable>(filteredPropertyTypes); + + case UmbracoEntityTypes.PropertyGroup: + + //get all document types, then combine all property types into one list + var propertyGroups = Services.ContentTypeService.GetAllContentTypes().Cast() + .Concat(Services.ContentTypeService.GetAllMediaTypes()) + .ToArray() + .SelectMany(x => x.PropertyGroups) + .DistinctBy(composition => composition.Name); + var filteredpropertyGroups = ExecutePostFilter(propertyGroups, postFilter, postFilterParams); + return Mapper.Map, IEnumerable>(filteredpropertyGroups); + + case UmbracoEntityTypes.User: + + int total; + var users = Services.UserService.GetAll(0, int.MaxValue, out total); + var filteredUsers = ExecutePostFilter(users, postFilter, postFilterParams); + return Mapper.Map, IEnumerable>(filteredUsers); + + case UmbracoEntityTypes.Domain: + + case UmbracoEntityTypes.Language: + + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + private IEnumerable GetResultForKeys(Guid[] keys, UmbracoEntityTypes entityType) + { + if (keys.Length == 0) + return Enumerable.Empty(); + + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + var entities = Services.EntityService.GetAll(objectType.Value, keys) + .WhereNotNull() + .Select(Mapper.Map); + + // entities are in "some" order, put them back in order + var xref = entities.ToDictionary(x => x.Key); + var result = keys.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); + + return result; + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.PropertyType: + case UmbracoEntityTypes.PropertyGroup: + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + private IEnumerable GetResultForIds(int[] ids, UmbracoEntityTypes entityType) + { + if (ids.Length == 0) + return Enumerable.Empty(); + + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + var entities = Services.EntityService.GetAll(objectType.Value, ids) + .WhereNotNull() + .Select(Mapper.Map); + + // entities are in "some" order, put them back in order + var xref = entities.ToDictionary(x => x.Id); + var result = ids.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); + + return result; + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.PropertyType: + case UmbracoEntityTypes.PropertyGroup: + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + private EntityBasic GetResultForKey(Guid key, UmbracoEntityTypes entityType) + { + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + var found = Services.EntityService.GetByKey(key, objectType.Value); + if (found == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return Mapper.Map(found); + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.PropertyType: + + case UmbracoEntityTypes.PropertyGroup: + + case UmbracoEntityTypes.Domain: + + case UmbracoEntityTypes.Language: + + case UmbracoEntityTypes.User: + + case UmbracoEntityTypes.Macro: + + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + private EntityBasic GetResultForId(int id, UmbracoEntityTypes entityType) + { + var objectType = ConvertToObjectType(entityType); + if (objectType.HasValue) + { + var found = Services.EntityService.Get(id, objectType.Value); + if (found == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return Mapper.Map(found); + } + //now we need to convert the unknown ones + switch (entityType) + { + case UmbracoEntityTypes.PropertyType: + + case UmbracoEntityTypes.PropertyGroup: + + case UmbracoEntityTypes.Domain: + + case UmbracoEntityTypes.Language: + + case UmbracoEntityTypes.User: + + case UmbracoEntityTypes.Macro: + + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); + } + } + + private static UmbracoObjectTypes? ConvertToObjectType(UmbracoEntityTypes entityType) + { + switch (entityType) + { + case UmbracoEntityTypes.Document: + return UmbracoObjectTypes.Document; + case UmbracoEntityTypes.Media: + return UmbracoObjectTypes.Media; + case UmbracoEntityTypes.MemberType: + return UmbracoObjectTypes.MediaType; + case UmbracoEntityTypes.MemberGroup: + return UmbracoObjectTypes.MemberGroup; + case UmbracoEntityTypes.ContentItem: + return UmbracoObjectTypes.ContentItem; + case UmbracoEntityTypes.MediaType: + return UmbracoObjectTypes.MediaType; + case UmbracoEntityTypes.DocumentType: + return UmbracoObjectTypes.DocumentType; + case UmbracoEntityTypes.Stylesheet: + return UmbracoObjectTypes.Stylesheet; + case UmbracoEntityTypes.Member: + return UmbracoObjectTypes.Member; + case UmbracoEntityTypes.DataType: + return UmbracoObjectTypes.DataType; + default: + //There is no UmbracoEntity conversion (things like Macros, Users, etc...) + return null; + } + } + + /// + /// Executes the post filter against a collection of objects + /// + /// + /// + /// + /// + /// + private IEnumerable ExecutePostFilter(IEnumerable entities, string postFilter, IDictionary postFilterParams) + { + //if a post filter is assigned then try to execute it + if (postFilter.IsNullOrWhiteSpace() == false) + { + return postFilterParams == null + ? entities.AsQueryable().Where(postFilter).ToArray() + : entities.AsQueryable().Where(postFilter, postFilterParams).ToArray(); + + } + return entities; + } + + } +} diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 6c0c293572..788ae3a0c7 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -1,1014 +1,1017 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Net.Http.Formatting; -using System.Text; -using System.Threading.Tasks; -using System.Web.Http; -using System.Web.Http.ModelBinding; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Models.Mapping; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi; -using System.Linq; -using System.Web.Http.Controllers; -using Umbraco.Web.WebApi.Binders; -using Umbraco.Web.WebApi.Filters; -using Constants = Umbraco.Core.Constants; -using Umbraco.Core.Configuration; -using Umbraco.Web.UI; -using Notification = Umbraco.Web.Models.ContentEditing.Notification; -using Umbraco.Core.Persistence; -using Umbraco.Core.Configuration.UmbracoSettings; - -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")] - [UmbracoApplicationAuthorize(Constants.Applications.Media)] - [MediaControllerControllerConfiguration] - public class MediaController : ContentControllerBase - { - /// - /// Configures this controller with a custom action selector - /// - private class MediaControllerControllerConfigurationAttribute : Attribute, IControllerConfiguration - { - public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) - { - controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)), - new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetChildren", "id", typeof(int), typeof(Guid), typeof(Udi), typeof(string)))); - } - } - - /// - /// 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, Security.CurrentUser.Id); - var mapped = AutoMapperExtensions.MapWithUmbracoContext(emptyContent, UmbracoContext); - - //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 media item by 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 AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - } - - /// - /// Gets the media item by id - /// - /// - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForMedia("id")] - public MediaItemDisplay GetById(Guid id) - { - var foundContent = GetObjectFromRequest(() => Services.MediaService.GetById(id)); - - if (foundContent == null) - { - HandleContentNotFound(id); - //HandleContentNotFound will throw an exception - return null; - } - return AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); - } - - /// - /// Gets the media item by id - /// - /// - /// - [OutgoingEditorModelEvent] - [EnsureUserPermissionForMedia("id")] - public MediaItemDisplay GetById(Udi id) - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - return GetById(guidUdi.Guid); - } - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - /// - /// Return media for the specified ids - /// - /// - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable))] - public IEnumerable GetByIds([FromUri]int[] ids) - { - var foundMedia = Services.MediaService.GetByIds(ids); - return foundMedia.Select(media => AutoMapperExtensions.MapWithUmbracoContext(media, UmbracoContext)); - } - - /// - /// Returns media items known to be of a "Folder" type - /// - /// - /// - [Obsolete("This is no longer used and shouldn't be because it performs poorly when there are a lot of media items")] - [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] - public IEnumerable> GetChildFolders(int id = -1) - { - //we are only allowing a max of 500 to be returned here, if more is required it needs to be paged - var result = GetChildFolders(id, 1, 500); - return result.Items; - } - - /// - /// Returns a paged result of media items known to be of a "Folder" type - /// - /// - /// - /// - /// - public PagedResult> GetChildFolders(int id, int pageNumber, int pageSize) - { - //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() - .Where(x => x.Alias.EndsWith("Folder")) - .Select(x => x.Id) - .ToArray(); - - if (folderTypes.Length == 0) - { - return new PagedResult>(0, pageNumber, pageSize); - } - - long total; - var children = Services.MediaService.GetPagedChildren(id, pageNumber - 1, pageSize, out total, "Name", Direction.Ascending, true, null, folderTypes.ToArray()); - - return new PagedResult>(total, pageNumber, pageSize) - { - Items = children.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>); - } - - #region GetChildren - - private int[] _userStartNodes; - protected int[] UserStartNodes - { - get { return _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService)); } - } - - /// - /// Returns the child media objects - using the entity INT id - /// - [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 = "", - bool ignoreUserStartNodes = false) - { - //if a request is made for the root node data but the user's start node is not the default, then - // we need to return their start nodes - if (id == Constants.System.Root && UserStartNodes.Length > 0 && (UserStartNodes.Contains(Constants.System.Root) == false && ignoreUserStartNodes == false)) - { - if (pageNumber > 0) - return new PagedResult>(0, 0, 0); - var nodes = Services.MediaService.GetByIds(UserStartNodes).ToArray(); - if (nodes.Length == 0) - return new PagedResult>(0, 0, 0); - if (pageSize < nodes.Length) pageSize = nodes.Length; // bah - var pr = new PagedResult>(nodes.Length, pageNumber, pageSize) - { - Items = nodes.Select(Mapper.Map>) - }; - return pr; - } - - // else proceed as usual - - long 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; - } - - /// - /// This method is obsolete, use the overload with ignoreUserStartNodes instead - /// Returns the child media objects - using the entity GUID id - /// - /// - /// - /// - /// - /// - /// - /// - /// - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(Guid id, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - return GetChildren(id, false, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); - } - - /// - /// Returns the child media objects - using the entity GUID id - /// - /// - /// - /// - /// - /// - /// - /// - /// If set to true, user and group start node permissions will be ignored. - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(Guid id, - bool ignoreUserStartNodes, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - var entity = Services.EntityService.GetByKey(id); - if (entity != null) - { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, ignoreUserStartNodes); - } - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - /// - /// This method is obsolete, use the overload with ignoreUserStartNodes instead - /// Returns the child media objects - using the entity UDI id - /// - /// - /// - /// - /// - /// - /// - /// - /// - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(Udi id, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - return GetChildren(id, false, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); - } - - /// - /// Returns the child media objects - using the entity UDI id - /// - /// - /// - /// - /// - /// - /// - /// - /// If set to true, user and group start node permissions will be ignored. - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren(Udi id, - bool ignoreUserStartNodes, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - var entity = Services.EntityService.GetByKey(guidUdi.Guid); - if (entity != null) - { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, ignoreUserStartNodes); - } - } - - throw new HttpResponseException(HttpStatusCode.NotFound); - } - - [Obsolete("Do not use this method, use either the overload with INT or GUID instead, this will be removed in future versions")] - [EditorBrowsable(EditorBrowsableState.Never)] - [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] - public PagedResult> GetChildren(string id, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "", - bool ignoreUserStartNodes = false) - { - foreach (var type in new[] { typeof(int), typeof(Guid) }) - { - var parsed = id.TryConvertTo(type); - if (parsed) - { - //oooh magic! will auto select the right overload - return GetChildren((dynamic)parsed.Result); - } - } - - throw new HttpResponseException(HttpStatusCode.NotFound); - } - #endregion - - /// - /// 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, 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, 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); - var destinationParentID = move.ParentId; - var sourceParentID = toMove.ParentId; - - var moveResult = Services.MediaService.WithResult().Move(toMove, move.ParentId, Security.CurrentUser.Id); - - if (sourceParentID == destinationParentID) - { - return Request.CreateValidationErrorResponse(new SimpleNotificationModel(new Notification("",Services.TextService.Localize("media/moveToSameFolderFailed"),SpeechBubbleIcon.Error))); - } - if (moveResult == false) - { - return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); - } - else - { - var response = Request.CreateResponse(HttpStatusCode.OK); - response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); - return response; - } - } - - /// - /// Saves content - /// - /// - [FileUploadCleanupFilter] - [MediaPostValidate] - [OutgoingEditorModelEvent] - public MediaItemDisplay PostSave( - [ModelBinder(typeof(MediaItemBinder))] - MediaItemSave contentItem) - { - //Recent versions of IE/Edge may send in the full clientside file path instead of just the file name. - //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all - //uploaded files to being *only* the actual file name (as it should be). - if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any()) - { - foreach (var file in contentItem.UploadedFiles) - { - file.FileName = Path.GetFileName(file.FileName); - } - } - - //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 = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); - forDisplay.Errors = ModelState.ToErrorDictionary(); - throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); - } - } - - //save the item - var saveStatus = Services.MediaService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id); - - //return the updated model - var display = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); - - //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(Security.CurrentUser.Id); - - return Request.CreateNotificationSuccessResponse(Services.TextService.Localize("defaultdialogs/recycleBinIsEmpty")); - } - - /// - /// 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; - } - } - - public MediaItemDisplay PostAddFolder(PostedFolder folder) - { - var intParentId = GetParentIdAsInt(folder.ParentId, validatePermissions:true); - - var mediaService = ApplicationContext.Services.MediaService; - - var f = mediaService.CreateMedia(folder.Name, intParentId, Constants.Conventions.MediaTypes.Folder); - mediaService.Save(f, Security.CurrentUser.Id); - - return AutoMapperExtensions.MapWithUmbracoContext(f, UmbracoContext); - } - - /// - /// 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 - string currentFolderId = result.FormData["currentFolder"]; - int parentId = GetParentIdAsInt(currentFolderId, validatePermissions: true); - - 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[] { '\"' }).TrimEnd(); - var safeFileName = fileName.ToSafeFileName(); - var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower(); - - if (UmbracoConfig.For.UmbracoSettings().Content.IsFileAllowedForUpload(ext)) - { - var mediaType = Constants.Conventions.MediaTypes.File; - - if (result.FormData["contentTypeAlias"] == Constants.Conventions.MediaTypes.AutoSelect) - { - if (UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.Contains(ext)) - { - mediaType = Constants.Conventions.MediaTypes.Image; - } - } - else - { - mediaType = result.FormData["contentTypeAlias"]; - } - - var mediaItemName = fileName.ToFriendlyName(); - - 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"), - Services.TextService.Localize("media/disallowedFileType"), - 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); - } - - /// - /// Given a parent id which could be a GUID, UDI or an INT, this will resolve the INT - /// - /// - /// - /// If true, this will check if the current user has access to the resolved integer parent id - /// and if that check fails an unauthorized exception will occur - /// - /// - private int GetParentIdAsInt(string parentId, bool validatePermissions) - { - int intParentId; - GuidUdi parentUdi; - - // test for udi - if (GuidUdi.TryParse(parentId, out parentUdi)) - { - parentId = parentUdi.Guid.ToString(); - } - - //if it's not an INT then we'll check for GUID - if (int.TryParse(parentId, out intParentId) == false) - { - // if a guid then try to look up the entity - Guid idGuid; - if (Guid.TryParse(parentId, out idGuid)) - { - var entity = Services.EntityService.GetByKey(idGuid); - if (entity != null) - { - intParentId = entity.Id; - } - else - { - throw new EntityNotFoundException(parentId, "The passed id doesn't exist"); - } - } - else - { - throw new HttpResponseException( - Request.CreateValidationErrorResponse("The request was not formatted correctly, the parentId is not an integer, Guid or UDI")); - } - } - - //ensure the user has access to this folder by parent id! - if (validatePermissions && CheckPermissions( - new Dictionary(), - Security.CurrentUser, - Services.MediaService, - Services.EntityService, - intParentId) == false) - { - throw new HttpResponseException(Request.CreateResponse( - HttpStatusCode.Forbidden, - new SimpleNotificationModel(new Notification( - Services.TextService.Localize("speechBubbles/operationFailedHeader"), - Services.TextService.Localize("speechBubbles/invalidUserPermissionsText"), - SpeechBubbleIcon.Warning)))); - } - - return intParentId; - } - - /// - /// 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, IEntityService entityService, int nodeId, IMedia media = null) - { - if (storage == null) throw new ArgumentNullException("storage"); - if (user == null) throw new ArgumentNullException("user"); - if (mediaService == null) throw new ArgumentNullException("mediaService"); - if (entityService == null) throw new ArgumentNullException("entityService"); - - 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) - ? user.HasMediaRootAccess(entityService) - : (nodeId == Constants.System.RecycleBinMedia) - ? user.HasMediaBinAccess(entityService) - : user.HasPathAccess(media, entityService); - - return hasPathAccess; - } - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Text; +using System.Threading.Tasks; +using System.Web.Http; +using System.Web.Http.ModelBinding; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Models.Mapping; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; +using System.Linq; +using System.Web.Http.Controllers; +using Umbraco.Web.WebApi.Binders; +using Umbraco.Web.WebApi.Filters; +using Constants = Umbraco.Core.Constants; +using Umbraco.Core.Configuration; +using Umbraco.Web.UI; +using Notification = Umbraco.Web.Models.ContentEditing.Notification; +using Umbraco.Core.Persistence; +using Umbraco.Core.Configuration.UmbracoSettings; + +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")] + [UmbracoApplicationAuthorize(Constants.Applications.Media)] + [MediaControllerControllerConfiguration] + public class MediaController : ContentControllerBase + { + /// + /// Configures this controller with a custom action selector + /// + private class MediaControllerControllerConfigurationAttribute : Attribute, IControllerConfiguration + { + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { + controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)), + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetChildren", "id", typeof(int), typeof(Guid), typeof(Udi), typeof(string)))); + } + } + + /// + /// 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, Security.CurrentUser.Id); + var mapped = AutoMapperExtensions.MapWithUmbracoContext(emptyContent, UmbracoContext); + + //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 media item by 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 AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + } + + /// + /// Gets the media item by id + /// + /// + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForMedia("id")] + public MediaItemDisplay GetById(Guid id) + { + var foundContent = GetObjectFromRequest(() => Services.MediaService.GetById(id)); + + if (foundContent == null) + { + HandleContentNotFound(id); + //HandleContentNotFound will throw an exception + return null; + } + return AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext); + } + + /// + /// Gets the media item by id + /// + /// + /// + [OutgoingEditorModelEvent] + [EnsureUserPermissionForMedia("id")] + public MediaItemDisplay GetById(Udi id) + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + return GetById(guidUdi.Guid); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + /// + /// Return media for the specified ids + /// + /// + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable))] + public IEnumerable GetByIds([FromUri]int[] ids) + { + var foundMedia = Services.MediaService.GetByIds(ids); + return foundMedia.Select(media => AutoMapperExtensions.MapWithUmbracoContext(media, UmbracoContext)); + } + + /// + /// Returns media items known to be of a "Folder" type + /// + /// + /// + [Obsolete("This is no longer used and shouldn't be because it performs poorly when there are a lot of media items")] + [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] + public IEnumerable> GetChildFolders(int id = -1) + { + //we are only allowing a max of 500 to be returned here, if more is required it needs to be paged + var result = GetChildFolders(id, 1, 500); + return result.Items; + } + + /// + /// Returns a paged result of media items known to be of a "Folder" type + /// + /// + /// + /// + /// + public PagedResult> GetChildFolders(int id, int pageNumber, int pageSize) + { + //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() + .Where(x => x.Alias.EndsWith("Folder")) + .Select(x => x.Id) + .ToArray(); + + if (folderTypes.Length == 0) + { + return new PagedResult>(0, pageNumber, pageSize); + } + + long total; + var children = Services.MediaService.GetPagedChildren(id, pageNumber - 1, pageSize, out total, "Name", Direction.Ascending, true, null, folderTypes.ToArray()); + + return new PagedResult>(total, pageNumber, pageSize) + { + Items = children.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>); + } + + #region GetChildren + + private int[] _userStartNodes; + protected int[] UserStartNodes + { + get { return _userStartNodes ?? (_userStartNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService)); } + } + + /// + /// Returns the child media objects - using the entity INT id + /// + [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 = "", + Guid? dataTypeId = null) + { + //if a request is made for the root node data but the user's start node is not the default, then + // we need to return their start nodes + if (id == Constants.System.Root && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false) + { + var ignoreUserStartNodes = dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); + if (ignoreUserStartNodes == false) + { + if (pageNumber > 0) + return new PagedResult>(0, 0, 0); + var nodes = Services.MediaService.GetByIds(UserStartNodes).ToArray(); + if (nodes.Length == 0) + return new PagedResult>(0, 0, 0); + if (pageSize < nodes.Length) pageSize = nodes.Length; // bah + var pr = new PagedResult>(nodes.Length, pageNumber, pageSize) + { + Items = nodes.Select(Mapper.Map>) + }; + return pr; + } + } + + // else proceed as usual + + long 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; + } + + /// + /// This method is obsolete, use the overload with dataTypeId instead + /// Returns the child media objects - using the entity GUID id + /// + /// + /// + /// + /// + /// + /// + /// + /// + [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(Guid id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + return GetChildren(id, null, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + } + + /// + /// Returns the child media objects - using the entity GUID id + /// + /// + /// + /// + /// + /// + /// + /// + /// If set used to lookup whether user and group start node permissions should be ignored. + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(Guid id, + Guid? dataTypeId, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + var entity = Services.EntityService.GetByKey(id); + if (entity != null) + { + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, dataTypeId); + } + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + /// + /// This method is obsolete, use the overload with dataTypeId instead + /// Returns the child media objects - using the entity UDI id + /// + /// + /// + /// + /// + /// + /// + /// + /// + [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(Udi id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + return GetChildren(id, null, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + } + + /// + /// Returns the child media objects - using the entity UDI id + /// + /// + /// + /// + /// + /// + /// + /// + /// If set used to lookup whether the user and group start node permissions will be ignored. + /// + [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] + public PagedResult> GetChildren(Udi id, + Guid? dataTypeId, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + var guidUdi = id as GuidUdi; + if (guidUdi != null) + { + var entity = Services.EntityService.GetByKey(guidUdi.Guid); + if (entity != null) + { + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, dataTypeId); + } + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + [Obsolete("Do not use this method, use either the overload with INT or GUID instead, this will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] + [UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)] + public PagedResult> GetChildren(string id, + int pageNumber = 0, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + bool orderBySystemField = true, + string filter = "") + { + foreach (var type in new[] { typeof(int), typeof(Guid) }) + { + var parsed = id.TryConvertTo(type); + if (parsed) + { + //oooh magic! will auto select the right overload + return GetChildren((dynamic)parsed.Result); + } + } + + throw new HttpResponseException(HttpStatusCode.NotFound); + } + #endregion + + /// + /// 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, 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, 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); + var destinationParentID = move.ParentId; + var sourceParentID = toMove.ParentId; + + var moveResult = Services.MediaService.WithResult().Move(toMove, move.ParentId, Security.CurrentUser.Id); + + if (sourceParentID == destinationParentID) + { + return Request.CreateValidationErrorResponse(new SimpleNotificationModel(new Notification("",Services.TextService.Localize("media/moveToSameFolderFailed"),SpeechBubbleIcon.Error))); + } + if (moveResult == false) + { + return Request.CreateValidationErrorResponse(new SimpleNotificationModel()); + } + else + { + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json"); + return response; + } + } + + /// + /// Saves content + /// + /// + [FileUploadCleanupFilter] + [MediaPostValidate] + [OutgoingEditorModelEvent] + public MediaItemDisplay PostSave( + [ModelBinder(typeof(MediaItemBinder))] + MediaItemSave contentItem) + { + //Recent versions of IE/Edge may send in the full clientside file path instead of just the file name. + //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all + //uploaded files to being *only* the actual file name (as it should be). + if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any()) + { + foreach (var file in contentItem.UploadedFiles) + { + file.FileName = Path.GetFileName(file.FileName); + } + } + + //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 = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); + forDisplay.Errors = ModelState.ToErrorDictionary(); + throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); + } + } + + //save the item + var saveStatus = Services.MediaService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id); + + //return the updated model + var display = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext); + + //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(Security.CurrentUser.Id); + + return Request.CreateNotificationSuccessResponse(Services.TextService.Localize("defaultdialogs/recycleBinIsEmpty")); + } + + /// + /// 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; + } + } + + public MediaItemDisplay PostAddFolder(PostedFolder folder) + { + var intParentId = GetParentIdAsInt(folder.ParentId, validatePermissions:true); + + var mediaService = ApplicationContext.Services.MediaService; + + var f = mediaService.CreateMedia(folder.Name, intParentId, Constants.Conventions.MediaTypes.Folder); + mediaService.Save(f, Security.CurrentUser.Id); + + return AutoMapperExtensions.MapWithUmbracoContext(f, UmbracoContext); + } + + /// + /// 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 + string currentFolderId = result.FormData["currentFolder"]; + int parentId = GetParentIdAsInt(currentFolderId, validatePermissions: true); + + 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[] { '\"' }).TrimEnd(); + var safeFileName = fileName.ToSafeFileName(); + var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower(); + + if (UmbracoConfig.For.UmbracoSettings().Content.IsFileAllowedForUpload(ext)) + { + var mediaType = Constants.Conventions.MediaTypes.File; + + if (result.FormData["contentTypeAlias"] == Constants.Conventions.MediaTypes.AutoSelect) + { + if (UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.Contains(ext)) + { + mediaType = Constants.Conventions.MediaTypes.Image; + } + } + else + { + mediaType = result.FormData["contentTypeAlias"]; + } + + var mediaItemName = fileName.ToFriendlyName(); + + 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"), + Services.TextService.Localize("media/disallowedFileType"), + 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); + } + + /// + /// Given a parent id which could be a GUID, UDI or an INT, this will resolve the INT + /// + /// + /// + /// If true, this will check if the current user has access to the resolved integer parent id + /// and if that check fails an unauthorized exception will occur + /// + /// + private int GetParentIdAsInt(string parentId, bool validatePermissions) + { + int intParentId; + GuidUdi parentUdi; + + // test for udi + if (GuidUdi.TryParse(parentId, out parentUdi)) + { + parentId = parentUdi.Guid.ToString(); + } + + //if it's not an INT then we'll check for GUID + if (int.TryParse(parentId, out intParentId) == false) + { + // if a guid then try to look up the entity + Guid idGuid; + if (Guid.TryParse(parentId, out idGuid)) + { + var entity = Services.EntityService.GetByKey(idGuid); + if (entity != null) + { + intParentId = entity.Id; + } + else + { + throw new EntityNotFoundException(parentId, "The passed id doesn't exist"); + } + } + else + { + throw new HttpResponseException( + Request.CreateValidationErrorResponse("The request was not formatted correctly, the parentId is not an integer, Guid or UDI")); + } + } + + //ensure the user has access to this folder by parent id! + if (validatePermissions && CheckPermissions( + new Dictionary(), + Security.CurrentUser, + Services.MediaService, + Services.EntityService, + intParentId) == false) + { + throw new HttpResponseException(Request.CreateResponse( + HttpStatusCode.Forbidden, + new SimpleNotificationModel(new Notification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + Services.TextService.Localize("speechBubbles/invalidUserPermissionsText"), + SpeechBubbleIcon.Warning)))); + } + + return intParentId; + } + + /// + /// 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, IEntityService entityService, int nodeId, IMedia media = null) + { + if (storage == null) throw new ArgumentNullException("storage"); + if (user == null) throw new ArgumentNullException("user"); + if (mediaService == null) throw new ArgumentNullException("mediaService"); + if (entityService == null) throw new ArgumentNullException("entityService"); + + 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) + ? user.HasMediaRootAccess(entityService) + : (nodeId == Constants.System.RecycleBinMedia) + ? user.HasMediaBinAccess(entityService) + : user.HasPathAccess(media, entityService); + + return hasPathAccess; + } + } +} diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs index 4ed46de135..f2174ab120 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs @@ -1,46 +1,51 @@ -using System.ComponentModel.DataAnnotations; -using System.Runtime.Serialization; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.Models.ContentEditing -{ - /// - /// Represents a content property to be saved - /// - [DataContract(Name = "property", Namespace = "")] - public class ContentPropertyBasic - { - /// - /// This is the cmsPropertyData ID - /// - /// - /// This is not really used for anything - /// - [DataMember(Name = "id", IsRequired = true)] - [Required] - public int Id { get; set; } - - [DataMember(Name = "value")] - public object Value { get; set; } - - [DataMember(Name = "alias", IsRequired = true)] - [Required(AllowEmptyStrings = false)] - public string Alias { get; set; } - - [DataMember(Name = "editor", IsRequired = false)] - public string Editor { get; set; } - - /// - /// Flags the property to denote that it can contain sensitive data - /// - [DataMember(Name = "isSensitive", IsRequired = false)] - public bool IsSensitive { get; set; } - - /// - /// Used internally during model mapping - /// - [IgnoreDataMember] - internal PropertyEditor PropertyEditor { get; set; } - - } -} +using System; +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Represents a content property to be saved + /// + [DataContract(Name = "property", Namespace = "")] + public class ContentPropertyBasic + { + /// + /// This is the cmsPropertyData ID + /// + /// + /// This is not really used for anything + /// + [DataMember(Name = "id", IsRequired = true)] + [Required] + public int Id { get; set; } + + [DataMember(Name = "dataTypeId", IsRequired = true)] + [Required] + public Guid DataTypeId { get; set; } + + [DataMember(Name = "value")] + public object Value { get; set; } + + [DataMember(Name = "alias", IsRequired = true)] + [Required(AllowEmptyStrings = false)] + public string Alias { get; set; } + + [DataMember(Name = "editor", IsRequired = false)] + public string Editor { get; set; } + + /// + /// Flags the property to denote that it can contain sensitive data + /// + [DataMember(Name = "isSensitive", IsRequired = false)] + public bool IsSensitive { get; set; } + + /// + /// Used internally during model mapping + /// + [IgnoreDataMember] + internal PropertyEditor PropertyEditor { get; set; } + + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs index ccf22a8c34..02cf570f7d 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs @@ -1,75 +1,78 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Creates a base generic ContentPropertyBasic from a Property - /// - /// - internal class ContentPropertyBasicConverter : ITypeConverter - where T : ContentPropertyBasic, new() - { - protected IDataTypeService DataTypeService { get; private set; } - - private static readonly List ComplexPropertyTypeAliases = new List {"Umbraco.NestedContent"}; - - public ContentPropertyBasicConverter(IDataTypeService dataTypeService) - { - DataTypeService = dataTypeService; - } - - /// - /// Assigns the PropertyEditor, Id, Alias and Value to the property - /// - /// - public virtual T Convert(ResolutionContext context) - { - var property = context.SourceValue as Property; - if (property == null) - throw new InvalidOperationException("Source value is not a property."); - - var editor = PropertyEditorResolver.Current.GetByAlias(property.PropertyType.PropertyEditorAlias); - if (editor == null) - { - LogHelper.Error>( - "No property editor found, converting to a Label", - new NullReferenceException("The property editor with alias " + - property.PropertyType.PropertyEditorAlias + " does not exist")); - - editor = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias); - } - - var result = new T - { - Id = property.Id, - Alias = property.Alias, - PropertyEditor = editor, - Editor = editor.Alias - }; - - // if there's a set of property aliases specified, we will check if the current property's value should be mapped. - // if it isn't one of the ones specified in 'includeProperties', we will just return the result without mapping the Value. - if (context.Options.Items.ContainsKey("IncludeProperties")) - { - var includeProperties = context.Options.Items["IncludeProperties"] as IEnumerable; - if (includeProperties != null && includeProperties.Contains(property.Alias) == false) - { - return result; - } - } - - // if no 'IncludeProperties' were specified or this property is set to be included - we will map the value and return. - result.Value = editor.ValueEditor.ConvertDbToEditor(property, property.PropertyType, DataTypeService); - return result; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Creates a base generic ContentPropertyBasic from a Property + /// + /// + internal class ContentPropertyBasicConverter : ITypeConverter + where T : ContentPropertyBasic, new() + { + protected IDataTypeService DataTypeService { get; private set; } + + private static readonly List ComplexPropertyTypeAliases = new List {"Umbraco.NestedContent"}; + + public ContentPropertyBasicConverter(IDataTypeService dataTypeService) + { + DataTypeService = dataTypeService; + } + + /// + /// Assigns the PropertyEditor, Id, Alias and Value to the property + /// + /// + public virtual T Convert(ResolutionContext context) + { + var property = context.SourceValue as Property; + if (property == null) + throw new InvalidOperationException("Source value is not a property."); + + var editor = PropertyEditorResolver.Current.GetByAlias(property.PropertyType.PropertyEditorAlias); + if (editor == null) + { + LogHelper.Error>( + "No property editor found, converting to a Label", + new NullReferenceException("The property editor with alias " + + property.PropertyType.PropertyEditorAlias + " does not exist")); + + editor = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias); + } + + var dataTypeDefinition = DataTypeService.GetDataTypeDefinitionById(property.PropertyType.DataTypeDefinitionId); + + var result = new T + { + Id = property.Id, + DataTypeId = dataTypeDefinition.Key, + Alias = property.Alias, + PropertyEditor = editor, + Editor = editor.Alias + }; + + // if there's a set of property aliases specified, we will check if the current property's value should be mapped. + // if it isn't one of the ones specified in 'includeProperties', we will just return the result without mapping the Value. + if (context.Options.Items.ContainsKey("IncludeProperties")) + { + var includeProperties = context.Options.Items["IncludeProperties"] as IEnumerable; + if (includeProperties != null && includeProperties.Contains(property.Alias) == false) + { + return result; + } + } + + // if no 'IncludeProperties' were specified or this property is set to be included - we will map the value and return. + result.Value = editor.ValueEditor.ConvertDbToEditor(property, property.PropertyType, DataTypeService); + return result; + } + } +} diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index 2ea41ff128..f2f83d1133 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -1,401 +1,407 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Net.Http.Formatting; -using System.Web.Http.ModelBinding; -using Umbraco.Core; -using Umbraco.Core.Events; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.WebApi; -using Umbraco.Web.WebApi.Filters; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Web.Search; - -namespace Umbraco.Web.Trees -{ - /// - /// A base controller reference for non-attributed trees (un-registered). Developers should inherit from - /// TreeController. - /// - [AngularJsonOnlyConfiguration] - public abstract class TreeControllerBase : UmbracoAuthorizedApiController - { - protected TreeControllerBase() - { - } - - protected TreeControllerBase(UmbracoContext umbracoContext) : base(umbracoContext) - { - } - - protected TreeControllerBase(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) : base(umbracoContext, umbracoHelper) - { - } - - /// - /// The method called to render the contents of the tree structure - /// - /// - /// - /// All of the query string parameters passed from jsTree - /// - /// - /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end - /// to the back end to be used in the query for model data. - /// - protected abstract TreeNodeCollection GetTreeNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings); - - /// - /// Returns the menu structure for the node - /// - /// - /// - /// - protected abstract MenuItemCollection GetMenuForNode(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings); - - /// - /// The name to display on the root node - /// - public abstract string RootNodeDisplayName { get; } - - /// - /// Gets the current tree alias from the attribute assigned to it. - /// - public abstract string TreeAlias { get; } - - /// - /// Returns the root node for the tree - /// - /// - /// - public TreeNode GetRootNode([ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) - { - if (queryStrings == null) queryStrings = new FormDataCollection(""); - var node = CreateRootNode(queryStrings); - - //add the tree alias to the root - node.AdditionalData["treeAlias"] = TreeAlias; - - AddQueryStringsToAdditionalData(node, queryStrings); - - //check if the tree is searchable and add that to the meta data as well - if (this is ISearchableTree) - { - node.AdditionalData.Add("searchable", "true"); - } - - //now update all data based on some of the query strings, like if we are running in dialog mode - if (IsDialog(queryStrings)) - { - node.RoutePath = "#"; - } - - OnRootNodeRendering(this, new TreeNodeRenderingEventArgs(node, queryStrings)); - - return node; - } - - /// - /// The action called to render the contents of the tree structure - /// - /// - /// - /// All of the query string parameters passed from jsTree - /// - /// JSON markup for jsTree - /// - /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end - /// to the back end to be used in the query for model data. - /// - public TreeNodeCollection GetNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) - { - if (queryStrings == null) queryStrings = new FormDataCollection(""); - var nodes = GetTreeNodes(id, queryStrings); - - foreach (var node in nodes) - { - AddQueryStringsToAdditionalData(node, queryStrings); - } - - //now update all data based on some of the query strings, like if we are running in dialog mode - if (IsDialog((queryStrings))) - { - foreach (var node in nodes) - { - node.RoutePath = "#"; - } - } - - //raise the event - OnTreeNodesRendering(this, new TreeNodesRenderingEventArgs(nodes, queryStrings)); - - return nodes; - } - - /// - /// The action called to render the menu for a tree node - /// - /// - /// - /// - public MenuItemCollection GetMenu(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) - { - if (queryStrings == null) queryStrings = new FormDataCollection(""); - var menu = GetMenuForNode(id, queryStrings); - //raise the event - OnMenuRendering(this, new MenuRenderingEventArgs(id, menu, queryStrings)); - return menu; - } - - /// - /// Helper method to create a root model for a tree - /// - /// - protected virtual TreeNode CreateRootNode(FormDataCollection queryStrings) - { - var rootNodeAsString = Constants.System.Root.ToString(CultureInfo.InvariantCulture); - var currApp = queryStrings.GetValue(TreeQueryStringParameters.Application); - - var node = new TreeNode( - rootNodeAsString, - null, //this is a root node, there is no parent - Url.GetTreeUrl(GetType(), rootNodeAsString, queryStrings), - Url.GetMenuUrl(GetType(), rootNodeAsString, queryStrings)) - { - HasChildren = true, - RoutePath = currApp, - Name = RootNodeDisplayName - }; - - return node; - } - - #region Create TreeNode methods - - /// - /// Helper method to create tree nodes - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title) - { - var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); - var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); - var node = new TreeNode(id, parentId, jsonUrl, menuUrl) { Name = title }; - return node; - } - - /// - /// Helper method to create tree nodes - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon) - { - var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); - var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); - var node = new TreeNode(id, parentId, jsonUrl, menuUrl) - { - Name = title, - Icon = icon, - NodeType = TreeAlias - }; - return node; - } - - /// - /// Helper method to create tree nodes - /// - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, string routePath) - { - var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); - var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); - var node = new TreeNode(id, parentId, jsonUrl, menuUrl) { Name = title, RoutePath = routePath, Icon = icon }; - return node; - } - - /// - /// Helper method to create tree nodes and automatically generate the json url + UDI - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(UmbracoEntity entity, Guid entityObjectType, string parentId, FormDataCollection queryStrings, bool hasChildren) - { - var treeNode = CreateTreeNode(entity.Id.ToInvariantString(), parentId, queryStrings, entity.Name, entity.ContentTypeIcon); - treeNode.Path = entity.Path; - treeNode.Udi = Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(entityObjectType), entity.Key); - treeNode.HasChildren = hasChildren; - return treeNode; - } - - /// - /// Helper method to create tree nodes and automatically generate the json url + UDI - /// - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(IUmbracoEntity entity, Guid entityObjectType, string parentId, FormDataCollection queryStrings, string icon, bool hasChildren) - { - var treeNode = CreateTreeNode(entity.Id.ToInvariantString(), parentId, queryStrings, entity.Name, icon); - treeNode.Path = entity.Path; - treeNode.Udi = Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(entityObjectType), entity.Key); - treeNode.HasChildren = hasChildren; - return treeNode; - } - - /// - /// Helper method to create tree nodes and automatically generate the json url - /// - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren) - { - var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); - treeNode.HasChildren = hasChildren; - return treeNode; - } - - /// - /// Helper method to create tree nodes and automatically generate the json url - /// - /// - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren, string routePath) - { - var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); - treeNode.HasChildren = hasChildren; - treeNode.RoutePath = routePath; - return treeNode; - } - - /// - /// Helper method to create tree nodes and automatically generate the json url + UDI - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren, string routePath, Udi udi) - { - var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); - treeNode.HasChildren = hasChildren; - treeNode.RoutePath = routePath; - treeNode.Udi = udi; - return treeNode; - } - - #endregion - - /// - /// The AdditionalData of a node is always populated with the query string data, this method performs this - /// operation and ensures that special values are not inserted or that duplicate keys are not added. - /// - /// - /// - protected void AddQueryStringsToAdditionalData(TreeNode node, FormDataCollection queryStrings) - { - foreach (var q in queryStrings.Where(x => node.AdditionalData.ContainsKey(x.Key) == false)) - { - node.AdditionalData.Add(q.Key, q.Value); - } - } - - /// - /// If the request is for a dialog mode tree - /// - /// - /// - protected bool IsDialog(FormDataCollection queryStrings) - { - return queryStrings.GetValue(TreeQueryStringParameters.IsDialog); - } - - /// - /// If the request should allows a user to choose nodes that they normally don't have access to - /// - /// - /// - protected bool IgnoreUserStartNodes(FormDataCollection queryStrings) - { - return queryStrings.GetValue(TreeQueryStringParameters.IgnoreUserStartNodes); - } - - /// - /// An event that allows developers to modify the tree node collection that is being rendered - /// - /// - /// Developers can add/remove/replace/insert/update/etc... any of the tree items in the collection. - /// - public static event TypedEventHandler TreeNodesRendering; - - private static void OnTreeNodesRendering(TreeControllerBase instance, TreeNodesRenderingEventArgs e) - { - var handler = TreeNodesRendering; - if (handler != null) handler(instance, e); - } - - /// - /// An event that allows developer to modify the root tree node that is being rendered - /// - public static event TypedEventHandler RootNodeRendering; - - private static void OnRootNodeRendering(TreeControllerBase instance, TreeNodeRenderingEventArgs e) - { - var handler = RootNodeRendering; - if (handler != null) handler(instance, e); - } - - /// - /// An event that allows developers to modify the meun that is being rendered - /// - /// - /// Developers can add/remove/replace/insert/update/etc... any of the tree items in the collection. - /// - public static event TypedEventHandler MenuRendering; - - private static void OnMenuRendering(TreeControllerBase instance, MenuRenderingEventArgs e) - { - var handler = MenuRendering; - if (handler != null) handler(instance, e); - } - } -} +using System; +using System.Globalization; +using System.Linq; +using System.Net.Http.Formatting; +using System.Web.Http.ModelBinding; +using Umbraco.Core; +using Umbraco.Core.Events; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.WebApi; +using Umbraco.Web.WebApi.Filters; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Web.Search; + +namespace Umbraco.Web.Trees +{ + /// + /// A base controller reference for non-attributed trees (un-registered). Developers should inherit from + /// TreeController. + /// + [AngularJsonOnlyConfiguration] + public abstract class TreeControllerBase : UmbracoAuthorizedApiController + { + protected TreeControllerBase() + { + } + + protected TreeControllerBase(UmbracoContext umbracoContext) : base(umbracoContext) + { + } + + protected TreeControllerBase(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) : base(umbracoContext, umbracoHelper) + { + } + + /// + /// The method called to render the contents of the tree structure + /// + /// + /// + /// All of the query string parameters passed from jsTree + /// + /// + /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end + /// to the back end to be used in the query for model data. + /// + protected abstract TreeNodeCollection GetTreeNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings); + + /// + /// Returns the menu structure for the node + /// + /// + /// + /// + protected abstract MenuItemCollection GetMenuForNode(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings); + + /// + /// The name to display on the root node + /// + public abstract string RootNodeDisplayName { get; } + + /// + /// Gets the current tree alias from the attribute assigned to it. + /// + public abstract string TreeAlias { get; } + + /// + /// Returns the root node for the tree + /// + /// + /// + public TreeNode GetRootNode([ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) + { + if (queryStrings == null) queryStrings = new FormDataCollection(""); + var node = CreateRootNode(queryStrings); + + //add the tree alias to the root + node.AdditionalData["treeAlias"] = TreeAlias; + + AddQueryStringsToAdditionalData(node, queryStrings); + + //check if the tree is searchable and add that to the meta data as well + if (this is ISearchableTree) + { + node.AdditionalData.Add("searchable", "true"); + } + + //now update all data based on some of the query strings, like if we are running in dialog mode + if (IsDialog(queryStrings)) + { + node.RoutePath = "#"; + } + + OnRootNodeRendering(this, new TreeNodeRenderingEventArgs(node, queryStrings)); + + return node; + } + + /// + /// The action called to render the contents of the tree structure + /// + /// + /// + /// All of the query string parameters passed from jsTree + /// + /// JSON markup for jsTree + /// + /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end + /// to the back end to be used in the query for model data. + /// + public TreeNodeCollection GetNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) + { + if (queryStrings == null) queryStrings = new FormDataCollection(""); + var nodes = GetTreeNodes(id, queryStrings); + + foreach (var node in nodes) + { + AddQueryStringsToAdditionalData(node, queryStrings); + } + + //now update all data based on some of the query strings, like if we are running in dialog mode + if (IsDialog((queryStrings))) + { + foreach (var node in nodes) + { + node.RoutePath = "#"; + } + } + + //raise the event + OnTreeNodesRendering(this, new TreeNodesRenderingEventArgs(nodes, queryStrings)); + + return nodes; + } + + /// + /// The action called to render the menu for a tree node + /// + /// + /// + /// + public MenuItemCollection GetMenu(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) + { + if (queryStrings == null) queryStrings = new FormDataCollection(""); + var menu = GetMenuForNode(id, queryStrings); + //raise the event + OnMenuRendering(this, new MenuRenderingEventArgs(id, menu, queryStrings)); + return menu; + } + + /// + /// Helper method to create a root model for a tree + /// + /// + protected virtual TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var rootNodeAsString = Constants.System.Root.ToString(CultureInfo.InvariantCulture); + var currApp = queryStrings.GetValue(TreeQueryStringParameters.Application); + + var node = new TreeNode( + rootNodeAsString, + null, //this is a root node, there is no parent + Url.GetTreeUrl(GetType(), rootNodeAsString, queryStrings), + Url.GetMenuUrl(GetType(), rootNodeAsString, queryStrings)) + { + HasChildren = true, + RoutePath = currApp, + Name = RootNodeDisplayName + }; + + return node; + } + + #region Create TreeNode methods + + /// + /// Helper method to create tree nodes + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title) + { + var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); + var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); + var node = new TreeNode(id, parentId, jsonUrl, menuUrl) { Name = title }; + return node; + } + + /// + /// Helper method to create tree nodes + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon) + { + var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); + var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); + var node = new TreeNode(id, parentId, jsonUrl, menuUrl) + { + Name = title, + Icon = icon, + NodeType = TreeAlias + }; + return node; + } + + /// + /// Helper method to create tree nodes + /// + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, string routePath) + { + var jsonUrl = Url.GetTreeUrl(GetType(), id, queryStrings); + var menuUrl = Url.GetMenuUrl(GetType(), id, queryStrings); + var node = new TreeNode(id, parentId, jsonUrl, menuUrl) { Name = title, RoutePath = routePath, Icon = icon }; + return node; + } + + /// + /// Helper method to create tree nodes and automatically generate the json url + UDI + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(UmbracoEntity entity, Guid entityObjectType, string parentId, FormDataCollection queryStrings, bool hasChildren) + { + var treeNode = CreateTreeNode(entity.Id.ToInvariantString(), parentId, queryStrings, entity.Name, entity.ContentTypeIcon); + treeNode.Path = entity.Path; + treeNode.Udi = Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(entityObjectType), entity.Key); + treeNode.HasChildren = hasChildren; + return treeNode; + } + + /// + /// Helper method to create tree nodes and automatically generate the json url + UDI + /// + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(IUmbracoEntity entity, Guid entityObjectType, string parentId, FormDataCollection queryStrings, string icon, bool hasChildren) + { + var treeNode = CreateTreeNode(entity.Id.ToInvariantString(), parentId, queryStrings, entity.Name, icon); + treeNode.Path = entity.Path; + treeNode.Udi = Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(entityObjectType), entity.Key); + treeNode.HasChildren = hasChildren; + return treeNode; + } + + /// + /// Helper method to create tree nodes and automatically generate the json url + /// + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren) + { + var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); + treeNode.HasChildren = hasChildren; + return treeNode; + } + + /// + /// Helper method to create tree nodes and automatically generate the json url + /// + /// + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren, string routePath) + { + var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); + treeNode.HasChildren = hasChildren; + treeNode.RoutePath = routePath; + return treeNode; + } + + /// + /// Helper method to create tree nodes and automatically generate the json url + UDI + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public TreeNode CreateTreeNode(string id, string parentId, FormDataCollection queryStrings, string title, string icon, bool hasChildren, string routePath, Udi udi) + { + var treeNode = CreateTreeNode(id, parentId, queryStrings, title, icon); + treeNode.HasChildren = hasChildren; + treeNode.RoutePath = routePath; + treeNode.Udi = udi; + return treeNode; + } + + #endregion + + /// + /// The AdditionalData of a node is always populated with the query string data, this method performs this + /// operation and ensures that special values are not inserted or that duplicate keys are not added. + /// + /// + /// + protected void AddQueryStringsToAdditionalData(TreeNode node, FormDataCollection queryStrings) + { + foreach (var q in queryStrings.Where(x => node.AdditionalData.ContainsKey(x.Key) == false)) + { + node.AdditionalData.Add(q.Key, q.Value); + } + } + + /// + /// If the request is for a dialog mode tree + /// + /// + /// + protected bool IsDialog(FormDataCollection queryStrings) + { + return queryStrings.GetValue(TreeQueryStringParameters.IsDialog); + } + + /// + /// If the request should allows a user to choose nodes that they normally don't have access to + /// + /// + /// + protected bool IgnoreUserStartNodes(FormDataCollection queryStrings) + { + var dataTypeId = queryStrings.GetValue(TreeQueryStringParameters.DataTypeId); + if (dataTypeId.HasValue) + { + return Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); + } + + return false; + } + + /// + /// An event that allows developers to modify the tree node collection that is being rendered + /// + /// + /// Developers can add/remove/replace/insert/update/etc... any of the tree items in the collection. + /// + public static event TypedEventHandler TreeNodesRendering; + + private static void OnTreeNodesRendering(TreeControllerBase instance, TreeNodesRenderingEventArgs e) + { + var handler = TreeNodesRendering; + if (handler != null) handler(instance, e); + } + + /// + /// An event that allows developer to modify the root tree node that is being rendered + /// + public static event TypedEventHandler RootNodeRendering; + + private static void OnRootNodeRendering(TreeControllerBase instance, TreeNodeRenderingEventArgs e) + { + var handler = RootNodeRendering; + if (handler != null) handler(instance, e); + } + + /// + /// An event that allows developers to modify the meun that is being rendered + /// + /// + /// Developers can add/remove/replace/insert/update/etc... any of the tree items in the collection. + /// + public static event TypedEventHandler MenuRendering; + + private static void OnMenuRendering(TreeControllerBase instance, MenuRenderingEventArgs e) + { + var handler = MenuRendering; + if (handler != null) handler(instance, e); + } + } +} diff --git a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs index c79b9f8781..20e02d1c42 100644 --- a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs +++ b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs @@ -1,15 +1,15 @@ -namespace Umbraco.Web.Trees -{ - /// - /// Common query string parameters used for tree query strings - /// - internal struct TreeQueryStringParameters - { - public const string IsDialog = "isDialog"; - public const string Application = "application"; - public const string StartNodeId = "startNodeId"; - public const string IgnoreUserStartNodes = "ignoreUserStartNodes"; - //public const string OnNodeClick = "OnNodeClick"; - //public const string RenderParent = "RenderParent"; - } -} +namespace Umbraco.Web.Trees +{ + /// + /// Common query string parameters used for tree query strings + /// + internal struct TreeQueryStringParameters + { + public const string IsDialog = "isDialog"; + public const string Application = "application"; + public const string StartNodeId = "startNodeId"; + public const string DataTypeId = "dataTypeId"; + //public const string OnNodeClick = "OnNodeClick"; + //public const string RenderParent = "RenderParent"; + } +} diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs index 64fe9a4b65..57a7db4889 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -1,148 +1,154 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Web.Http; -using System.Web.Http.Controllers; -using System.Web.Http.Filters; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; -using Umbraco.Web.Editors; -using Umbraco.Web.Models.ContentEditing; -using umbraco.BusinessLogic.Actions; - -namespace Umbraco.Web.WebApi.Filters -{ - /// - /// Auth filter to check if the current user has access to the content item (by id). - /// - /// - /// - /// This first checks if the user can access this based on their start node, and then checks node permissions - /// - /// By default the permission that is checked is browse but this can be specified in the ctor. - /// NOTE: This cannot be an auth filter because that happens too soon and we don't have access to the action params. - /// - public sealed class EnsureUserPermissionForContentAttribute : ActionFilterAttribute - { - private readonly int? _nodeId; - private readonly string _paramName; - private readonly char? _permissionToCheck; - - /// - /// This constructor will only be able to test the start node access - /// - public EnsureUserPermissionForContentAttribute(int nodeId) - { - _nodeId = nodeId; - } - - public EnsureUserPermissionForContentAttribute(int nodeId, char permissionToCheck) - : this(nodeId) - { - _permissionToCheck = permissionToCheck; - } - - public EnsureUserPermissionForContentAttribute(string paramName) - { - if (string.IsNullOrWhiteSpace(paramName)) throw new ArgumentException("Value cannot be null or whitespace.", "paramName"); - - _paramName = paramName; - _permissionToCheck = ActionBrowse.Instance.Letter; - } - - public EnsureUserPermissionForContentAttribute(string paramName, char permissionToCheck) - : this(paramName) - { - _permissionToCheck = permissionToCheck; - } - - public override bool AllowMultiple - { - get { return true; } - } - - public override void OnActionExecuting(HttpActionContext actionContext) - { - if (UmbracoContext.Current.Security.CurrentUser == null) - { - //not logged in - throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized); - } - - var ignoreUserStartNodes = actionContext.ActionArguments.ContainsKey("ignoreUserStartNodes") && - bool.Parse(actionContext.ActionArguments.GetValueAsString("ignoreUserStartNodes")); - - int nodeId; - if (_nodeId.HasValue == false) - { - var parts = _paramName.Split(new char[] {'.'}, StringSplitOptions.RemoveEmptyEntries); - - if (actionContext.ActionArguments[parts[0]] == null) - { - throw new InvalidOperationException("No argument found for the current action with the name: " + _paramName); - } - - if (parts.Length == 1) - { - var argument = actionContext.ActionArguments[parts[0]].ToString(); - // if the argument is an int, it will parse and can be assigned to nodeId - // if might be a udi, so check that next - // otherwise treat it as a guid - unlikely we ever get here - if (int.TryParse(argument, out int parsedId)) - { - nodeId = parsedId; - } - else if (Udi.TryParse(argument, true, out Udi udi)) - { - nodeId = ApplicationContext.Current.Services.EntityService.GetIdForUdi(udi).Result; - } - else - { - Guid.TryParse(argument, out Guid key); - nodeId = ApplicationContext.Current.Services.EntityService.GetIdForKey(key, UmbracoObjectTypes.Document).Result; - } - } - else - { - //now we need to see if we can get the property of whatever object it is - var pType = actionContext.ActionArguments[parts[0]].GetType(); - var prop = pType.GetProperty(parts[1]); - if (prop == null) - { - throw new InvalidOperationException("No argument found for the current action with the name: " + _paramName); - } - nodeId = (int)prop.GetValue(actionContext.ActionArguments[parts[0]]); - } - } - else - { - nodeId = _nodeId.Value; - } - - if (ContentController.CheckPermissions( - actionContext.Request.Properties, - UmbracoContext.Current.Security.CurrentUser, - ApplicationContext.Current.Services.UserService, - ApplicationContext.Current.Services.ContentService, - ApplicationContext.Current.Services.EntityService, - nodeId, - _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null, - ignoreUserStartNodes: ignoreUserStartNodes)) - { - base.OnActionExecuting(actionContext); - } - else - { - throw new HttpResponseException(actionContext.Request.CreateUserNoAccessResponse()); - } - - } - - - - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Web.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; +using Umbraco.Web.Editors; +using Umbraco.Web.Models.ContentEditing; +using umbraco.BusinessLogic.Actions; + +namespace Umbraco.Web.WebApi.Filters +{ + /// + /// Auth filter to check if the current user has access to the content item (by id). + /// + /// + /// + /// This first checks if the user can access this based on their start node, and then checks node permissions + /// + /// By default the permission that is checked is browse but this can be specified in the ctor. + /// NOTE: This cannot be an auth filter because that happens too soon and we don't have access to the action params. + /// + public sealed class EnsureUserPermissionForContentAttribute : ActionFilterAttribute + { + private readonly int? _nodeId; + private readonly string _paramName; + private readonly char? _permissionToCheck; + + /// + /// This constructor will only be able to test the start node access + /// + public EnsureUserPermissionForContentAttribute(int nodeId) + { + _nodeId = nodeId; + } + + public EnsureUserPermissionForContentAttribute(int nodeId, char permissionToCheck) + : this(nodeId) + { + _permissionToCheck = permissionToCheck; + } + + public EnsureUserPermissionForContentAttribute(string paramName) + { + if (string.IsNullOrWhiteSpace(paramName)) throw new ArgumentException("Value cannot be null or whitespace.", "paramName"); + + _paramName = paramName; + _permissionToCheck = ActionBrowse.Instance.Letter; + } + + public EnsureUserPermissionForContentAttribute(string paramName, char permissionToCheck) + : this(paramName) + { + _permissionToCheck = permissionToCheck; + } + + public override bool AllowMultiple + { + get { return true; } + } + + public override void OnActionExecuting(HttpActionContext actionContext) + { + if (UmbracoContext.Current.Security.CurrentUser == null) + { + //not logged in + throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized); + } + + var ignoreUserStartNodes = false; + + if (actionContext.ActionArguments.ContainsKey("dataTypeId") && + Guid.TryParse(actionContext.ActionArguments.GetValueAsString("dataTypeId"), out var dataTypeId)) + { + ignoreUserStartNodes = + ApplicationContext.Current.Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId); + } + + int nodeId; + if (_nodeId.HasValue == false) + { + var parts = _paramName.Split(new char[] {'.'}, StringSplitOptions.RemoveEmptyEntries); + + if (actionContext.ActionArguments[parts[0]] == null) + { + throw new InvalidOperationException("No argument found for the current action with the name: " + _paramName); + } + + if (parts.Length == 1) + { + var argument = actionContext.ActionArguments[parts[0]].ToString(); + // if the argument is an int, it will parse and can be assigned to nodeId + // if might be a udi, so check that next + // otherwise treat it as a guid - unlikely we ever get here + if (int.TryParse(argument, out int parsedId)) + { + nodeId = parsedId; + } + else if (Udi.TryParse(argument, true, out Udi udi)) + { + nodeId = ApplicationContext.Current.Services.EntityService.GetIdForUdi(udi).Result; + } + else + { + Guid.TryParse(argument, out Guid key); + nodeId = ApplicationContext.Current.Services.EntityService.GetIdForKey(key, UmbracoObjectTypes.Document).Result; + } + } + else + { + //now we need to see if we can get the property of whatever object it is + var pType = actionContext.ActionArguments[parts[0]].GetType(); + var prop = pType.GetProperty(parts[1]); + if (prop == null) + { + throw new InvalidOperationException("No argument found for the current action with the name: " + _paramName); + } + nodeId = (int)prop.GetValue(actionContext.ActionArguments[parts[0]]); + } + } + else + { + nodeId = _nodeId.Value; + } + + if (ContentController.CheckPermissions( + actionContext.Request.Properties, + UmbracoContext.Current.Security.CurrentUser, + ApplicationContext.Current.Services.UserService, + ApplicationContext.Current.Services.ContentService, + ApplicationContext.Current.Services.EntityService, + nodeId, + _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null, + ignoreUserStartNodes: ignoreUserStartNodes)) + { + base.OnActionExecuting(actionContext); + } + else + { + throw new HttpResponseException(actionContext.Request.CreateUserNoAccessResponse()); + } + + } + + + + } +} diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index 23d4fb871c..40f0ad2a79 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -8,13 +8,12 @@ using System.Web.Http.Filters; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; -using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Trees; namespace Umbraco.Web.WebApi.Filters { /// - /// This inspects the result of the action that returns a collection of content and removes + /// This inspects the result of the action that returns a collection of content and removes /// any item that the current user doesn't have access to /// internal class FilterAllowedOutgoingMediaAttribute : ActionFilterAttribute @@ -79,12 +78,18 @@ namespace Umbraco.Web.WebApi.Filters protected virtual void FilterItems(IUser user, IList items) { - bool.TryParse(HttpContext.Current.Request.QueryString.Get(TreeQueryStringParameters.IgnoreUserStartNodes), out var ignoreUserStartNodes); + + Guid? dataTypeId = Guid.TryParse(HttpContext.Current.Request.QueryString.Get(TreeQueryStringParameters.DataTypeId), out var temp) ? (Guid?)temp : null; + + if (dataTypeId.HasValue == false) return; + + var ignoreUserStartNodes = ApplicationContext.Current.Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); if (ignoreUserStartNodes == false) { FilterBasedOnStartNode(items, user); } + } internal void FilterBasedOnStartNode(IList items, IUser user) From c0f26f93b3f2ba4b6d7c2af0be30154853f7ac05 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 20 Jun 2019 22:07:59 +0200 Subject: [PATCH 007/776] WIP for property level copying --- .../PropertyEditors/DataEditorAttribute.cs | 5 +++++ src/Umbraco.Core/PropertyEditors/DataValueEditor.cs | 7 +++++++ .../PropertyEditors/IDataValueEditor.cs | 5 +++++ .../components/property/umbproperty.directive.js | 4 ++++ .../src/views/components/property/umb-property.html | 3 +++ .../nestedcontent/nestedcontent.controller.js | 13 ++++++++++--- .../Models/ContentEditing/ContentPropertyDisplay.cs | 3 +++ .../Models/Mapping/ContentPropertyDisplayMapper.cs | 1 + .../PropertyEditors/NestedContentPropertyEditor.cs | 2 +- 9 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs index ca08127d51..bf67522449 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs @@ -111,6 +111,11 @@ namespace Umbraco.Core.PropertyEditors /// public bool HideLabel { get; set; } + /// + /// Gets or sets a value indicating whether the editor value can be copied + /// + public bool CanCopy { get; set; } + /// /// Gets or sets an optional icon. /// diff --git a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs index c4380f032c..2f215c6032 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs @@ -57,6 +57,7 @@ namespace Umbraco.Core.PropertyEditors View = view; ValueType = attribute.ValueType; HideLabel = attribute.HideLabel; + CanCopy = attribute.CanCopy; } /// @@ -133,6 +134,12 @@ namespace Umbraco.Core.PropertyEditors [JsonProperty("hideLabel")] public bool HideLabel { get; set; } + /// + /// If this is true then the editor value can be copied + /// + [JsonProperty("canCopy")] + public bool CanCopy { get; set; } + /// /// Set this to true if the property editor is for display purposes only /// diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs index cb68531cc7..18670ccf4f 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs @@ -34,6 +34,11 @@ namespace Umbraco.Core.PropertyEditors /// bool HideLabel { get; } + /// + /// Gets a value indicating whether the value can be copied + /// + bool CanCopy { get; } + /// /// Validates a property value. /// diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js index 302378b8c0..1e4c810fec 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js @@ -32,6 +32,10 @@ angular.module("umbraco.directives") self.setPropertyError = function (errorMsg) { $scope.property.propertyErrorMessage = errorMsg; }; + + $scope.onCopy = function () { + $scope.$broadcast("propertyCopy"); + } } }; }); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html index 9d2588484c..01d8f2b616 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html @@ -17,6 +17,9 @@ * + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 4e8b35a276..2884212c85 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -547,12 +547,19 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop $scope.realCurrentNode = newVal; }); - var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { + var unsubscribe = []; + unsubscribe.push($scope.$on("formSubmitting", function (ev, args) { updateModel(); - }); + })); + unsubscribe.push($scope.$on("propertyCopy", function (ev, args) { + console.log("NC: I be the copy function by broadcast", $scope.model.alias); + })); $scope.$on("$destroy", function () { - unsubscribe(); + console.log("Unsubscribing", unsubscribe.length) + for (var u in unsubscribe) { + unsubscribe[u](); + } }); } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs index 39a4718dd0..add557c8fc 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs @@ -33,6 +33,9 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "hideLabel")] public bool HideLabel { get; set; } + [DataMember(Name = "canCopy")] + public bool CanCopy { get; set; } + [DataMember(Name = "validation")] public PropertyTypeValidation Validation { get; set; } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs index 8a45548e9c..7a10f7062b 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs @@ -39,6 +39,7 @@ namespace Umbraco.Web.Models.Mapping dest.Description = originalProp.PropertyType.Description; dest.Label = originalProp.PropertyType.Name; dest.HideLabel = valEditor.HideLabel; + dest.CanCopy = valEditor.CanCopy; //add the validation information dest.Validation.Mandatory = originalProp.PropertyType.Mandatory; diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index 6dee2f78b5..6fcb7ea642 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.PropertyEditors /// /// Represents a nested content property editor. /// - [DataEditor(Constants.PropertyEditors.Aliases.NestedContent, "Nested Content", "nestedcontent", ValueType = "JSON", Group = "lists", Icon = "icon-thumbnail-list")] + [DataEditor(Constants.PropertyEditors.Aliases.NestedContent, "Nested Content", "nestedcontent", ValueType = "JSON", Group = "lists", Icon = "icon-thumbnail-list", CanCopy = true)] public class NestedContentPropertyEditor : DataEditor { private readonly Lazy _propertyEditors; From 959a4b77bbb9358b02dd5072371f3e2e6232a7b6 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 20 Jun 2019 22:24:46 +0200 Subject: [PATCH 008/776] Remove inline property copying for NC --- .../propertyeditors/nestedcontent/nestedcontent.controller.js | 2 +- .../src/views/propertyeditors/nestedcontent/nestedcontent.html | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 2f4fa08e32..5c97a935a1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -596,7 +596,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop updateModel(); })); unsubscribe.push($scope.$on("propertyCopy", function (ev, args) { - console.log("NC: I be the copy function by broadcast", $scope.model.alias); + $scope.clickCopyAll(); })); $scope.$on("$destroy", function () { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html index a3196e8928..1eb7311ca7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html @@ -42,9 +42,6 @@ - - -
From 5fa0641a5ce9009c9d3f5717ebdecf8c4d5a5cf0 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 20 Jun 2019 22:39:06 +0200 Subject: [PATCH 009/776] Add hover state to the property copy icon --- src/Umbraco.Web.UI.Client/src/less/property-editors.less | 9 +++++++++ .../src/views/components/property/umb-property.html | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) 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 9e8dd37ab9..93cbeb60d1 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -59,7 +59,16 @@ border-radius: 3px; } +.umb-property .umb-property__copy { + font-size: 16px; + padding: 4px; + line-height: 10px; + &:hover { + color: @ui-option-type-hover; + text-decoration: none; + } +} // // Content picker diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html index 01d8f2b616..7f48ca250d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html @@ -18,7 +18,7 @@ * - + From b55d2a8a41c96f9e6461157f45c7f8df27d64733 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 21 Jun 2019 09:55:46 +0200 Subject: [PATCH 010/776] Modelsbuilder is asking for too old a version of the CodeAnalysis dependency --- build/NuSpecs/UmbracoCms.nuspec | 1 + 1 file changed, 1 insertion(+) diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 8b650edc13..7d864fb03a 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -22,6 +22,7 @@ + From ad4416f6633c95520d9dff8f009d2f43f5093b26 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 21 Jun 2019 10:07:45 +0200 Subject: [PATCH 011/776] So apparently Microsoft.CodeAnalysis.CSharp version 1.3.2 contains the dll that has the version 1.3.1.. very confusing --- build/NuSpecs/UmbracoCms.nuspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 7d864fb03a..838ec5eff0 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -22,7 +22,7 @@ - + From 5ebdfc7b1ce7e9329af64e51f1004a481a8fefb4 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 21 Jun 2019 10:22:16 +0200 Subject: [PATCH 012/776] Previously added a few too many binding redirects to the web.config --- src/Umbraco.Web.UI/web.Template.config | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 583952f29d..4ff70db974 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -438,22 +438,6 @@ - - - - - - - - - - - - - - - - From 2898203cb9d21acfb3430f5b04a50e991de414b8 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 21 Jun 2019 10:35:05 +0200 Subject: [PATCH 013/776] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 Bugfixes for the "Bypass security" feature + filter out possible sensitive data --- .../src/common/resources/content.resource.js | 6 +++- .../src/common/resources/entity.resource.js | 15 +++++--- .../contentpicker/contentpicker.controller.js | 2 +- .../ContentEditing/ContentPropertyBasic.cs | 5 ++- ...EnsureUserPermissionForContentAttribute.cs | 17 +++++++--- .../FilterAllowedOutgoingMediaAttribute.cs | 34 +++++++++++++++++++ 6 files changed, 65 insertions(+), 14 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 31eccfb5aa..d85cc74d7d 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 @@ -331,12 +331,16 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { //now copy back to the options we will use options = defaults; + var args = [{ id: id }]; + if(options.dataTypeId){ + args.push({ dataTypeId: options.dataTypeId }); + } return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "contentApiBaseUrl", "GetById", - [{ id: id }, { dataTypeId: options.dataTypeId }])), + args)), 'Failed to retrieve data for content id ' + id); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index fff4ddde37..3991932d2d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -304,16 +304,21 @@ function entityResource($q, $http, umbRequestHelper) { //now copy back to the options we will use options = defaults; + + var args = [ + { id: id }, + { type: type }, + ]; + if(options.dataTypeId){ + args.push({dataTypeId: options.dataTypeId}); + } + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetAncestors", - [ - { id: id }, - { type: type }, - { dataTypeId: options.dataTypeId } - ])), + args)), 'Failed to retrieve ancestor data for id ' + id); }, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 627baa3e7a..187b86a811 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -133,7 +133,6 @@ function contentPickerController($scope, entityResource, editorState, iconHelper idType: "int" }; - dialogOptions.dataTypeId = $scope.model.dataTypeId; //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the // pre-value config on to the dialog options angular.extend(dialogOptions, $scope.model.config); @@ -184,6 +183,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper $scope.contentPickerOverlay = dialogOptions; $scope.contentPickerOverlay.view = "treepicker"; $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.dataTypeId = $scope.model.dataTypeId; $scope.contentPickerOverlay.submit = function(model) { diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs index f2174ab120..9edbab04ae 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs @@ -21,9 +21,8 @@ namespace Umbraco.Web.Models.ContentEditing [Required] public int Id { get; set; } - [DataMember(Name = "dataTypeId", IsRequired = true)] - [Required] - public Guid DataTypeId { get; set; } + [DataMember(Name = "dataTypeId", IsRequired = false)] + public Guid? DataTypeId { get; set; } [DataMember(Name = "value")] public object Value { get; set; } diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs index 57a7db4889..dfdea268c4 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -75,11 +75,20 @@ namespace Umbraco.Web.WebApi.Filters var ignoreUserStartNodes = false; - if (actionContext.ActionArguments.ContainsKey("dataTypeId") && - Guid.TryParse(actionContext.ActionArguments.GetValueAsString("dataTypeId"), out var dataTypeId)) + if (actionContext.ActionArguments.ContainsKey("dataTypeId")) { - ignoreUserStartNodes = - ApplicationContext.Current.Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId); + if (actionContext.ActionArguments.TryGetValue("dataTypeId", out var dataTypeIdValue)) + { + var dataTypeIdString = dataTypeIdValue?.ToString(); + if (string.IsNullOrEmpty(dataTypeIdString) == false && + Guid.TryParse(dataTypeIdString, out var dataTypeId)) + { + ignoreUserStartNodes = + ApplicationContext.Current.Services.DataTypeService + .IsDataTypeIgnoringUserStartNodes(dataTypeId); + } + } + } int nodeId; diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index 40f0ad2a79..d054337fd9 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -89,9 +89,43 @@ namespace Umbraco.Web.WebApi.Filters { FilterBasedOnStartNode(items, user); } + else + { + FilterOutPossibleSensitiveData(items); + } } + /// + /// Removes all properties of the items except the umbracoFile. + /// + /// + private void FilterOutPossibleSensitiveData(IList items) + { + foreach (dynamic item in items) + { + + if (item.Properties != null) + { + if (item.Properties is IList properties) + // Iterate reverse, because we removed from the same list, such that the ordering of indexes not + // changes doing the iteration + for (var i = properties.Count - 1; i >= 0; i--) + { + dynamic property = properties[i]; + if (property.Alias != null && property.Alias is string) + { + var alias = property.Alias as string; + if (string.Equals(alias, Constants.Conventions.Media.File) == false) + { + properties.RemoveAt(i); + } + } + } + } + } + } + internal void FilterBasedOnStartNode(IList items, IUser user) { var toRemove = new List(); From 40d4a8fe6aa40a46a1b186b93f1e2dafcfcb84c8 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 21 Jun 2019 13:32:53 +0200 Subject: [PATCH 014/776] Modelsbuilder is asking for too old a version of CodeAnalysis, Immutable and Metadata dependencies --- build/NuSpecs/UmbracoCms.nuspec | 1 - src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 20 ++++++++++---------- src/Umbraco.Web.UI/packages.config | 10 +++++----- src/Umbraco.Web.UI/web.Template.config | 8 -------- 4 files changed, 15 insertions(+), 24 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 838ec5eff0..8b650edc13 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -22,7 +22,6 @@ - diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 9d92d55388..a0624a2dd7 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -155,11 +155,11 @@ ..\packages\Microsoft.AspNet.Identity.Owin.2.2.2\lib\net45\Microsoft.AspNet.Identity.Owin.dll - - ..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll + + ..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.dll - - ..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll + + ..\packages\Microsoft.CodeAnalysis.CSharp.1.0.0\lib\net45\Microsoft.CodeAnalysis.CSharp.dll ..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll @@ -204,8 +204,8 @@ System - - ..\packages\System.Collections.Immutable.1.5.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + + ..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll @@ -236,8 +236,8 @@ False False - - ..\packages\System.Reflection.Metadata.1.6.0\lib\portable-net45+win8\System.Reflection.Metadata.dll + + ..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll @@ -1002,8 +1002,8 @@ - - + + 11.0 diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config index abcaf6bf83..18cbfdb042 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -19,9 +19,9 @@ - - - + + + @@ -36,7 +36,7 @@ - - + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 4ff70db974..0d26b71a38 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -438,14 +438,6 @@ - - - - - - - - From c4c2082fa93aa663349551009e8b52a04f9e54e0 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 21 Jun 2019 13:57:17 +0200 Subject: [PATCH 015/776] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - Only allow "ignoreUserStartNodes" for custom data types and not the default build in. --- src/Umbraco.Core/Constants-DataTypes.cs | 295 ++++++++ src/Umbraco.Core/Models/DataTypeDefinition.cs | 477 ++++++------ .../Models/IDataTypeDefinition.cs | 53 +- .../Factories/DataTypeDefinitionFactory.cs | 196 ++--- .../Migrations/Initial/BaseDataCreation.cs | 684 +++++++++--------- src/Umbraco.Core/Services/DataTypeService.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 3 + .../Models/Mapping/PreValueDisplayResolver.cs | 173 ++--- .../ContentPicker2PropertyEditor.cs | 10 +- .../PropertyEditors/GridPropertyEditor.cs | 16 +- .../MediaPicker2PropertyEditor.cs | 4 +- .../MultiNodeTreePicker2PropertyEditor.cs | 12 +- .../MultiUrlPickerPropertyEditor.cs | 2 +- .../RelatedLinks2PropertyEditor.cs | 6 +- .../PropertyEditors/RichTextPreValueEditor.cs | 147 ++-- 15 files changed, 1217 insertions(+), 863 deletions(-) create mode 100644 src/Umbraco.Core/Constants-DataTypes.cs diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs new file mode 100644 index 0000000000..d5d0e99dab --- /dev/null +++ b/src/Umbraco.Core/Constants-DataTypes.cs @@ -0,0 +1,295 @@ +using System; + +namespace Umbraco.Core +{ + public static partial class Constants + { + /// + /// Defines the identifiers for Umbraco data types as constants for easy centralized access/management. + /// + public static class DataTypes + { + + public static class ReservedPreValueKeys + { + public const string IgnoreUserStartNodes = "ignoreUserStartNodes"; + } + + /// + /// Guid for Content Picker as string + /// + public const string ContentPicker = "FD1E0DA5-5606-4862-B679-5D0CF3A52A59"; + + /// + /// Guid for Content Picker + /// + public static readonly Guid ContentPickerGuid = new Guid(ContentPicker); + + + /// + /// Guid for Member Picker as string + /// + public const string MemberPicker = "1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"; + + /// + /// Guid for Member Picker + /// + public static readonly Guid MemberPickerGuid = new Guid(MemberPicker); + + + /// + /// Guid for Media Picker as string + /// + public const string MediaPicker = "135D60E0-64D9-49ED-AB08-893C9BA44AE5"; + + /// + /// Guid for Media Picker + /// + public static readonly Guid MediaPickerGuid = new Guid(MediaPicker); + + + /// + /// Guid for Multiple Media Picker as string + /// + public const string MultipleMediaPicker = "9DBBCBBB-2327-434A-B355-AF1B84E5010A"; + + /// + /// Guid for Multiple Media Picker + /// + public static readonly Guid MultipleMediaPickerGuid = new Guid(MultipleMediaPicker); + + + /// + /// Guid for Related Links as string + /// + public const string RelatedLinks = "B4E3535A-1753-47E2-8568-602CF8CFEE6F"; + + /// + /// Guid for Related Links + /// + public static readonly Guid RelatedLinksGuid = new Guid(RelatedLinks); + + + /// + /// Guid for Member as string + /// + public const string Member = "d59be02f-1df9-4228-aa1e-01917d806cda"; + + /// + /// Guid for Member + /// + public static readonly Guid MemberGuid = new Guid(Member); + + + /// + /// Guid for Image Cropper as string + /// + public const string ImageCropper = "1df9f033-e6d4-451f-b8d2-e0cbc50a836f"; + + /// + /// Guid for Image Cropper + /// + public static readonly Guid ImageCropperGuid = new Guid(ImageCropper); + + + /// + /// Guid for Tags as string + /// + public const string Tags = "b6b73142-b9c1-4bf8-a16d-e1c23320b549"; + + /// + /// Guid for Tags + /// + public static readonly Guid TagsGuid = new Guid(Tags); + + + /// + /// Guid for List View - Content as string + /// + public const string ListViewContent = "C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"; + + /// + /// Guid for List View - Content + /// + public static readonly Guid ListViewContentGuid = new Guid(ListViewContent); + + + /// + /// Guid for List View - Media as string + /// + public const string ListViewMedia = "3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"; + + /// + /// Guid for List View - Media + /// + public static readonly Guid ListViewMediaGuid = new Guid(ListViewMedia); + + + /// + /// Guid for List View - Members as string + /// + public const string ListViewMembers = "AA2C52A0-CE87-4E65-A47C-7DF09358585D"; + + /// + /// Guid for List View - Members + /// + public static readonly Guid ListViewMembersGuid = new Guid(ListViewMembers); + + + /// + /// Guid for Date Picker with time as string + /// + public const string DatePickerWithTime = "e4d66c0f-b935-4200-81f0-025f7256b89a"; + + /// + /// Guid for Date Picker with time + /// + public static readonly Guid DatePickerWithTimeGuid = new Guid(DatePickerWithTime); + + + /// + /// Guid for Approved Color as string + /// + public const string ApprovedColor = "0225af17-b302-49cb-9176-b9f35cab9c17"; + + /// + /// Guid for Approved Color + /// + public static readonly Guid ApprovedColorGuid = new Guid(ApprovedColor); + + + /// + /// Guid for Dropdown multiple as string + /// + public const string DropdownMultiple = "f38f0ac7-1d27-439c-9f3f-089cd8825a53"; + + /// + /// Guid for Dropdown multiple + /// + public static readonly Guid DropdownMultipleGuid = new Guid(DropdownMultiple); + + + /// + /// Guid for Radiobox as string + /// + public const string Radiobox = "bb5f57c9-ce2b-4bb9-b697-4caca783a805"; + + /// + /// Guid for Radiobox + /// + public static readonly Guid RadioboxGuid = new Guid(Radiobox); + + + /// + /// Guid for Date Picker as string + /// + public const string DatePicker = "5046194e-4237-453c-a547-15db3a07c4e1"; + + /// + /// Guid for Date Picker + /// + public static readonly Guid DatePickerGuid = new Guid(DatePicker); + + + /// + /// Guid for Dropdown as string + /// + public const string Dropdown = "0b6a45e7-44ba-430d-9da5-4e46060b9e03"; + + /// + /// Guid for Dropdown + /// + public static readonly Guid DropdownGuid = new Guid(Dropdown); + + + /// + /// Guid for Checkbox list as string + /// + public const string CheckboxList = "fbaf13a8-4036-41f2-93a3-974f678c312a"; + + /// + /// Guid for Checkbox list + /// + public static readonly Guid CheckboxListGuid = new Guid(CheckboxList); + + + /// + /// Guid for Checkbox as string + /// + public const string Checkbox = "92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"; + + /// + /// Guid for Checkbox + /// + public static readonly Guid CheckboxGuid = new Guid(Checkbox); + + + /// + /// Guid for Numeric as string + /// + public const string Numeric = "2e6d3631-066e-44b8-aec4-96f09099b2b5"; + + /// + /// Guid for Dropdown + /// + public static readonly Guid NumericGuid = new Guid(Numeric); + + + /// + /// Guid for Richtext editor as string + /// + public const string RichtextEditor = "ca90c950-0aff-4e72-b976-a30b1ac57dad"; + + /// + /// Guid for Richtext editor + /// + public static readonly Guid RichtextEditorGuid = new Guid(RichtextEditor); + + + /// + /// Guid for Textstring as string + /// + public const string Textstring = "0cc0eba1-9960-42c9-bf9b-60e150b429ae"; + + /// + /// Guid for Textstring + /// + public static readonly Guid TextstringGuid = new Guid(Textstring); + + + /// + /// Guid for Textarea as string + /// + public const string Textarea = "c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"; + + /// + /// Guid for Dropdown + /// + public static readonly Guid TextareaGuid = new Guid(Textarea); + + + /// + /// Guid for Upload as string + /// + public const string Upload = "84c6b441-31df-4ffe-b67e-67d5bc3ae65a"; + + /// + /// Guid for Upload + /// + public static readonly Guid UploadGuid = new Guid(Upload); + + + /// + /// Guid for Label as string + /// + public const string Label = "f0bc4bfb-b499-40d6-ba86-058885a5178c"; + + /// + /// Guid for Label + /// + public static readonly Guid LabelGuid = new Guid(Label); + + + } + } +} diff --git a/src/Umbraco.Core/Models/DataTypeDefinition.cs b/src/Umbraco.Core/Models/DataTypeDefinition.cs index 3ba5125a90..5e1fd649ae 100644 --- a/src/Umbraco.Core/Models/DataTypeDefinition.cs +++ b/src/Umbraco.Core/Models/DataTypeDefinition.cs @@ -1,219 +1,260 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Reflection; -using System.Runtime.Serialization; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Models -{ - /// - /// Definition of a DataType/PropertyEditor - /// - /// - /// The definition exists as a database reference between an actual DataType/PropertyEditor - /// (identified by its control id), its prevalues (configuration) and the named DataType in the backoffice UI. - /// - [Serializable] - [DataContract(IsReference = true)] - public class DataTypeDefinition : Entity, IDataTypeDefinition - { - private int _parentId; - private string _name; - private int _sortOrder; - private int _level; - private string _path; - private int _creatorId; - private bool _trashed; - private string _propertyEditorAlias; - private DataTypeDatabaseType _databaseType; - - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the alternative contructor that specifies an alias")] - [EditorBrowsable(EditorBrowsableState.Never)] - public DataTypeDefinition(int parentId, Guid controlId) - { - _parentId = parentId; - - _propertyEditorAlias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(controlId, false); - if (_propertyEditorAlias == null) - { - //convert to Label! - LogHelper.Warn("Could not find a GUID -> Alias mapping for the legacy property editor with id " + controlId + ". The DataType has been converted to a Label."); - _propertyEditorAlias = Constants.PropertyEditors.NoEditAlias; - } - - _additionalData = new Dictionary(); - } - - public DataTypeDefinition(int parentId, string propertyEditorAlias) - { - _parentId = parentId; - _propertyEditorAlias = propertyEditorAlias; - - _additionalData = new Dictionary(); - } - - public DataTypeDefinition(string propertyEditorAlias) - { - _parentId = -1; - _propertyEditorAlias = propertyEditorAlias; - - _additionalData = new Dictionary(); - } - - private static readonly Lazy Ps = new Lazy(); - - private class PropertySelectors - { - public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); - public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); - public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); - public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); - public readonly PropertyInfo UserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); - public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); - public readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); - public readonly PropertyInfo DatabaseTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.DatabaseType); - } - - /// - /// Gets or sets the Id of the Parent entity - /// - /// Might not be necessary if handled as a relation? - [DataMember] - public int ParentId - { - get { return _parentId; } - set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } - } - - /// - /// Gets or sets the name of the current entity - /// - [DataMember] - public string Name - { - get { return _name; } - set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } - } - - /// - /// Gets or sets the sort order of the content entity - /// - [DataMember] - public int SortOrder - { - get { return _sortOrder; } - set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } - } - - /// - /// Gets or sets the level of the content entity - /// - [DataMember] - public int Level - { - get { return _level; } - set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } - } - - /// - /// Gets or sets the path - /// - [DataMember] - public string Path //Setting this value should be handled by the class not the user - { - get { return _path; } - set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } - } - - /// - /// Id of the user who created this entity - /// - [DataMember] - public int CreatorId - { - get { return _creatorId; } - set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.UserIdSelector); } - } - - //NOTE: SD: Why do we have this ?? - - /// - /// Boolean indicating whether this entity is Trashed or not. - /// - [DataMember] - public bool Trashed - { - get { return _trashed; } - internal set - { - SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - _additionalData["Trashed"] = value; - } - } - - [DataMember] - public string PropertyEditorAlias - { - get { return _propertyEditorAlias; } - set - { - SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, Ps.Value.PropertyEditorAliasSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - _additionalData["DatabaseType"] = value; - } - } - - /// - /// Id of the DataType control - /// - [DataMember] - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the PropertyEditorAlias property instead. This method will return a generated GUID for any property editor alias not explicitly mapped to a legacy ID")] - public Guid ControlId - { - get - { - return LegacyPropertyEditorIdToAliasConverter.GetLegacyIdFromAlias( - _propertyEditorAlias, LegacyPropertyEditorIdToAliasConverter.NotFoundLegacyIdResponseBehavior.GenerateId).Value; - } - set - { - var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(value, true); - PropertyEditorAlias = alias; - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - _additionalData["ControlId"] = value; - } - } - - /// - /// Gets or Sets the DatabaseType for which the DataType's value is saved as - /// - [DataMember] - public DataTypeDatabaseType DatabaseType - { - get { return _databaseType; } - set - { - SetPropertyValueAndDetectChanges(value, ref _databaseType, Ps.Value.DatabaseTypeSelector); - //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data - _additionalData["DatabaseType"] = value; - } - } - - private readonly IDictionary _additionalData; - /// - /// Some entities may expose additional data that other's might not, this custom data will be available in this collection - /// - [EditorBrowsable(EditorBrowsableState.Never)] - IDictionary IUmbracoEntity.AdditionalData - { - get { return _additionalData; } - } - } +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Persistence; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Models +{ + /// + /// Definition of a DataType/PropertyEditor + /// + /// + /// The definition exists as a database reference between an actual DataType/PropertyEditor + /// (identified by its control id), its prevalues (configuration) and the named DataType in the backoffice UI. + /// + [Serializable] + [DataContract(IsReference = true)] + public class DataTypeDefinition : Entity, IDataTypeDefinition + { + private int _parentId; + private string _name; + private int _sortOrder; + private int _level; + private string _path; + private int _creatorId; + private bool _trashed; + private string _propertyEditorAlias; + private DataTypeDatabaseType _databaseType; + + private static readonly ISet IdsOfBuildInDataTypes = new HashSet() + { + Constants.DataTypes.ContentPickerGuid, + Constants.DataTypes.MemberPickerGuid, + Constants.DataTypes.MediaPickerGuid, + Constants.DataTypes.MultipleMediaPickerGuid, + Constants.DataTypes.RelatedLinksGuid, + Constants.DataTypes.MemberGuid, + Constants.DataTypes.ImageCropperGuid, + Constants.DataTypes.TagsGuid, + Constants.DataTypes.ListViewContentGuid, + Constants.DataTypes.ListViewMediaGuid, + Constants.DataTypes.ListViewMembersGuid, + Constants.DataTypes.DatePickerWithTimeGuid, + Constants.DataTypes.ApprovedColorGuid, + Constants.DataTypes.DropdownMultipleGuid, + Constants.DataTypes.RadioboxGuid, + Constants.DataTypes.DatePickerGuid, + Constants.DataTypes.DropdownGuid, + Constants.DataTypes.CheckboxListGuid, + Constants.DataTypes.CheckboxGuid, + Constants.DataTypes.NumericGuid, + Constants.DataTypes.RichtextEditorGuid, + Constants.DataTypes.TextstringGuid, + Constants.DataTypes.TextareaGuid, + Constants.DataTypes.UploadGuid, + Constants.DataTypes.LabelGuid, + }; + + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the alternative contructor that specifies an alias")] + [EditorBrowsable(EditorBrowsableState.Never)] + public DataTypeDefinition(int parentId, Guid controlId) + { + _parentId = parentId; + + _propertyEditorAlias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(controlId, false); + if (_propertyEditorAlias == null) + { + //convert to Label! + LogHelper.Warn("Could not find a GUID -> Alias mapping for the legacy property editor with id " + controlId + ". The DataType has been converted to a Label."); + _propertyEditorAlias = Constants.PropertyEditors.NoEditAlias; + } + + _additionalData = new Dictionary(); + } + + public DataTypeDefinition(int parentId, string propertyEditorAlias) + { + _parentId = parentId; + _propertyEditorAlias = propertyEditorAlias; + + _additionalData = new Dictionary(); + } + + public DataTypeDefinition(string propertyEditorAlias) + { + _parentId = -1; + _propertyEditorAlias = propertyEditorAlias; + + _additionalData = new Dictionary(); + } + + public DataTypeDefinition(string propertyEditorAlias, Guid uniqueId) + { + _parentId = -1; + _propertyEditorAlias = propertyEditorAlias; + + _additionalData = new Dictionary(); + IsBuildInDataType = IdsOfBuildInDataTypes.Contains(uniqueId); + } + + private static readonly Lazy Ps = new Lazy(); + + private class PropertySelectors + { + public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); + public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); + public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); + public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); + public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo(x => x.Path); + public readonly PropertyInfo UserIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); + public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo(x => x.Trashed); + public readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.PropertyEditorAlias); + public readonly PropertyInfo DatabaseTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.DatabaseType); + } + + /// + /// Gets or sets the Id of the Parent entity + /// + /// Might not be necessary if handled as a relation? + [DataMember] + public int ParentId + { + get { return _parentId; } + set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); } + } + + /// + /// Gets or sets the name of the current entity + /// + [DataMember] + public string Name + { + get { return _name; } + set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); } + } + + /// + /// Gets or sets the sort order of the content entity + /// + [DataMember] + public int SortOrder + { + get { return _sortOrder; } + set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); } + } + + /// + /// Gets or sets the level of the content entity + /// + [DataMember] + public int Level + { + get { return _level; } + set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); } + } + + /// + /// Gets or sets the path + /// + [DataMember] + public string Path //Setting this value should be handled by the class not the user + { + get { return _path; } + set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); } + } + + /// + /// Id of the user who created this entity + /// + [DataMember] + public int CreatorId + { + get { return _creatorId; } + set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.UserIdSelector); } + } + + //NOTE: SD: Why do we have this ?? + + /// + /// Boolean indicating whether this entity is Trashed or not. + /// + [DataMember] + public bool Trashed + { + get { return _trashed; } + internal set + { + SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector); + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + _additionalData["Trashed"] = value; + } + } + + [DataMember] + public string PropertyEditorAlias + { + get { return _propertyEditorAlias; } + set + { + SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, Ps.Value.PropertyEditorAliasSelector); + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + _additionalData["DatabaseType"] = value; + } + } + + /// + /// Id of the DataType control + /// + [DataMember] + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the PropertyEditorAlias property instead. This method will return a generated GUID for any property editor alias not explicitly mapped to a legacy ID")] + public Guid ControlId + { + get + { + return LegacyPropertyEditorIdToAliasConverter.GetLegacyIdFromAlias( + _propertyEditorAlias, LegacyPropertyEditorIdToAliasConverter.NotFoundLegacyIdResponseBehavior.GenerateId).Value; + } + set + { + var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(value, true); + PropertyEditorAlias = alias; + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + _additionalData["ControlId"] = value; + } + } + + /// + /// Gets or Sets the DatabaseType for which the DataType's value is saved as + /// + [DataMember] + public DataTypeDatabaseType DatabaseType + { + get { return _databaseType; } + set + { + SetPropertyValueAndDetectChanges(value, ref _databaseType, Ps.Value.DatabaseTypeSelector); + //This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data + _additionalData["DatabaseType"] = value; + } + } + + private readonly IDictionary _additionalData; + /// + /// Some entities may expose additional data that other's might not, this custom data will be available in this collection + /// + [EditorBrowsable(EditorBrowsableState.Never)] + IDictionary IUmbracoEntity.AdditionalData + { + get { return _additionalData; } + } + + public bool IsBuildInDataType { get;} + } + } diff --git a/src/Umbraco.Core/Models/IDataTypeDefinition.cs b/src/Umbraco.Core/Models/IDataTypeDefinition.cs index a33736e604..f57a1bdca7 100644 --- a/src/Umbraco.Core/Models/IDataTypeDefinition.cs +++ b/src/Umbraco.Core/Models/IDataTypeDefinition.cs @@ -1,24 +1,29 @@ -using System; -using Umbraco.Core.Models.EntityBase; - -namespace Umbraco.Core.Models -{ - public interface IDataTypeDefinition : IUmbracoEntity - { - /// - /// The Property editor alias assigned to the data type - /// - string PropertyEditorAlias { get; set; } - - /// - /// Id of the DataType control - /// - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the PropertyEditorAlias property instead")] - Guid ControlId { get; set; } - - /// - /// Gets or Sets the DatabaseType for which the DataType's value is saved as - /// - DataTypeDatabaseType DatabaseType { get; set; } - } -} \ No newline at end of file +using System; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Models +{ + public interface IDataTypeDefinition : IUmbracoEntity + { + /// + /// The Property editor alias assigned to the data type + /// + string PropertyEditorAlias { get; set; } + + /// + /// Id of the DataType control + /// + [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the PropertyEditorAlias property instead")] + Guid ControlId { get; set; } + + /// + /// Gets or Sets the DatabaseType for which the DataType's value is saved as + /// + DataTypeDatabaseType DatabaseType { get; set; } + + /// + /// Gets information about whether this data type is build in or not. + /// + bool IsBuildInDataType { get;} + } +} diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs index cf487fa1a2..14c162d8ea 100644 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs @@ -1,98 +1,98 @@ -using System; -using System.Globalization; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class DataTypeDefinitionFactory - { - private readonly Guid _nodeObjectTypeId; - private int _primaryKey; - - public DataTypeDefinitionFactory(Guid nodeObjectTypeId) - { - _nodeObjectTypeId = nodeObjectTypeId; - } - - #region Implementation of IEntityFactory - - public IDataTypeDefinition BuildEntity(DataTypeDto dto) - { - var dataTypeDefinition = new DataTypeDefinition(dto.PropertyEditorAlias); - - - try - { - dataTypeDefinition.DisableChangeTracking(); - - dataTypeDefinition.CreateDate = dto.NodeDto.CreateDate; - dataTypeDefinition.DatabaseType = dto.DbType.EnumParse(true); - dataTypeDefinition.Id = dto.DataTypeId; - dataTypeDefinition.Key = dto.NodeDto.UniqueId; - dataTypeDefinition.Level = dto.NodeDto.Level; - dataTypeDefinition.UpdateDate = dto.NodeDto.CreateDate; - dataTypeDefinition.Name = dto.NodeDto.Text; - dataTypeDefinition.ParentId = dto.NodeDto.ParentId; - dataTypeDefinition.Path = dto.NodeDto.Path; - dataTypeDefinition.SortOrder = dto.NodeDto.SortOrder; - dataTypeDefinition.Trashed = dto.NodeDto.Trashed; - dataTypeDefinition.CreatorId = dto.NodeDto.UserId.Value; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - dataTypeDefinition.ResetDirtyProperties(false); - return dataTypeDefinition; - } - finally - { - dataTypeDefinition.EnableChangeTracking(); - } - } - - public DataTypeDto BuildDto(IDataTypeDefinition entity) - { - var dataTypeDto = new DataTypeDto - { - PropertyEditorAlias = entity.PropertyEditorAlias, - DataTypeId = entity.Id, - DbType = entity.DatabaseType.ToString(), - NodeDto = BuildNodeDto(entity) - }; - - if (_primaryKey > 0) - { - dataTypeDto.PrimaryKey = _primaryKey; - } - - return dataTypeDto; - } - - #endregion - - public void SetPrimaryKey(int primaryKey) - { - _primaryKey = primaryKey; - } - - private NodeDto BuildNodeDto(IDataTypeDefinition entity) - { - var nodeDto = new NodeDto - { - CreateDate = entity.CreateDate, - NodeId = entity.Id, - Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), - NodeObjectType = _nodeObjectTypeId, - ParentId = entity.ParentId, - Path = entity.Path, - SortOrder = entity.SortOrder, - Text = entity.Name, - Trashed = entity.Trashed, - UniqueId = entity.Key, - UserId = entity.CreatorId - }; - - return nodeDto; - } - } -} \ No newline at end of file +using System; +using System.Globalization; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Factories +{ + internal class DataTypeDefinitionFactory + { + private readonly Guid _nodeObjectTypeId; + private int _primaryKey; + + public DataTypeDefinitionFactory(Guid nodeObjectTypeId) + { + _nodeObjectTypeId = nodeObjectTypeId; + } + + #region Implementation of IEntityFactory + + public IDataTypeDefinition BuildEntity(DataTypeDto dto) + { + var dataTypeDefinition = new DataTypeDefinition(dto.PropertyEditorAlias,dto.NodeDto.UniqueId); + + + try + { + dataTypeDefinition.DisableChangeTracking(); + + dataTypeDefinition.CreateDate = dto.NodeDto.CreateDate; + dataTypeDefinition.DatabaseType = dto.DbType.EnumParse(true); + dataTypeDefinition.Id = dto.DataTypeId; + dataTypeDefinition.Key = dto.NodeDto.UniqueId; + dataTypeDefinition.Level = dto.NodeDto.Level; + dataTypeDefinition.UpdateDate = dto.NodeDto.CreateDate; + dataTypeDefinition.Name = dto.NodeDto.Text; + dataTypeDefinition.ParentId = dto.NodeDto.ParentId; + dataTypeDefinition.Path = dto.NodeDto.Path; + dataTypeDefinition.SortOrder = dto.NodeDto.SortOrder; + dataTypeDefinition.Trashed = dto.NodeDto.Trashed; + dataTypeDefinition.CreatorId = dto.NodeDto.UserId.Value; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + dataTypeDefinition.ResetDirtyProperties(false); + return dataTypeDefinition; + } + finally + { + dataTypeDefinition.EnableChangeTracking(); + } + } + + public DataTypeDto BuildDto(IDataTypeDefinition entity) + { + var dataTypeDto = new DataTypeDto + { + PropertyEditorAlias = entity.PropertyEditorAlias, + DataTypeId = entity.Id, + DbType = entity.DatabaseType.ToString(), + NodeDto = BuildNodeDto(entity) + }; + + if (_primaryKey > 0) + { + dataTypeDto.PrimaryKey = _primaryKey; + } + + return dataTypeDto; + } + + #endregion + + public void SetPrimaryKey(int primaryKey) + { + _primaryKey = primaryKey; + } + + private NodeDto BuildNodeDto(IDataTypeDefinition entity) + { + var nodeDto = new NodeDto + { + CreateDate = entity.CreateDate, + NodeId = entity.Id, + Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), + NodeObjectType = _nodeObjectTypeId, + ParentId = entity.ParentId, + Path = entity.Path, + SortOrder = entity.SortOrder, + Text = entity.Name, + Trashed = entity.Trashed, + UniqueId = entity.Key, + UserId = entity.CreatorId + }; + + return nodeDto; + } + } +} diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index b8e605e63e..e1716947e1 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -1,342 +1,342 @@ -using System; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Migrations.Initial -{ - /// - /// Represents the initial data creation by running Insert for the base data. - /// - internal class BaseDataCreation - { - private readonly Database _database; - private readonly ILogger _logger; - - public BaseDataCreation(Database database, ILogger logger) - { - _database = database; - _logger = logger; - } - - /// - /// Initialize the base data creation by inserting the data foundation for umbraco - /// specific to a table - /// - /// Name of the table to create base data for - public void InitializeBaseData(string tableName) - { - _logger.Info(string.Format("Creating data in table {0}", tableName)); - - if(tableName.Equals("umbracoNode")) - { - CreateUmbracoNodeData(); - } - - if (tableName.Equals("umbracoLock")) - { - CreateUmbracoLockData(); - } - - if (tableName.Equals("cmsContentType")) - { - CreateCmsContentTypeData(); - } - - if (tableName.Equals("umbracoUser")) - { - CreateUmbracoUserData(); - } - - if (tableName.Equals("umbracoUserGroup")) - { - CreateUmbracoUserGroupData(); - } - - if (tableName.Equals("umbracoUser2UserGroup")) - { - CreateUmbracoUser2UserGroupData(); - } - - if (tableName.Equals("umbracoUserGroup2App")) - { - CreateUmbracoUserGroup2AppData(); - } - - if (tableName.Equals("cmsPropertyTypeGroup")) - { - CreateCmsPropertyTypeGroupData(); - } - - if (tableName.Equals("cmsPropertyType")) - { - CreateCmsPropertyTypeData(); - } - - if (tableName.Equals("umbracoLanguage")) - { - CreateUmbracoLanguageData(); - } - - if (tableName.Equals("cmsContentTypeAllowedContentType")) - { - CreateCmsContentTypeAllowedContentTypeData(); - } - - if(tableName.Equals("cmsDataType")) - { - CreateCmsDataTypeData(); - } - - if (tableName.Equals("cmsDataTypePreValues")) - { - CreateCmsDataTypePreValuesData(); - } - - if (tableName.Equals("umbracoRelationType")) - { - CreateUmbracoRelationTypeData(); - } - - if (tableName.Equals("cmsTaskType")) - { - CreateCmsTaskTypeData(); - } - - if (tableName.Equals("umbracoMigration")) - { - CreateUmbracoMigrationData(); - } - - _logger.Info(string.Format("Done creating data in table {0}", tableName)); - } - - private void CreateUmbracoNodeData() - { - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -1, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1", SortOrder = 0, UniqueId = new Guid("916724a5-173d-4619-b97e-b9de133dd6f5"), Text = "SYSTEM DATA: umbraco master root", NodeObjectType = new Guid(Constants.ObjectTypes.SystemRoot), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -20, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-20", SortOrder = 0, UniqueId = new Guid("0F582A79-1E41-4CF0-BFA0-76340651891A"), Text = "Recycle Bin", NodeObjectType = new Guid(Constants.ObjectTypes.ContentRecycleBin), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -21, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-21", SortOrder = 0, UniqueId = new Guid("BF7C7CBC-952F-4518-97A2-69E9C7B33842"), Text = "Recycle Bin", NodeObjectType = new Guid(Constants.ObjectTypes.MediaRecycleBin), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -92, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-92", SortOrder = 35, UniqueId = new Guid("f0bc4bfb-b499-40d6-ba86-058885a5178c"), Text = "Label", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -90, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-90", SortOrder = 34, UniqueId = new Guid("84c6b441-31df-4ffe-b67e-67d5bc3ae65a"), Text = "Upload", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -89, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-89", SortOrder = 33, UniqueId = new Guid("c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"), Text = "Textarea", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -88, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-88", SortOrder = 32, UniqueId = new Guid("0cc0eba1-9960-42c9-bf9b-60e150b429ae"), Text = "Textstring", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -87, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-87", SortOrder = 4, UniqueId = new Guid("ca90c950-0aff-4e72-b976-a30b1ac57dad"), Text = "Richtext editor", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = new Guid("2e6d3631-066e-44b8-aec4-96f09099b2b5"), Text = "Numeric", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -49, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-49", SortOrder = 2, UniqueId = new Guid("92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"), Text = "Checkbox", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -43, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-43", SortOrder = 2, UniqueId = new Guid("fbaf13a8-4036-41f2-93a3-974f678c312a"), Text = "Checkbox list", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -42, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-42", SortOrder = 2, UniqueId = new Guid("0b6a45e7-44ba-430d-9da5-4e46060b9e03"), Text = "Dropdown", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -41, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-41", SortOrder = 2, UniqueId = new Guid("5046194e-4237-453c-a547-15db3a07c4e1"), Text = "Date Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -40, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-40", SortOrder = 2, UniqueId = new Guid("bb5f57c9-ce2b-4bb9-b697-4caca783a805"), Text = "Radiobox", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -39, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-39", SortOrder = 2, UniqueId = new Guid("f38f0ac7-1d27-439c-9f3f-089cd8825a53"), Text = "Dropdown multiple", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = new Guid("0225af17-b302-49cb-9176-b9f35cab9c17"), Text = "Approved Color", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -36, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-36", SortOrder = 2, UniqueId = new Guid("e4d66c0f-b935-4200-81f0-025f7256b89a"), Text = "Date Picker with time", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultContentListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-95", SortOrder = 2, UniqueId = new Guid("C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMediaListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-96", SortOrder = 2, UniqueId = new Guid("3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMembersListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-97", SortOrder = 2, UniqueId = new Guid("AA2C52A0-CE87-4E65-A47C-7DF09358585D"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"), Text = Constants.Conventions.MediaTypes.Folder, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"), Text = Constants.Conventions.MediaTypes.Image, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1033, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1033", SortOrder = 2, UniqueId = new Guid("4c52d8ab-54e6-40cd-999c-7a5f24903e4d"), Text = Constants.Conventions.MediaTypes.File, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1041, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1041", SortOrder = 2, UniqueId = new Guid("b6b73142-b9c1-4bf8-a16d-e1c23320b549"), Text = "Tags", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1043, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1043", SortOrder = 2, UniqueId = new Guid("1df9f033-e6d4-451f-b8d2-e0cbc50a836f"), Text = "Image Cropper", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1044, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1044", SortOrder = 0, UniqueId = new Guid("d59be02f-1df9-4228-aa1e-01917d806cda"), Text = Constants.Conventions.MemberTypes.DefaultAlias, NodeObjectType = new Guid(Constants.ObjectTypes.MemberType), CreateDate = DateTime.Now }); - - //New UDI pickers with newer Ids - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1046, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1046", SortOrder = 2, UniqueId = new Guid("FD1E0DA5-5606-4862-B679-5D0CF3A52A59"), Text = "Content Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1047, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1047", SortOrder = 2, UniqueId = new Guid("1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"), Text = "Member Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = new Guid("135D60E0-64D9-49ED-AB08-893C9BA44AE5"), Text = "Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1049, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1049", SortOrder = 2, UniqueId = new Guid("9DBBCBBB-2327-434A-B355-AF1B84E5010A"), Text = "Multiple Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1050, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1050", SortOrder = 2, UniqueId = new Guid("B4E3535A-1753-47E2-8568-602CF8CFEE6F"), Text = "Related Links", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - } - - private void CreateUmbracoLockData() - { - // all lock objects - _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.Servers, Name = "Servers" }); - } - - private void CreateCmsContentTypeData() - { - _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = "icon-folder", Thumbnail = "icon-folder", IsContainer = false, AllowAtRoot = true }); - _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = "icon-picture", Thumbnail = "icon-picture", AllowAtRoot = true }); - _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = "icon-document", Thumbnail = "icon-document", AllowAtRoot = true }); - _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Constants.Conventions.MemberTypes.DefaultAlias, Icon = "icon-user", Thumbnail = "icon-user" }); - } - - private void CreateUmbracoUserData() - { - _database.Insert("umbracoUser", "id", false, new UserDto { Id = 0, Disabled = false, NoConsole = false, UserName = "Administrator", Login = "admin", Password = "default", Email = "", UserLanguage = "en-US", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); - } - - private void CreateUmbracoUserGroupData() - { - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 1, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.AdminGroupAlias, Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7ï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-medal" }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 4, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.TranslatorGroupAlias, Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" }); - _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 5, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" }); - } - - private void CreateUmbracoUser2UserGroupData() - { - _database.Insert(new User2UserGroupDto { UserGroupId = 1, UserId = 0 }); //add admin to admins - _database.Insert(new User2UserGroupDto { UserGroupId = 5, UserId = 0 }); //add admin to sensitive data - } - - private void CreateUmbracoUserGroup2AppData() - { - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Content }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Developer }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Media }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Members }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Settings }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Users }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Forms }); - - _database.Insert(new UserGroup2AppDto { UserGroupId = 2, AppAlias = Constants.Applications.Content }); - - _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Content }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Media }); - _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Forms }); - - _database.Insert(new UserGroup2AppDto { UserGroupId = 4, AppAlias = Constants.Applications.Translation }); - } - - private void CreateCmsPropertyTypeGroupData() - { - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Image) }); - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 4, ContentTypeNodeId = 1033, Text = "File", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.File) }); - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 5, ContentTypeNodeId = 1031, Text = "Contents", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Contents) }); - //membership property group - _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 11, ContentTypeNodeId = 1044, Text = "Membership", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Membership) }); - } - - private void CreateCmsPropertyTypeData() - { - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = 1043, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 7, UniqueId = 7.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 8, UniqueId = 8.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 9, UniqueId = 9.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 10, UniqueId = 10.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 27, UniqueId = 27.ToGuid(), DataTypeId = Constants.System.DefaultMediaListViewDataTypeId, ContentTypeId = 1031, PropertyTypeGroupId = 5, Alias = "contents", Name = "Contents:", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - //membership property types - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null }); - _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null }); - - } - - private void CreateUmbracoLanguageData() - { - _database.Insert("umbracoLanguage", "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "en-US" }); - } - - private void CreateCmsContentTypeAllowedContentTypeData() - { - _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1031 }); - _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1032 }); - _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1033 }); - } - - private void CreateCmsDataTypeData() - { - //TODO Check which of the DataTypeIds below doesn't exist in umbracoNode, which results in a foreign key constraint errors. - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 1, DataTypeId = -49, PropertyEditorAlias = Constants.PropertyEditors.TrueFalseAlias, DbType = "Integer" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 2, DataTypeId = -51, PropertyEditorAlias = Constants.PropertyEditors.IntegerAlias, DbType = "Integer" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 3, DataTypeId = -87, PropertyEditorAlias = Constants.PropertyEditors.TinyMCEAlias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 4, DataTypeId = -88, PropertyEditorAlias = Constants.PropertyEditors.TextboxAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 5, DataTypeId = -89, PropertyEditorAlias = Constants.PropertyEditors.TextboxMultipleAlias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 6, DataTypeId = -90, PropertyEditorAlias = Constants.PropertyEditors.UploadFieldAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 7, DataTypeId = -92, PropertyEditorAlias = Constants.PropertyEditors.NoEditAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 8, DataTypeId = -36, PropertyEditorAlias = Constants.PropertyEditors.DateTimeAlias, DbType = "Date" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 9, DataTypeId = -37, PropertyEditorAlias = Constants.PropertyEditors.ColorPickerAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 11, DataTypeId = -39, PropertyEditorAlias = Constants.PropertyEditors.DropDownListFlexibleAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 12, DataTypeId = -40, PropertyEditorAlias = Constants.PropertyEditors.RadioButtonListAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 13, DataTypeId = -41, PropertyEditorAlias = Constants.PropertyEditors.DateAlias, DbType = "Date" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 14, DataTypeId = -42, PropertyEditorAlias = Constants.PropertyEditors.DropDownListFlexibleAlias, DbType = "Integer" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 15, DataTypeId = -43, PropertyEditorAlias = Constants.PropertyEditors.CheckBoxListAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 22, DataTypeId = 1041, PropertyEditorAlias = Constants.PropertyEditors.TagsAlias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 24, DataTypeId = 1043, PropertyEditorAlias = Constants.PropertyEditors.ImageCropperAlias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -26, DataTypeId = Constants.System.DefaultContentListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -27, DataTypeId = Constants.System.DefaultMediaListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -28, DataTypeId = Constants.System.DefaultMembersListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); - - //New UDI pickers with newer Ids - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 26, DataTypeId = 1046, PropertyEditorAlias = Constants.PropertyEditors.ContentPicker2Alias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 27, DataTypeId = 1047, PropertyEditorAlias = Constants.PropertyEditors.MemberPicker2Alias, DbType = "Nvarchar" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 28, DataTypeId = 1048, PropertyEditorAlias = Constants.PropertyEditors.MediaPicker2Alias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 29, DataTypeId = 1049, PropertyEditorAlias = Constants.PropertyEditors.MediaPicker2Alias, DbType = "Ntext" }); - _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 30, DataTypeId = 1050, PropertyEditorAlias = Constants.PropertyEditors.RelatedLinks2Alias, DbType = "Ntext" }); - } - - private void CreateCmsDataTypePreValuesData() - { - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 3, Alias = "", SortOrder = 0, DataTypeNodeId = -87, Value = ",code,undo,redo,cut,copy,mcepasteword,stylepicker,bold,italic,bullist,numlist,outdent,indent,mcelink,unlink,mceinsertanchor,mceimage,umbracomacro,mceinserttable,umbracoembed,mcecharmap,|1|1,2,3,|0|500,400|1049,|true|" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 4, Alias = "group", SortOrder = 0, DataTypeNodeId = 1041, Value = "default" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 5, Alias = "storageType", SortOrder = 0, DataTypeNodeId = 1041, Value = "Json" }); - - //defaults for the member list - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -1, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "10" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -2, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "username" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -3, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "asc" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -4, Alias = "includeProperties", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "[{\"alias\":\"username\",\"isSystem\":1},{\"alias\":\"email\",\"isSystem\":1},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1}]" }); - - //layouts for the list view - var cardLayout = "{\"name\": \"Grid\",\"path\": \"views/propertyeditors/listview/layouts/grid/grid.html\", \"icon\": \"icon-thumbnails-small\", \"isSystem\": 1, \"selected\": true}"; - var listLayout = "{\"name\": \"List\",\"path\": \"views/propertyeditors/listview/layouts/list/list.html\",\"icon\": \"icon-list\", \"isSystem\": 1,\"selected\": true}"; - - //defaults for the media list - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -5, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "100" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -6, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "updateDate" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -7, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "desc" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -8, Alias = "layouts", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[" + cardLayout + "," + listLayout + "]" }); - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -9, Alias = "includeProperties", SortOrder = 5, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]" }); - - //default's for MultipleMediaPickerAlias picker - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 6, Alias = "multiPicker", SortOrder = 0, DataTypeNodeId = 1049, Value = "1" }); - - // Defaults for single item dropdown - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 7, Alias = "multiple", SortOrder = 0, DataTypeNodeId = -42, Value = "0" }); - - // Defaults for multiple item dropdown - _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 8, Alias = "multiple", SortOrder = 0, DataTypeNodeId = -39, Value = "1" }); - } - - private void CreateUmbracoRelationTypeData() - { - var relationType = new RelationTypeDto { Id = 1, Alias = Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = true, Name = Constants.Conventions.RelationTypes.RelateDocumentOnCopyName }; - relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); - _database.Insert("umbracoRelationType", "id", false, relationType); - relationType = new RelationTypeDto { Id = 2, Alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName }; - relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); - _database.Insert("umbracoRelationType", "id", false, relationType); - relationType = new RelationTypeDto { Id = 3, Alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Media), ParentObjectType = new Guid(Constants.ObjectTypes.Media), Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName }; - relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); - _database.Insert("umbracoRelationType", "id", false, relationType); - } - - private void CreateCmsTaskTypeData() - { - _database.Insert("cmsTaskType", "id", false, new TaskTypeDto { Id = 1, Alias = "toTranslate" }); - } - - private void CreateUmbracoMigrationData() - { - var dto = new MigrationDto - { - Id = 1, - Name = Constants.System.UmbracoMigrationName, - Version = UmbracoVersion.GetSemanticVersion().ToString(), - CreateDate = DateTime.Now - }; - - _database.Insert("umbracoMigration", "pk", false, dto); - } - } -} +using System; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Migrations.Initial +{ + /// + /// Represents the initial data creation by running Insert for the base data. + /// + internal class BaseDataCreation + { + private readonly Database _database; + private readonly ILogger _logger; + + public BaseDataCreation(Database database, ILogger logger) + { + _database = database; + _logger = logger; + } + + /// + /// Initialize the base data creation by inserting the data foundation for umbraco + /// specific to a table + /// + /// Name of the table to create base data for + public void InitializeBaseData(string tableName) + { + _logger.Info(string.Format("Creating data in table {0}", tableName)); + + if(tableName.Equals("umbracoNode")) + { + CreateUmbracoNodeData(); + } + + if (tableName.Equals("umbracoLock")) + { + CreateUmbracoLockData(); + } + + if (tableName.Equals("cmsContentType")) + { + CreateCmsContentTypeData(); + } + + if (tableName.Equals("umbracoUser")) + { + CreateUmbracoUserData(); + } + + if (tableName.Equals("umbracoUserGroup")) + { + CreateUmbracoUserGroupData(); + } + + if (tableName.Equals("umbracoUser2UserGroup")) + { + CreateUmbracoUser2UserGroupData(); + } + + if (tableName.Equals("umbracoUserGroup2App")) + { + CreateUmbracoUserGroup2AppData(); + } + + if (tableName.Equals("cmsPropertyTypeGroup")) + { + CreateCmsPropertyTypeGroupData(); + } + + if (tableName.Equals("cmsPropertyType")) + { + CreateCmsPropertyTypeData(); + } + + if (tableName.Equals("umbracoLanguage")) + { + CreateUmbracoLanguageData(); + } + + if (tableName.Equals("cmsContentTypeAllowedContentType")) + { + CreateCmsContentTypeAllowedContentTypeData(); + } + + if(tableName.Equals("cmsDataType")) + { + CreateCmsDataTypeData(); + } + + if (tableName.Equals("cmsDataTypePreValues")) + { + CreateCmsDataTypePreValuesData(); + } + + if (tableName.Equals("umbracoRelationType")) + { + CreateUmbracoRelationTypeData(); + } + + if (tableName.Equals("cmsTaskType")) + { + CreateCmsTaskTypeData(); + } + + if (tableName.Equals("umbracoMigration")) + { + CreateUmbracoMigrationData(); + } + + _logger.Info(string.Format("Done creating data in table {0}", tableName)); + } + + private void CreateUmbracoNodeData() + { + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -1, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1", SortOrder = 0, UniqueId = new Guid("916724a5-173d-4619-b97e-b9de133dd6f5"), Text = "SYSTEM DATA: umbraco master root", NodeObjectType = new Guid(Constants.ObjectTypes.SystemRoot), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -20, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-20", SortOrder = 0, UniqueId = new Guid("0F582A79-1E41-4CF0-BFA0-76340651891A"), Text = "Recycle Bin", NodeObjectType = new Guid(Constants.ObjectTypes.ContentRecycleBin), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -21, Trashed = false, ParentId = -1, UserId = 0, Level = 0, Path = "-1,-21", SortOrder = 0, UniqueId = new Guid("BF7C7CBC-952F-4518-97A2-69E9C7B33842"), Text = "Recycle Bin", NodeObjectType = new Guid(Constants.ObjectTypes.MediaRecycleBin), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -92, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-92", SortOrder = 35, UniqueId = Constants.DataTypes.LabelGuid, Text = "Label", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -90, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-90", SortOrder = 34, UniqueId = Constants.DataTypes.UploadGuid, Text = "Upload", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -89, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-89", SortOrder = 33, UniqueId = Constants.DataTypes.TextareaGuid, Text = "Textarea", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -88, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-88", SortOrder = 32, UniqueId = Constants.DataTypes.TextstringGuid, Text = "Textstring", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -87, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-87", SortOrder = 4, UniqueId = Constants.DataTypes.RichtextEditorGuid, Text = "Richtext editor", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = Constants.DataTypes.NumericGuid, Text = "Numeric", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -49, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-49", SortOrder = 2, UniqueId = Constants.DataTypes.CheckboxGuid, Text = "Checkbox", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -43, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-43", SortOrder = 2, UniqueId = Constants.DataTypes.CheckboxListGuid, Text = "Checkbox list", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -42, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-42", SortOrder = 2, UniqueId = Constants.DataTypes.DropdownGuid, Text = "Dropdown", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -41, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-41", SortOrder = 2, UniqueId = Constants.DataTypes.DatePickerGuid, Text = "Date Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -40, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-40", SortOrder = 2, UniqueId = Constants.DataTypes.RadioboxGuid, Text = "Radiobox", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -39, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-39", SortOrder = 2, UniqueId = Constants.DataTypes.DropdownMultipleGuid, Text = "Dropdown multiple", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = Constants.DataTypes.ApprovedColorGuid, Text = "Approved Color", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -36, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-36", SortOrder = 2, UniqueId = Constants.DataTypes.DatePickerWithTimeGuid, Text = "Date Picker with time", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultContentListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-95", SortOrder = 2, UniqueId = Constants.DataTypes.ListViewContentGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMediaListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-96", SortOrder = 2, UniqueId = Constants.DataTypes.ListViewMembersGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMembersListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-97", SortOrder = 2, UniqueId = Constants.DataTypes.ListViewMembersGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"), Text = Constants.Conventions.MediaTypes.Folder, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"), Text = Constants.Conventions.MediaTypes.Image, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1033, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1033", SortOrder = 2, UniqueId = new Guid("4c52d8ab-54e6-40cd-999c-7a5f24903e4d"), Text = Constants.Conventions.MediaTypes.File, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1041, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1041", SortOrder = 2, UniqueId = Constants.DataTypes.TagsGuid, Text = "Tags", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1043, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1043", SortOrder = 2, UniqueId = Constants.DataTypes.ImageCropperGuid, Text = "Image Cropper", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1044, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1044", SortOrder = 0, UniqueId = Constants.DataTypes.MemberGuid, Text = Constants.Conventions.MemberTypes.DefaultAlias, NodeObjectType = new Guid(Constants.ObjectTypes.MemberType), CreateDate = DateTime.Now }); + + //New UDI pickers with newer Ids + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1046, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1046", SortOrder = 2, UniqueId = Constants.DataTypes.ContentPickerGuid, Text = "Content Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1047, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1047", SortOrder = 2, UniqueId = Constants.DataTypes.MemberPickerGuid, Text = "Member Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1048, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1048", SortOrder = 2, UniqueId = Constants.DataTypes.MediaPickerGuid, Text = "Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1049, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1049", SortOrder = 2, UniqueId = Constants.DataTypes.MultipleMediaPickerGuid, Text = "Multiple Media Picker", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1050, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1050", SortOrder = 2, UniqueId = Constants.DataTypes.RelatedLinksGuid, Text = "Related Links", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + } + + private void CreateUmbracoLockData() + { + // all lock objects + _database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.Servers, Name = "Servers" }); + } + + private void CreateCmsContentTypeData() + { + _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = "icon-folder", Thumbnail = "icon-folder", IsContainer = false, AllowAtRoot = true }); + _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = "icon-picture", Thumbnail = "icon-picture", AllowAtRoot = true }); + _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = "icon-document", Thumbnail = "icon-document", AllowAtRoot = true }); + _database.Insert("cmsContentType", "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Constants.Conventions.MemberTypes.DefaultAlias, Icon = "icon-user", Thumbnail = "icon-user" }); + } + + private void CreateUmbracoUserData() + { + _database.Insert("umbracoUser", "id", false, new UserDto { Id = 0, Disabled = false, NoConsole = false, UserName = "Administrator", Login = "admin", Password = "default", Email = "", UserLanguage = "en-US", CreateDate = DateTime.Now, UpdateDate = DateTime.Now }); + } + + private void CreateUmbracoUserGroupData() + { + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 1, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.AdminGroupAlias, Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7ï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-medal" }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 4, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.TranslatorGroupAlias, Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" }); + _database.Insert("umbracoUserGroup", "id", false, new UserGroupDto { Id = 5, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" }); + } + + private void CreateUmbracoUser2UserGroupData() + { + _database.Insert(new User2UserGroupDto { UserGroupId = 1, UserId = 0 }); //add admin to admins + _database.Insert(new User2UserGroupDto { UserGroupId = 5, UserId = 0 }); //add admin to sensitive data + } + + private void CreateUmbracoUserGroup2AppData() + { + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Content }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Developer }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Media }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Members }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Settings }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Users }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 1, AppAlias = Constants.Applications.Forms }); + + _database.Insert(new UserGroup2AppDto { UserGroupId = 2, AppAlias = Constants.Applications.Content }); + + _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Content }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Media }); + _database.Insert(new UserGroup2AppDto { UserGroupId = 3, AppAlias = Constants.Applications.Forms }); + + _database.Insert(new UserGroup2AppDto { UserGroupId = 4, AppAlias = Constants.Applications.Translation }); + } + + private void CreateCmsPropertyTypeGroupData() + { + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Image) }); + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 4, ContentTypeNodeId = 1033, Text = "File", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.File) }); + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 5, ContentTypeNodeId = 1031, Text = "Contents", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Contents) }); + //membership property group + _database.Insert("cmsPropertyTypeGroup", "id", false, new PropertyTypeGroupDto { Id = 11, ContentTypeNodeId = 1044, Text = "Membership", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Membership) }); + } + + private void CreateCmsPropertyTypeData() + { + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = 1043, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 7, UniqueId = 7.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 8, UniqueId = 8.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 9, UniqueId = 9.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 10, UniqueId = 10.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 27, UniqueId = 27.ToGuid(), DataTypeId = Constants.System.DefaultMediaListViewDataTypeId, ContentTypeId = 1031, PropertyTypeGroupId = 5, Alias = "contents", Name = "Contents:", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + //membership property types + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null }); + _database.Insert("cmsPropertyType", "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = -92, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null }); + + } + + private void CreateUmbracoLanguageData() + { + _database.Insert("umbracoLanguage", "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "en-US" }); + } + + private void CreateCmsContentTypeAllowedContentTypeData() + { + _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1031 }); + _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1032 }); + _database.Insert("cmsContentTypeAllowedContentType", "Id", false, new ContentTypeAllowedContentTypeDto { Id = 1031, AllowedId = 1033 }); + } + + private void CreateCmsDataTypeData() + { + //TODO Check which of the DataTypeIds below doesn't exist in umbracoNode, which results in a foreign key constraint errors. + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 1, DataTypeId = -49, PropertyEditorAlias = Constants.PropertyEditors.TrueFalseAlias, DbType = "Integer" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 2, DataTypeId = -51, PropertyEditorAlias = Constants.PropertyEditors.IntegerAlias, DbType = "Integer" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 3, DataTypeId = -87, PropertyEditorAlias = Constants.PropertyEditors.TinyMCEAlias, DbType = "Ntext" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 4, DataTypeId = -88, PropertyEditorAlias = Constants.PropertyEditors.TextboxAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 5, DataTypeId = -89, PropertyEditorAlias = Constants.PropertyEditors.TextboxMultipleAlias, DbType = "Ntext" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 6, DataTypeId = -90, PropertyEditorAlias = Constants.PropertyEditors.UploadFieldAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 7, DataTypeId = -92, PropertyEditorAlias = Constants.PropertyEditors.NoEditAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 8, DataTypeId = -36, PropertyEditorAlias = Constants.PropertyEditors.DateTimeAlias, DbType = "Date" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 9, DataTypeId = -37, PropertyEditorAlias = Constants.PropertyEditors.ColorPickerAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 11, DataTypeId = -39, PropertyEditorAlias = Constants.PropertyEditors.DropDownListFlexibleAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 12, DataTypeId = -40, PropertyEditorAlias = Constants.PropertyEditors.RadioButtonListAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 13, DataTypeId = -41, PropertyEditorAlias = Constants.PropertyEditors.DateAlias, DbType = "Date" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 14, DataTypeId = -42, PropertyEditorAlias = Constants.PropertyEditors.DropDownListFlexibleAlias, DbType = "Integer" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 15, DataTypeId = -43, PropertyEditorAlias = Constants.PropertyEditors.CheckBoxListAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 22, DataTypeId = 1041, PropertyEditorAlias = Constants.PropertyEditors.TagsAlias, DbType = "Ntext" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 24, DataTypeId = 1043, PropertyEditorAlias = Constants.PropertyEditors.ImageCropperAlias, DbType = "Ntext" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -26, DataTypeId = Constants.System.DefaultContentListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -27, DataTypeId = Constants.System.DefaultMediaListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = -28, DataTypeId = Constants.System.DefaultMembersListViewDataTypeId, PropertyEditorAlias = Constants.PropertyEditors.ListViewAlias, DbType = "Nvarchar" }); + + //New UDI pickers with newer Ids + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 26, DataTypeId = 1046, PropertyEditorAlias = Constants.PropertyEditors.ContentPicker2Alias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 27, DataTypeId = 1047, PropertyEditorAlias = Constants.PropertyEditors.MemberPicker2Alias, DbType = "Nvarchar" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 28, DataTypeId = 1048, PropertyEditorAlias = Constants.PropertyEditors.MediaPicker2Alias, DbType = "Ntext" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 29, DataTypeId = 1049, PropertyEditorAlias = Constants.PropertyEditors.MediaPicker2Alias, DbType = "Ntext" }); + _database.Insert("cmsDataType", "pk", false, new DataTypeDto { PrimaryKey = 30, DataTypeId = 1050, PropertyEditorAlias = Constants.PropertyEditors.RelatedLinks2Alias, DbType = "Ntext" }); + } + + private void CreateCmsDataTypePreValuesData() + { + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 3, Alias = "", SortOrder = 0, DataTypeNodeId = -87, Value = ",code,undo,redo,cut,copy,mcepasteword,stylepicker,bold,italic,bullist,numlist,outdent,indent,mcelink,unlink,mceinsertanchor,mceimage,umbracomacro,mceinserttable,umbracoembed,mcecharmap,|1|1,2,3,|0|500,400|1049,|true|" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 4, Alias = "group", SortOrder = 0, DataTypeNodeId = 1041, Value = "default" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 5, Alias = "storageType", SortOrder = 0, DataTypeNodeId = 1041, Value = "Json" }); + + //defaults for the member list + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -1, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "10" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -2, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "username" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -3, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "asc" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -4, Alias = "includeProperties", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMembersListViewDataTypeId, Value = "[{\"alias\":\"username\",\"isSystem\":1},{\"alias\":\"email\",\"isSystem\":1},{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1}]" }); + + //layouts for the list view + var cardLayout = "{\"name\": \"Grid\",\"path\": \"views/propertyeditors/listview/layouts/grid/grid.html\", \"icon\": \"icon-thumbnails-small\", \"isSystem\": 1, \"selected\": true}"; + var listLayout = "{\"name\": \"List\",\"path\": \"views/propertyeditors/listview/layouts/list/list.html\",\"icon\": \"icon-list\", \"isSystem\": 1,\"selected\": true}"; + + //defaults for the media list + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -5, Alias = "pageSize", SortOrder = 1, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "100" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -6, Alias = "orderBy", SortOrder = 2, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "updateDate" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -7, Alias = "orderDirection", SortOrder = 3, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "desc" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -8, Alias = "layouts", SortOrder = 4, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[" + cardLayout + "," + listLayout + "]" }); + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = -9, Alias = "includeProperties", SortOrder = 5, DataTypeNodeId = Constants.System.DefaultMediaListViewDataTypeId, Value = "[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]" }); + + //default's for MultipleMediaPickerAlias picker + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 6, Alias = "multiPicker", SortOrder = 0, DataTypeNodeId = 1049, Value = "1" }); + + // Defaults for single item dropdown + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 7, Alias = "multiple", SortOrder = 0, DataTypeNodeId = -42, Value = "0" }); + + // Defaults for multiple item dropdown + _database.Insert("cmsDataTypePreValues", "id", false, new DataTypePreValueDto { Id = 8, Alias = "multiple", SortOrder = 0, DataTypeNodeId = -39, Value = "1" }); + } + + private void CreateUmbracoRelationTypeData() + { + var relationType = new RelationTypeDto { Id = 1, Alias = Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = true, Name = Constants.Conventions.RelationTypes.RelateDocumentOnCopyName }; + relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + _database.Insert("umbracoRelationType", "id", false, relationType); + relationType = new RelationTypeDto { Id = 2, Alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Document), ParentObjectType = new Guid(Constants.ObjectTypes.Document), Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName }; + relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + _database.Insert("umbracoRelationType", "id", false, relationType); + relationType = new RelationTypeDto { Id = 3, Alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, ChildObjectType = new Guid(Constants.ObjectTypes.Media), ParentObjectType = new Guid(Constants.ObjectTypes.Media), Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName }; + relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid(); + _database.Insert("umbracoRelationType", "id", false, relationType); + } + + private void CreateCmsTaskTypeData() + { + _database.Insert("cmsTaskType", "id", false, new TaskTypeDto { Id = 1, Alias = "toTranslate" }); + } + + private void CreateUmbracoMigrationData() + { + var dto = new MigrationDto + { + Id = 1, + Name = Constants.System.UmbracoMigrationName, + Version = UmbracoVersion.GetSemanticVersion().ToString(), + CreateDate = DateTime.Now + }; + + _database.Insert("umbracoMigration", "pk", false, dto); + } + } +} diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 43c2b69c47..b02fb644d8 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -356,7 +356,7 @@ namespace Umbraco.Core.Services if (dataType != null) { var preValues = GetPreValuesCollectionByDataTypeId(dataType.Id); - if (preValues.PreValuesAsDictionary.TryGetValue("ignoreUserStartNodes", out var preValue)) + if (preValues.PreValuesAsDictionary.TryGetValue(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, out var preValue)) { return string.Equals(preValue.Value, "1", StringComparison.InvariantCulture); } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 0b3c923c83..f29c1bea46 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -324,6 +324,9 @@ + + Constants.cs + diff --git a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs b/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs index 9d1331e691..652bd34fff 100644 --- a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs @@ -1,84 +1,93 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - internal class PreValueDisplayResolver : ValueResolver> - { - private readonly IDataTypeService _dataTypeService; - - public PreValueDisplayResolver(IDataTypeService dataTypeService) - { - _dataTypeService = dataTypeService; +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + internal class PreValueDisplayResolver : ValueResolver> + { + private readonly IDataTypeService _dataTypeService; + + public PreValueDisplayResolver(IDataTypeService dataTypeService) + { + _dataTypeService = dataTypeService; } - /// - /// Maps pre-values in the dictionary to the values for the fields. - /// - /// The fields. - /// The pre-values. - /// The editor alias. - internal static void MapPreValueValuesToPreValueFields(PreValueFieldDisplay[] fields, IDictionary preValues, string editorAlias) - { - if (fields == null) throw new ArgumentNullException(nameof(fields)); - if (preValues == null) throw new ArgumentNullException(nameof(preValues)); - - // Now we need to wire up the pre-values values with the actual fields defined - foreach (var field in fields) - { - // If the dictionary would be constructed with StringComparer.InvariantCultureIgnoreCase, we could just use TryGetValue - var preValue = preValues.SingleOrDefault(x => x.Key.InvariantEquals(field.Key)); - if (preValue.Key == null) - { - LogHelper.Warn("Could not find persisted pre-value for field {0} on property editor {1}", () => field.Key, () => editorAlias); - continue; - } - - field.Value = preValue.Value; - } - } - - internal IEnumerable Convert(IDataTypeDefinition source) - { - PropertyEditor propEd = null; - if (source.PropertyEditorAlias.IsNullOrWhiteSpace() == false) - { - propEd = PropertyEditorResolver.Current.GetByAlias(source.PropertyEditorAlias); - if (propEd == null) - { - throw new InvalidOperationException("Could not find property editor with alias " + source.PropertyEditorAlias); - } - } - - // Set up the defaults - var dataTypeService = _dataTypeService; - var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(source.Id); - IDictionary dictionaryVals = preVals.FormatAsDictionary().ToDictionary(x => x.Key, x => (object)x.Value); - var result = Enumerable.Empty().ToArray(); - - // If we have a prop editor, then format the pre-values based on it and create it's fields - if (propEd != null) - { - result = propEd.PreValueEditor.Fields.Select(Mapper.Map).ToArray(); - dictionaryVals = propEd.PreValueEditor.ConvertDbToEditor(propEd.DefaultPreValues, preVals); - } - - MapPreValueValuesToPreValueFields(result, dictionaryVals, source.PropertyEditorAlias); - - return result; - } - - protected override IEnumerable ResolveCore(IDataTypeDefinition source) - { - return Convert(source); - } - } -} + /// + /// Maps pre-values in the dictionary to the values for the fields. + /// + /// The fields. + /// The pre-values. + /// The editor alias. + internal static void MapPreValueValuesToPreValueFields(IEnumerable fields, IDictionary preValues, string editorAlias) + { + if (fields == null) throw new ArgumentNullException(nameof(fields)); + if (preValues == null) throw new ArgumentNullException(nameof(preValues)); + + // Now we need to wire up the pre-values values with the actual fields defined + foreach (var field in fields) + { + // If the dictionary would be constructed with StringComparer.InvariantCultureIgnoreCase, we could just use TryGetValue + var preValue = preValues.SingleOrDefault(x => x.Key.InvariantEquals(field.Key)); + if (preValue.Key == null) + { + LogHelper.Warn("Could not find persisted pre-value for field {0} on property editor {1}", () => field.Key, () => editorAlias); + continue; + } + + field.Value = preValue.Value; + } + } + + internal IEnumerable Convert(IDataTypeDefinition source) + { + PropertyEditor propEd = null; + if (source.PropertyEditorAlias.IsNullOrWhiteSpace() == false) + { + propEd = PropertyEditorResolver.Current.GetByAlias(source.PropertyEditorAlias); + if (propEd == null) + { + throw new InvalidOperationException("Could not find property editor with alias " + source.PropertyEditorAlias); + } + } + + // Set up the defaults + var dataTypeService = _dataTypeService; + var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(source.Id); + IDictionary dictionaryVals = preVals.FormatAsDictionary().ToDictionary(x => x.Key, x => (object)x.Value); + var result = Enumerable.Empty(); + + // If we have a prop editor, then format the pre-values based on it and create it's fields + if (propEd != null) + { + result = propEd.PreValueEditor.Fields.Select(Mapper.Map).AsEnumerable(); + if (source.IsBuildInDataType) + { + result = RemovePreValuesNotSupportedOnBuildInTypes(result); + } + dictionaryVals = propEd.PreValueEditor.ConvertDbToEditor(propEd.DefaultPreValues, preVals); + } + + MapPreValueValuesToPreValueFields(result, dictionaryVals, source.PropertyEditorAlias); + + return result; + } + + private IEnumerable RemovePreValuesNotSupportedOnBuildInTypes(IEnumerable preValues) + { + return preValues.Where(preValue => string.Equals(preValue.Key, Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes) == false); + } + + protected override IEnumerable ResolveCore(IDataTypeDefinition source) + { + return Convert(source); + } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs index 0039385e5f..46f251ebf4 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs @@ -15,11 +15,11 @@ namespace Umbraco.Web.PropertyEditors { InternalPreValues = new Dictionary { - {"startNodeId", "-1"}, + {"startNodeId", "-1"}, {"showOpenButton", "0"}, {"showEditButton", "0"}, {"showPathOnHover", "0"}, - {"ignoreUserStartNodes", "0"}, + {Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "0"}, {"idType", "udi"} }; } @@ -40,9 +40,9 @@ namespace Umbraco.Web.PropertyEditors { public ContentPickerPreValueEditor() { - //create the fields + //create the fields Fields.Add(new PreValueField() - { + { Key = "showOpenButton", View = "boolean", Name = "Show open button (this feature is in preview!)", @@ -50,7 +50,7 @@ namespace Umbraco.Web.PropertyEditors }); Fields.Add(new PreValueField() { - Key = "ignoreUserStartNodes", + Key = Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, View = "boolean", Name = "Ignore user start nodes", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs index 4a8803a099..94aed3e852 100644 --- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.PropertyEditors { [PropertyEditor(Core.Constants.PropertyEditors.GridAlias, "Grid layout", "grid", HideLabel = true, IsParameterEditor = false, ValueType = PropertyEditorValueTypes.Json, Group="rich content", Icon="icon-layout")] public class GridPropertyEditor : PropertyEditor, IApplicationEventHandler - { + { private static void DocumentWriting(object sender, Examine.LuceneEngine.DocumentWritingEventArgs e) { @@ -49,7 +49,7 @@ namespace Umbraco.Web.PropertyEditors foreach (var areaVal in areaVals) { - //TODO: If it's not a string, then it's a json formatted value - + //TODO: If it's not a string, then it's a json formatted value - // we cannot really index this in a smart way since it could be 'anything' if (areaVal.Type == JTokenType.String) { @@ -87,12 +87,12 @@ namespace Umbraco.Web.PropertyEditors catch (InvalidCastException) { //swallow...on purpose, there's a chance that this isn't the json format we are looking for - // and we don't want that to affect the website. + // and we don't want that to affect the website. } catch (JsonException) { - //swallow...on purpose, there's a chance that this isn't json and we don't want that to affect - // the website. + //swallow...on purpose, there's a chance that this isn't json and we don't want that to affect + // the website. } catch (ArgumentException) { @@ -137,7 +137,7 @@ namespace Umbraco.Web.PropertyEditors [PreValueField("rte", "Rich text editor", "views/propertyeditors/rte/rte.prevalues.html", Description = "Rich text editor configuration")] public string Rte { get; set; } - [PreValueField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] + [PreValueField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } } @@ -152,7 +152,7 @@ namespace Umbraco.Web.PropertyEditors { /// /// We're going to bind to the Examine events so we can ensure grid data is index nicely. - /// + /// protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { foreach (var i in ExamineManager.Instance.IndexProviderCollection.OfType()) @@ -175,7 +175,7 @@ namespace Umbraco.Web.PropertyEditors public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { //wrap - _applicationStartup.OnApplicationStarted(umbracoApplication, applicationContext); + _applicationStartup.OnApplicationStarted(umbracoApplication, applicationContext); } #endregion } diff --git a/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs index 6aa7ab4a54..e62ac98abd 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.PropertyEditors } internal IDictionary InternalPreValues; - + public override IDictionary DefaultPreValues { get { return InternalPreValues; } @@ -58,7 +58,7 @@ namespace Umbraco.Web.PropertyEditors }); Fields.Add(new PreValueField() { - Key = "ignoreUserStartNodes", + Key = Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, View = "boolean", Name = "Ignore user start nodes", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs index f57a9951b6..b79cd96313 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs @@ -16,11 +16,11 @@ namespace Umbraco.Web.PropertyEditors {"showOpenButton", "0"}, {"showEditButton", "0"}, {"showPathOnHover", "0"}, - {"ignoreUserStartNodes", "0"}, + {Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "0"}, {"idType", "udi"} }; } - + protected override PreValueEditor CreatePreValueEditor() { return new MultiNodePickerPreValueEditor(); @@ -40,7 +40,7 @@ namespace Umbraco.Web.PropertyEditors //create the fields Fields.Add(new PreValueField() { - Key = "ignoreUserStartNodes", + Key = Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, View = "boolean", Name = "Ignore user start nodes", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." @@ -81,7 +81,7 @@ namespace Umbraco.Web.PropertyEditors Name = "Show open button (this feature is in preview!)", Description = "Opens the node in a dialog" }); - } + } /// /// This ensures the multiPicker pre-val is set based on the maxNumber of nodes set @@ -117,9 +117,9 @@ namespace Umbraco.Web.PropertyEditors { result["multiPicker"] = "1"; } - } + } } - + return result; } diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs index f3ae317efa..ea3a1572b1 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs @@ -34,7 +34,7 @@ namespace Umbraco.Web.PropertyEditors { Fields.Add(new PreValueField() { - Key = "ignoreUserStartNodes", + Key = Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, View = "boolean", Name = "Ignore user start nodes", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs index a96c0724ff..289e6588a9 100644 --- a/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs @@ -14,7 +14,7 @@ namespace Umbraco.Web.PropertyEditors { InternalPreValues = new Dictionary { - {"ignoreUserStartNodes", "0"}, + {Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "0"}, {"idType", "udi"} }; } @@ -33,11 +33,11 @@ namespace Umbraco.Web.PropertyEditors internal class RelatedLinksPreValueEditor : PreValueEditor { - [PreValueField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] + [PreValueField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } [PreValueField("max", "Maximum number of links", "number", Description = "Enter the maximum amount of links to be added, enter 0 for unlimited")] - public int Maximum { get; set; } + public int Maximum { get; set; } } } } diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs index 69445bc304..9fd2bf6de4 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs @@ -1,73 +1,74 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - //need to figure out how to use this... - internal class RichTextPreValueEditor : PreValueEditor - { - public RichTextPreValueEditor() - { - //SD: You can add pre-val fields here like you are doing, or you can add fields using attributes (http://issues.umbraco.org/issue/U4-2692), - // see below for examples. - - //use a custom editor too - Fields.Add(new PreValueField() - { - View = "views/propertyeditors/rte/rte.prevalues.html", - HideLabel = true, - Key = "editor" - }); - - Fields.Add(new PreValueField() - { - Key = "ignoreUserStartNodes", - View = "boolean", - Name = "Ignore user start nodes", - Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." - }); - - Fields.Add(new PreValueField() - { - Name = "Hide Label", - View = "boolean", - Key = "hideLabel" - }); - } - - //SD: You can declare a field like this if you want to instead of in the ctor, there's some options here: - //#1 - the property name becomes the Key: - // - // [PreValueField("", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] - // public string Editor { get; set; } - - //#2 - You can specify a custom Key: - // - // [PreValueField("editor", "", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] - // public string Editor { get; set; } - - //#3 - If you require custom server side validation for your field then you have to specify a custom PreValueField type to use, - // this is why in this case I find it easier to use the ctor logic but thats just an opinion - // - Any value specified for this property attribute will override the values set in on the class instance of the field, this - // allows you to re-use class instances of fields if you want. - // - // [PreValueField(typeof(EditorPreValueField))] - // public string Editor { get; set; } - - // [PreValueField("", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] - // public class EditorPreValueField : PreValueField - // { - // public EditorPreValueField() - // { - // //add any required server validators for this field - // Validators.Add(new RegexValidator("^\\d*$")); - // //You could also set the field properties directly here if you wanted instead of the attribute - // } - // } - - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Web.PropertyEditors +{ + //need to figure out how to use this... + internal class RichTextPreValueEditor : PreValueEditor + { + public RichTextPreValueEditor() + { + //SD: You can add pre-val fields here like you are doing, or you can add fields using attributes (http://issues.umbraco.org/issue/U4-2692), + // see below for examples. + + //use a custom editor too + Fields.Add(new PreValueField() + { + View = "views/propertyeditors/rte/rte.prevalues.html", + HideLabel = true, + Key = "editor" + }); + + Fields.Add(new PreValueField() + { + Key = Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + View = "boolean", + Name = "Ignore user start nodes", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to." + }); + + Fields.Add(new PreValueField() + { + Name = "Hide Label", + View = "boolean", + Key = "hideLabel" + }); + } + + //SD: You can declare a field like this if you want to instead of in the ctor, there's some options here: + //#1 - the property name becomes the Key: + // + // [PreValueField("", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] + // public string Editor { get; set; } + + //#2 - You can specify a custom Key: + // + // [PreValueField("editor", "", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] + // public string Editor { get; set; } + + //#3 - If you require custom server side validation for your field then you have to specify a custom PreValueField type to use, + // this is why in this case I find it easier to use the ctor logic but thats just an opinion + // - Any value specified for this property attribute will override the values set in on the class instance of the field, this + // allows you to re-use class instances of fields if you want. + // + // [PreValueField(typeof(EditorPreValueField))] + // public string Editor { get; set; } + + // [PreValueField("", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] + // public class EditorPreValueField : PreValueField + // { + // public EditorPreValueField() + // { + // //add any required server validators for this field + // Validators.Add(new RegexValidator("^\\d*$")); + // //You could also set the field properties directly here if you wanted instead of the attribute + // } + // } + + } +} From 8d12170def955ee00f53dcc7d19d441ad532a395 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 21 Jun 2019 14:58:58 +0200 Subject: [PATCH 016/776] Correct NuGet transform for the web.config file in the Views folder --- build/NuSpecs/tools/Views.Web.config.install.xdt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build/NuSpecs/tools/Views.Web.config.install.xdt b/build/NuSpecs/tools/Views.Web.config.install.xdt index 4d660301a8..828bb8612f 100644 --- a/build/NuSpecs/tools/Views.Web.config.install.xdt +++ b/build/NuSpecs/tools/Views.Web.config.install.xdt @@ -8,7 +8,7 @@ - + @@ -18,13 +18,13 @@ - + From cb16eb1826b3cc3282beeb4c945106f990e8bd59 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sat, 22 Jun 2019 10:43:40 +0200 Subject: [PATCH 017/776] Don't expand the last item when pasting multiple items --- .../nestedcontent/nestedcontent.controller.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 5c97a935a1..440f391a44 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -165,10 +165,10 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop if (item.alias === "nc_pasteAllItems") { _.each(item.data, function (node) { delete node.$$hashKey; - $scope.pasteFromClipboard(node); + $scope.pasteFromClipboard(node, false); }); } else { - $scope.pasteFromClipboard(item.data); + $scope.pasteFromClipboard(item.data, true); } $scope.overlayMenu.show = false; @@ -404,7 +404,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop }); } - $scope.pasteFromClipboard = function(newNode) { + $scope.pasteFromClipboard = function(newNode, setCurrentNode) { if (newNode === undefined) { return; @@ -415,9 +415,14 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop $scope.nodes.push(newNode); $scope.setDirty(); - //updateModel();// done by setting current node... + + if (setCurrentNode) { + $scope.currentNode = newNode; + } + else { + updateModel(); + } - $scope.currentNode = newNode; } function checkAbilityToPasteContent() { From d38c12115e5a4b5d59db0d3b9f8cccffee1e1546 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sat, 22 Jun 2019 13:23:50 +0200 Subject: [PATCH 018/776] Remove some debugging stuff --- .../propertyeditors/nestedcontent/nestedcontent.controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 440f391a44..567f78bc39 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -605,7 +605,6 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop })); $scope.$on("$destroy", function () { - console.log("Unsubscribing", unsubscribe.length) for (var u in unsubscribe) { unsubscribe[u](); } From 601d48277e3042ef0655ede6370b750414930313 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Sun, 23 Jun 2019 19:53:57 +0100 Subject: [PATCH 019/776] Update NuSpecs with the XML MetaData for SourceLink --- build/NuSpecs/UmbracoCms.Core.nuspec | 1 + build/NuSpecs/UmbracoCms.Web.nuspec | 1 + build/NuSpecs/UmbracoCms.nuspec | 1 + 3 files changed, 3 insertions(+) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 56462fcc40..d93cd4e241 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -14,6 +14,7 @@ Contains the core assemblies needed to run Umbraco Cms en-US umbraco + diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 614a816f3f..4937c85466 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -14,6 +14,7 @@ Contains the core assemblies needed to run Umbraco Cms en-US umbraco + diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 5cdacca419..99a4a9e98c 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -14,6 +14,7 @@ Installs Umbraco Cms in your Visual Studio ASP.NET project en-US umbraco + From 003ee1a9916c4cd0c69389c099bc1447daf9ea66 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Sun, 23 Jun 2019 19:57:43 +0100 Subject: [PATCH 020/776] Added in Microsoft.SourceLink.GitHub Nuget package --- src/Umbraco.Core/Umbraco.Core.csproj | 5 +++++ src/Umbraco.Examine/Umbraco.Examine.csproj | 5 +++++ src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 5 +++++ src/Umbraco.Web/Umbraco.Web.csproj | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 7c0e41348b..a874024c85 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -55,6 +55,11 @@ + + 1.0.0-beta2-19270-01 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + 1.3.0 diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index 05b209f927..14a57c3216 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -49,6 +49,11 @@ + + 1.0.0-beta2-19270-01 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 7ae996a6c5..d9aba825d8 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -101,6 +101,11 @@ + + 1.0.0-beta2-19270-01 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7ebcf55155..af4b37f2b1 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -80,6 +80,11 @@ + + 1.0.0-beta2-19270-01 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + From eab597a2a8bc09916c912eeceba1d46cbd359b99 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 24 Jun 2019 08:58:25 +0200 Subject: [PATCH 021/776] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - fix for js linter --- .../src/common/resources/entity.resource.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 3991932d2d..31ff7424ac 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -307,7 +307,7 @@ function entityResource($q, $http, umbRequestHelper) { var args = [ { id: id }, - { type: type }, + { type: type } ]; if(options.dataTypeId){ args.push({dataTypeId: options.dataTypeId}); From d4311fe4ab50b3a5bd7716765739f5deb7826d74 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 24 Jun 2019 10:52:35 +0200 Subject: [PATCH 022/776] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - Replaced client side method to get Auchors with a server side version, and eliminated the use of contentResource in linkPikers --- src/Umbraco.Core/Services/ContentService.cs | 5843 +++++++++-------- src/Umbraco.Core/Services/IContentService.cs | 1437 ++-- .../src/common/resources/entity.resource.js | 35 + .../src/common/services/tinymce.service.js | 1729 +++-- .../common/dialogs/linkpicker.controller.js | 18 +- .../linkpicker/linkpicker.controller.js | 16 +- .../grid/editors/rte.controller.js | 29 +- .../propertyeditors/rte/rte.controller.js | 34 +- src/Umbraco.Web/Editors/EntityController.cs | 20 + .../Models/ContentEditing/UrlAndAnchors.cs | 21 + src/Umbraco.Web/Umbraco.Web.csproj | 4053 ++++++------ 11 files changed, 6665 insertions(+), 6570 deletions(-) create mode 100644 src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index a1e7c9955e..cf49e892dd 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1,2904 +1,2939 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Xml; -using System.Xml.Linq; -using Umbraco.Core.Events; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Publishing; - -namespace Umbraco.Core.Services -{ - /// - /// Represents the Content Service, which is an easy access to operations involving - /// - public class ContentService : ScopeRepositoryService, IContentService, IContentServiceOperations - { - private readonly IPublishingStrategy2 _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, - IDataTypeService dataTypeService, - IUserService userService) - : base(provider, repositoryFactory, logger, eventMessagesFactory) - { - if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); - if (userService == null) throw new ArgumentNullException("userService"); - _publishingStrategy = new PublishingStrategy(UowProvider.ScopeProvider, eventMessagesFactory, logger); - _dataTypeService = dataTypeService; - _userService = userService; - } - - #region Static Queries - - private IQuery _notTrashedQuery; - - #endregion - - public int CountPublished(string contentTypeAlias = null) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.CountPublished(contentTypeAlias); - } - } - - public int Count(string contentTypeAlias = null) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.Count(contentTypeAlias); - } - } - - public int CountChildren(int parentId, string contentTypeAlias = null) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.CountChildren(parentId, contentTypeAlias); - } - } - - public int CountDescendants(int parentId, string contentTypeAlias = null) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - 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) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - repository.ReplaceContentPermissions(permissionSet); - uow.Commit(); - } - } - - /// - /// Assigns a single permission to the current content item for the specified group ids - /// - /// - /// - /// - public void AssignContentPermission(IContent entity, char permission, IEnumerable groupIds) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - repository.AssignEntityPermission(entity, permission, groupIds); - uow.Commit(); - } - } - - /// - /// Returns implicit/inherited permissions assigned to the content item for all user groups - /// - /// - /// - public EntityPermissionCollection GetPermissionsForEntity(IContent content) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - 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, Guid parentId, string contentTypeAlias, int userId = 0) - { - var parent = GetById(parentId); - return CreateContent(name, parent, contentTypeAlias, userId); - } - - /// - /// 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); - - using (var uow = UowProvider.GetUnitOfWork()) - { - var newEventArgs = new NewEventArgs(content, contentTypeAlias, parentId); - if (uow.Events.DispatchCancelable(Creating, this, newEventArgs)) - { - uow.Commit(); - content.WasCancelled = true; - return content; - } - - content.CreatorId = userId; - content.WriterId = userId; - newEventArgs.CanCancel = false; - uow.Events.Dispatch(Created, this, newEventArgs); - - 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); - - using (var uow = UowProvider.GetUnitOfWork()) - { - var newEventArgs = new NewEventArgs(content, contentTypeAlias, parent); - if (uow.Events.DispatchCancelable(Creating, this, newEventArgs)) - { - uow.Commit(); - content.WasCancelled = true; - return content; - } - - content.CreatorId = userId; - content.WriterId = userId; - newEventArgs.CanCancel = false; - uow.Events.Dispatch(Created, this, newEventArgs); - - uow.Commit(); - } - - 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); - - using (var uow = UowProvider.GetUnitOfWork()) - { - //NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found - // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. - var newEventArgs = new NewEventArgs(content, contentTypeAlias, parentId); - if (uow.Events.DispatchCancelable(Creating, this, newEventArgs)) - { - uow.Commit(); - content.WasCancelled = true; - return content; - } - - var saveEventArgs = new SaveEventArgs(content); - if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) - { - uow.Commit(); - content.WasCancelled = true; - return content; - } - - var repository = RepositoryFactory.CreateContentRepository(uow); - content.CreatorId = userId; - content.WriterId = userId; - - repository.AddOrUpdate(content); - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); - newEventArgs.CanCancel = false; - uow.Events.Dispatch(Created, this, newEventArgs); - - Audit(uow, AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id); - uow.Commit(); - } - - 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); - - using (var uow = UowProvider.GetUnitOfWork()) - { - //NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found - // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. - var newEventArgs = new NewEventArgs(content, contentTypeAlias, parent); - if (uow.Events.DispatchCancelable(Creating, this, newEventArgs)) - { - uow.Commit(); - content.WasCancelled = true; - return content; - } - - var saveEventArgs = new SaveEventArgs(content); - if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) - { - uow.Commit(); - content.WasCancelled = true; - return content; - } - - var repository = RepositoryFactory.CreateContentRepository(uow); - content.CreatorId = userId; - content.WriterId = userId; - - repository.AddOrUpdate(content); - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); - newEventArgs.CanCancel = false; - uow.Events.Dispatch(Created, this, newEventArgs); - - Audit(uow, AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id); - uow.Commit(); - } - - return content; - } - - /// - /// Gets an object by Id - /// - /// Id of the Content to retrieve - /// - public IContent GetById(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.Get(id); - } - } - - /// - /// Gets objects by Ids - /// - /// Ids of the Content to retrieve - /// - public IEnumerable GetByIds(IEnumerable ids) - { - var idsArray = ids.ToArray(); - if (idsArray.Length == 0) return Enumerable.Empty(); - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - // ensure that the result has the order based on the ids passed in - var result = repository.GetAll(idsArray); - var content = result.ToDictionary(x => x.Id, x => x); - - var sortedResult = idsArray.Select(x => - { - IContent c; - return content.TryGetValue(x, out c) ? c : null; - }).WhereNotNull(); - - return sortedResult; - } - } - - /// - /// Gets objects by Ids - /// - /// Ids of the Content to retrieve - /// - public IEnumerable GetByIds(IEnumerable ids) - { - var idsArray = ids.ToArray(); - if (idsArray.Length == 0) return Enumerable.Empty(); - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - // ensure that the result has the order based on the ids passed in - var result = repository.GetAll(idsArray); - var content = result.ToDictionary(x => x.Key, x => x); - - var sortedResult = idsArray.Select(x => - { - IContent c; - return content.TryGetValue(x, out c) ? c : null; - }).WhereNotNull(); - - return sortedResult; - } - } - - /// - /// Gets an object by its 'UniqueId' - /// - /// Guid key of the Content to retrieve - /// - public IContent GetById(Guid key) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.Get(key); - } - } - - /// - /// 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.ContentTypeId == id); - return repository.GetByQuery(query); - } - } - - internal IEnumerable GetPublishedContentOfContentType(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.ContentTypeId == id); - return repository.GetByPublishedVersion(query); - } - } - - /// - /// 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.Level == level && x.Path.StartsWith(Constants.System.RecycleBinContent.ToInvariantString()) == false); - return repository.GetByQuery(query); - } - } - - /// - /// Gets a specific version of an item. - /// - /// Id of the version to retrieve - /// An item - public IContent GetByVersion(Guid versionId) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.GetAllVersions(id); - } - } - - /// - /// Gets a list of all version Ids for the given content item ordered so latest is first - /// - /// - /// The maximum number of rows to return - /// - public IEnumerable GetVersionIds(int id, int maxRows) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.GetVersionIds(id, maxRows); - } - } - - /// - /// 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.ParentId == id); - return repository.GetByQuery(query).OrderBy(x => x.SortOrder); - } - } - - [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 = "") - { - long total; - var result = GetPagedChildren(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); - totalChildren = Convert.ToInt32(total); - 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 - /// - /// 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, string filter) - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - var query = Query.Builder; - // always check for a parent - else it will also get decendants (and then you should use the GetPagedDescendants method) - query.Where(x => x.ParentId == id); - - IQuery filterQuery = null; - if (filter.IsNullOrWhiteSpace() == false) - { - filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); - } - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filterQuery); - } - } - - [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 = "") - { - long total; - var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); - totalChildren = Convert.ToInt32(total); - 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 - /// - /// 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, Direction orderDirection, bool orderBySystemField, string filter) - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - // get query - if the id is System Root, then just get all - var query = Query.Builder; - if (id != Constants.System.Root) - { - var entityRepository = RepositoryFactory.CreateEntityRepository(uow); - var contentPath = entityRepository.GetAllPaths(Constants.ObjectTypes.DocumentGuid, id).ToArray(); - if (contentPath.Length == 0) - { - totalChildren = 0; - return Enumerable.Empty(); - } - query.Where(x => x.Path.SqlStartsWith(string.Format("{0},", contentPath[0].Path), TextColumnType.NVarchar)); - } - - - // get filter - IQuery filterQuery = null; - if (filter.IsNullOrWhiteSpace() == false) - filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); - - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filterQuery); - } - } - - /// - /// 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 filter - /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - // get query - if the id is System Root, then just get all - var query = Query.Builder; - if (id != Constants.System.Root) - { - var entityRepository = RepositoryFactory.CreateEntityRepository(uow); - var contentPath = entityRepository.GetAllPaths(Constants.ObjectTypes.DocumentGuid, id).ToArray(); - if (contentPath.Length == 0) - { - totalChildren = 0; - return Enumerable.Empty(); - } - query.Where(x => x.Path.SqlStartsWith(string.Format("{0},", contentPath[0].Path), TextColumnType.NVarchar)); - } - - return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); - } - } - - /// - /// 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - var query = Query.Builder.Where(x => x.ParentId == parentId && x.Name.Contains(name)); - return repository.GetByQuery(query); - } - } - - /// - /// 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); - return content == null ? Enumerable.Empty() : 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) - { - //This is a check to ensure that the path is correct for this entity to avoid problems like: http://issues.umbraco.org/issue/U4-9336 due to data corruption - if (content.ValidatePath() == false) - throw new InvalidDataException(string.Format("The content item {0} has an invalid path: {1} with parentID: {2}", content.Id, content.Path, content.ParentId)); - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - var pathMatch = content.Path + ","; - var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != content.Id); - return repository.GetByQuery(query); - } - } - - /// - /// 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); - } - - /// - /// 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - var query = Query.Builder.Where(x => x.ParentId == Constants.System.Root); - return repository.GetByQuery(query); - } - } - - /// - /// Gets all published content items - /// - /// - internal IEnumerable GetAllPublished() - { - //create it once if it is needed (no need for locking here) - if (_notTrashedQuery == null) - { - _notTrashedQuery = Query.Builder.Where(x => x.Trashed == false); - } - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - return repository.GetByPublishedVersion(_notTrashedQuery); - } - } - - /// - /// 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.ExpireDate <= DateTime.Now); - return repository.GetByQuery(query).Where(x => x.HasPublishedVersion); - } - } - - /// - /// 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.Published == false && x.ReleaseDate <= DateTime.Now); - return repository.GetByQuery(query); - } - } - - /// - /// Gets a collection of an objects, which resides in the Recycle Bin - /// - /// An Enumerable list of objects - public IEnumerable GetContentInRecycleBin() - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.Path.StartsWith(Constants.System.RecycleBinContentPathPrefix)); - return repository.GetByQuery(query); - } - } - - /// - /// 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.ParentId == id); - return repository.Count(query); - } - } - - /// - /// 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var query = Query.Builder.Where(x => x.Published == true && x.Id == id && x.Trashed == false); - return repository.Count(query) > 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) - { - int[] ids; - if (content.HasIdentity) - { - // get ids from path (we have identity) - // skip the first one that has to be -1 - and we don't care - // skip the last one that has to be "this" - and it's ok to stop at the parent - ids = content.Path.Split(',').Skip(1).SkipLast().Select(int.Parse).ToArray(); - } - else - { - // no path yet (no identity), have to move up to parent - // skip the first one that has to be -1 - and we don't care - // don't skip the last one that is "parent" - var parent = GetById(content.ParentId); - if (parent == null) return false; - ids = parent.Path.Split(',').Skip(1).Select(int.Parse).ToArray(); - } - if (ids.Length == 0) - return false; - - // if the first one is recycle bin, fail fast - if (ids[0] == Constants.System.RecycleBinContent) - return false; - - // fixme - move to repository? - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var sql = new Sql(@" - SELECT id - FROM umbracoNode - JOIN cmsDocument ON umbracoNode.id=cmsDocument.nodeId AND cmsDocument.published=@0 - WHERE umbracoNode.trashed=@1 AND umbracoNode.id IN (@2)", - true, false, ids); - var x = uow.Database.Fetch(sql); - return ids.Length == x.Count; - } - } - - /// - /// 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) - { - return MoveToRecycleBinDo(content, userId, false); - } - - /// - /// 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 - /// - /// A boolean indicating to ignore this item's descendant list from also being moved to the recycle bin. This is required for the DeleteContentOfTypes method - /// because it has already looked up all descendant nodes that will need to be recycled - /// TODO: Fix all of this, it will require a reasonable refactor and most of this stuff should be done at the repo level instead of service sub operations - /// - private Attempt MoveToRecycleBinDo(IContent content, int userId, bool ignoreDescendants) - { - var evtMsgs = EventMessagesFactory.Get(); - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - //Hack: this ensures that the entity's path is valid and if not it fixes/persists it - //see: http://issues.umbraco.org/issue/U4-9336 - content.EnsureValidPath(Logger, entity => GetById(entity.ParentId), QuickUpdate); - var originalPath = content.Path; - var moveEventInfo = new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent); - var moveEventArgs = new MoveEventArgs(evtMsgs, moveEventInfo); - if (uow.Events.DispatchCancelable(Trashing, this, moveEventArgs, "Trashing")) - { - uow.Commit(); - return OperationStatus.Cancelled(evtMsgs); - } - var moveInfo = new List> - { - moveEventInfo - }; - - //get descendents to process of the content item that is being moved to trash - must be done before changing the state below - //must be processed with shallowest levels first - var descendants = ignoreDescendants ? Enumerable.Empty() : GetDescendants(content).OrderBy(x => x.Level); - - //Do the updates for this item - var repository = RepositoryFactory.CreateContentRepository(uow); - //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); - } - 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) - { - //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); - descendant.WriterId = userId; - descendant.ChangeTrashedState(true, descendant.ParentId); - repository.AddOrUpdate(descendant); - - moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); - } - - moveEventArgs.CanCancel = false; - moveEventArgs.MoveInfoCollection = moveInfo; - uow.Events.Dispatch(Trashed, this, moveEventArgs, "Trashed"); - - Audit(uow, AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id); - uow.Commit(); - } - - 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) - { - var attempt = ((IContentServiceOperations)this).UnPublish(content, userId); - LogHelper.Debug(string.Format("Result of unpublish attempt: {0}", attempt.Result.StatusType)); - return attempt.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); - } - - public IContent GetBlueprintById(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); - var blueprint = repository.Get(id); - if (blueprint != null) - ((Content) blueprint).IsBlueprint = true; - return blueprint; - } - } - - public IContent GetBlueprintById(Guid id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); - var blueprint = repository.Get(id); - if (blueprint != null) - ((Content)blueprint).IsBlueprint = true; - return blueprint; - } - } - - public void SaveBlueprint(IContent content, int userId = 0) - { - //always ensure the blueprint is at the root - if (content.ParentId != -1) - content.ParentId = -1; - - ((Content) content).IsBlueprint = true; - - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - if (string.IsNullOrWhiteSpace(content.Name)) - { - throw new ArgumentException("Cannot save content blueprint with empty name."); - } - - var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); - - if (content.HasIdentity == false) - { - content.CreatorId = userId; - } - content.WriterId = userId; - - repository.AddOrUpdate(content); - - uow.Events.Dispatch(SavedBlueprint, this, new SaveEventArgs(content), "SavedBlueprint"); - - uow.Commit(); - } - } - } - - public void DeleteBlueprint(IContent content, int userId = 0) - { - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); - repository.Delete(content); - uow.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(content), "DeletedBlueprint"); - uow.Commit(); - } - } - } - - public IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = 0) - { - if (blueprint == null) throw new ArgumentNullException("blueprint"); - - var contentType = blueprint.ContentType; - var content = new Content(name, -1, contentType); - content.Path = string.Concat(content.ParentId.ToString(), ",", content.Id); - - content.CreatorId = userId; - content.WriterId = userId; - - foreach (var property in blueprint.Properties) - content.SetValue(property.Alias, property.Value); - - return content; - } - - public void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = 0) - { - using (new WriteLock(Locker)) - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); - - var contentTypeIdsA = contentTypeIds.ToArray(); - var query = new Query(); - if (contentTypeIdsA.Length > 0) - { - query.Where(x => contentTypeIdsA.Contains(x.ContentTypeId)); - } - var blueprints = repository.GetByQuery(query).Select(x => - { - ((Content) x).IsBlueprint = true; - return x; - }).ToArray(); - - foreach (var blueprint in blueprints) - { - repository.Delete(blueprint); - } - - uow.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(blueprints), "DeletedBlueprint"); - uow.Commit(); - } - } - - public void DeleteBlueprintsOfType(int contentTypeId, int userId = 0) - { - DeleteBlueprintsOfTypes(new[] {contentTypeId}, 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. - 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(); - - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(asArray, evtMsgs); - if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) - { - uow.Commit(); - return OperationStatus.Cancelled(evtMsgs); - } - - // todo - understand what's a lock in a scope? - // (though, these locks are refactored in v8) - using (new WriteLock(Locker)) - { - var containsNew = asArray.Any(x => x.HasIdentity == false); - 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)); - } - } - } - - if (raiseEvents) - { - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); - } - - Audit(uow, AuditType.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, Constants.System.Root); - uow.Commit(); - - 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)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var deleteEventArgs = new DeleteEventArgs(content, evtMsgs); - if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs, "Deleting")) - { - uow.Commit(); - 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 repository = RepositoryFactory.CreateContentRepository(uow); - - repository.Delete(content); - - deleteEventArgs.CanCancel = false; - uow.Events.Dispatch(Deleted, this, deleteEventArgs, "Deleted"); - - Audit(uow, AuditType.Delete, "Delete Content performed by user", userId, content.Id); - uow.Commit(); - } - - 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 the specified types. All Descendants of deleted content that is not of these types is moved to Recycle Bin. - /// - /// Id of the - /// Optional Id of the user issueing the delete operation - public void DeleteContentOfTypes(IEnumerable contentTypeIds, int userId = 0) - { - using (new WriteLock(Locker)) - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - //track the 'root' items of the collection of nodes discovered to delete, we need to use - //these items to lookup descendants that are not of this doc type so they can be transfered - //to the recycle bin - IDictionary rootItems; - var contentToDelete = this.TrackDeletionsForDeleteContentOfTypes(contentTypeIds, repository, out rootItems).ToArray(); - - if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(contentToDelete), "Deleting")) - { - uow.Commit(); - return; - } - - //Determine the items that will need to be recycled (that are children of these content items but not of these content types) - var contentToRecycle = this.TrackTrashedForDeleteContentOfTypes(contentTypeIds, rootItems, repository); - - //move each item to the bin starting with the deepest items - foreach (var child in contentToRecycle.OrderByDescending(x => x.Level)) - { - MoveToRecycleBinDo(child, userId, true); - } - - foreach (var content in contentToDelete) - { - Delete(content, userId); - } - - Audit(uow, AuditType.Delete, - string.Format("Delete Content of Types {0} performed by user", string.Join(",", contentTypeIds)), - userId, Constants.System.Root); - uow.Commit(); - } - } - - /// - /// 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) - { - DeleteContentOfTypes(new[] {contentTypeId}, userId); - } - - /// - /// 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) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var deleteRevisionsEventArgs = new DeleteRevisionsEventArgs(id, dateToRetain: versionDate); - if (uow.Events.DispatchCancelable(DeletingVersions, this, deleteRevisionsEventArgs, "DeletingVersions")) - { - uow.Commit(); - return; - } - - var repository = RepositoryFactory.CreateContentRepository(uow); - repository.DeleteVersions(id, versionDate); - deleteRevisionsEventArgs.CanCancel = false; - uow.Events.Dispatch(DeletedVersions, this, deleteRevisionsEventArgs, "DeletedVersions"); - - Audit(uow, AuditType.Delete, "Delete Content by version date performed by user", userId, Constants.System.Root); - uow.Commit(); - } - } - - /// - /// 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)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - if (uow.Events.DispatchCancelable(DeletingVersions, this, new DeleteRevisionsEventArgs(id, specificVersion: versionId), "DeletingVersions")) - { - uow.Commit(); - return; - } - - if (deletePriorVersions) - { - var content = GetByVersion(versionId); - DeleteVersions(id, content.UpdateDate, userId); - } - - var repository = RepositoryFactory.CreateContentRepository(uow); - repository.DeleteVersion(versionId); - - uow.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), "DeletedVersions"); - - Audit(uow, AuditType.Delete, "Delete Content by version performed by user", userId, Constants.System.Root); - uow.Commit(); - } - } - } - - /// - /// 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; - } - - using (var uow = UowProvider.GetUnitOfWork()) - { - var moveEventInfo = new MoveEventInfo(content, content.Path, parentId); - var moveEventArgs = new MoveEventArgs(moveEventInfo); - if (uow.Events.DispatchCancelable(Moving, this, moveEventArgs, "Moving")) - { - uow.Commit(); - 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); - - moveEventArgs.MoveInfoCollection = moveInfo; - moveEventArgs.CanCancel = false; - uow.Events.Dispatch(Moved, this, moveEventArgs, "Moved"); - - Audit(uow, AuditType.Move, "Move Content performed by user", userId, content.Id); - uow.Commit(); - } - } - } - - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")] - public void EmptyRecycleBin() => EmptyRecycleBin(0); - - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - /// Optional Id of the User emptying the Recycle Bin - public void EmptyRecycleBin(int userId = 0) - { - using (new WriteLock(Locker)) - { - var nodeObjectType = Constants.ObjectTypes.DocumentGuid; - - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - //Create a dictionary of ids -> dictionary of property aliases + values - var entities = repository.GetEntitiesInRecycleBin() - .ToDictionary( - key => key.Id, - val => (IEnumerable)val.Properties); - - var files = ((ContentRepository)repository).GetFilesInRecycleBinForUploadField(); - - var recycleBinEventArgs = new RecycleBinEventArgs(nodeObjectType, entities, files); - if (uow.Events.DispatchCancelable(EmptyingRecycleBin, this, recycleBinEventArgs)) - { - uow.Commit(); - return; - } - - var success = repository.EmptyRecycleBin(); - recycleBinEventArgs.CanCancel = false; - recycleBinEventArgs.RecycleBinEmptiedSuccessfully = success; - uow.Events.Dispatch(EmptiedRecycleBin, this, recycleBinEventArgs); - - Audit(uow, AuditType.Delete, "Empty Content Recycle Bin performed by user", userId, Constants.System.RecycleBinContent); - uow.Commit(); - } - } - } - - /// - /// 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); - - using (var uow = UowProvider.GetUnitOfWork()) - { - var copyEventArgs = new CopyEventArgs(content, copy, true, parentId, relateToOriginal); - if (uow.Events.DispatchCancelable(Copying, this, copyEventArgs)) - { - uow.Commit(); - return null; - } - - var repository = RepositoryFactory.CreateContentRepository(uow); - - // Update the create author and last edit author - copy.CreatorId = userId; - copy.WriterId = userId; - - //get the current permissions, if there are any explicit ones they need to be copied - var currentPermissions = GetPermissionsForEntity(content); - currentPermissions.RemoveWhere(p => p.IsDefaultPermissions); - - repository.AddOrUpdate(copy); - repository.AddOrUpdatePreviewXml(copy, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - - //add permissions - if (currentPermissions.Count > 0) - { - var permissionSet = new ContentPermissionSet(copy, currentPermissions); - repository.AddOrUpdatePermissions(permissionSet); - } - - uow.Commit(); // todo - this should flush, not 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 - }); - } - uow.Commit(); // todo - this should flush, not commit - - 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); - } - } - copyEventArgs.CanCancel = false; - uow.Events.Dispatch(Copied, this, copyEventArgs); - Audit(uow, AuditType.Copy, "Copy Content performed by user", userId, content.Id); - uow.Commit(); - } - - 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) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var sendToPublishEventArgs = new SendToPublishEventArgs(content); - if (uow.Events.DispatchCancelable(SendingToPublish, this, sendToPublishEventArgs)) - { - uow.Commit(); - return false; - } - - //Save before raising event - Save(content, userId); - sendToPublishEventArgs.CanCancel = false; - uow.Events.Dispatch(SentToPublish, this, sendToPublishEventArgs); - - Audit(uow, AuditType.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id); - uow.Commit(); - - 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); - - using (var uow = UowProvider.GetUnitOfWork()) - { - var rollbackEventArgs = new RollbackEventArgs(content); - if (uow.Events.DispatchCancelable(RollingBack, this, rollbackEventArgs)) - { - uow.Commit(); - return content; - } - - var repository = RepositoryFactory.CreateContentRepository(uow); - - content.WriterId = userId; - content.CreatorId = userId; - content.ChangePublishedState(PublishedState.Unpublished); - - repository.AddOrUpdate(content); - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - rollbackEventArgs.CanCancel = false; - uow.Events.Dispatch(RolledBack, this, rollbackEventArgs); - - Audit(uow, AuditType.RollBack, "Content rollback performed by user", content.WriterId, content.Id); - uow.Commit(); - } - - 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) - { - var shouldBePublished = new List(); - var shouldBeSaved = new List(); - - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var asArray = items.ToArray(); - var saveEventArgs = new SaveEventArgs(asArray); - if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) - { - uow.Commit(); - return false; - } - - var repository = RepositoryFactory.CreateContentRepository(uow); - - var 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(uow, content, userId).Success; - 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)); - } - - if (raiseEvents) - { - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); - } - - 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(uow, shouldBePublished, false); - } - - Audit(uow, AuditType.Sort, "Sorting content performed by user", userId, 0); - uow.Commit(); - } - } - - return true; - } - - /// - /// Sorts a collection of objects by updating the SortOrder according - /// to the ordering of node Ids 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(int[] ids, int userId = 0, bool raiseEvents = true) - { - var shouldBePublished = new List(); - var shouldBeSaved = new List(); - - using (new WriteLock(Locker)) - { - var allContent = GetByIds(ids).ToDictionary(x => x.Id, x => x); - if (allContent.Any() == false) - { - return false; - } - var items = ids.Select(x => allContent[x]).ToArray(); - - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(items); - if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) - { - uow.Commit(); - return false; - } - - var repository = RepositoryFactory.CreateContentRepository(uow); - - var i = 0; - foreach (var content in items) - { - //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(uow, content, userId).Success; - 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)); - } - - if (raiseEvents) - { - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); - } - - 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(uow, shouldBePublished, false); - } - - Audit(uow, AuditType.Sort, "Sort child items performed by user", userId, items.First().ParentId); - uow.Commit(); - } - } - - return true; - } - - public IEnumerable GetBlueprintsForContentTypes(params int[] documentTypeIds) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); - - var query = new Query(); - if (documentTypeIds.Length > 0) - { - query.Where(x => documentTypeIds.Contains(x.ContentTypeId)); - } - return repository.GetByQuery(query).Select(x => - { - ((Content) x).IsBlueprint = true; - return x; - }); - } - } - - /// - /// Gets paged content descendants as XML by path - /// - /// Path starts with - /// Page number - /// Page size - /// Total records the query would return without paging - /// A paged enumerable of XML entries of content items - public IEnumerable GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords) - { - Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); - Mandate.ParameterCondition(pageSize > 0, "pageSize"); - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, - //This order by is VERY important! This allows us to figure out what is implicitly not published, see ContentRepository.BuildXmlCache and - // UmbracoContentIndexer.PerformIndexAll which uses the logic based on this sort order - new[] { "level", "parentID", "sortOrder" }, - out totalRecords); - return contents; - } - } - - /// - /// This builds the Xml document used for the XML cache - /// - /// - public XmlDocument BuildXmlCache() - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var result = repository.BuildXmlCache(); - uow.Commit(); - return result; - } - } - - public XmlDocument BuildPreviewXmlCache() - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - var result = repository.BuildPreviewXmlCache(); - uow.Commit(); - return result; - } - } - - /// - /// Rebuilds all xml content in the cmsContentXml table for all documents - /// - /// - /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures - /// for all content - /// - public void RebuildXmlStructures(params int[] contentTypeIds) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - repository.RebuildXmlStructures( - content => _entitySerializer.Serialize(this, _dataTypeService, _userService, content), - contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds); - - Audit(uow, AuditType.Publish, "ContentService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, Constants.System.Root); - uow.Commit(); - } - } - - #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 uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - - var query = Query.Builder.Where(x => x.Id != content.Id && x.Path.StartsWith(content.Path) && x.Trashed == false); - return repository.GetByPublishedVersion(query); - } - } - - #endregion - - #region Private Methods - - /// - /// Hack: This is used to fix some data if an entity's properties are invalid/corrupt - /// - /// - private void QuickUpdate(IContent content) - { - if (content == null) throw new ArgumentNullException("content"); - if (content.HasIdentity == false) throw new InvalidOperationException("Cannot update an entity without an Identity"); - - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateContentRepository(uow); - repository.AddOrUpdate(content); - uow.Commit(); - } - } - - private void Audit(IScopeUnitOfWork uow, AuditType type, string message, int userId, int objectId) - { - var auditRepo = RepositoryFactory.CreateAuditRepository(uow); - auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); - } - - //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)); - uow.Commit(); - } - } - } - 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)) - { - //Hack: this ensures that the entity's path is valid and if not it fixes/persists it - //see: http://issues.umbraco.org/issue/U4-9336 - content.EnsureValidPath(Logger, entity => GetById(entity.ParentId), QuickUpdate); - - 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; - - using (var uow = UowProvider.GetUnitOfWork()) - { - //Publish and then update the database with new status - var publishedOutcome = internalStrategy.PublishWithChildren(uow, 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 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); - } - - //Save xml to db and call following method to fire event: - _publishingStrategy.PublishingFinalized(uow, updated, false); - - Audit(uow, AuditType.Publish, "Publish with Children performed by user", userId, content.Id); - uow.Commit(); - - 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 - } - - using (var uow = UowProvider.GetUnitOfWork()) - { - var unpublished = _publishingStrategy.UnPublish(uow, content, userId); - if (unpublished == false) - { - uow.Commit(); - return Attempt.Fail(new UnPublishStatus(content, UnPublishedStatusType.FailedCancelledByEvent, evtMsgs)); - } - - 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); - - //Delete xml from db? and call following method to fire event through PublishingStrategy to update cache - if (omitCacheRefresh == false) - _publishingStrategy.UnPublishingFinalized(uow, content); - - Audit(uow, AuditType.UnPublish, "UnPublish performed by user", userId, content.Id); - uow.Commit(); - } - - 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(); - - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(content, evtMsgs); - if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) - { - uow.Commit(); - return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent, evtMsgs)); - } - - //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) - { - //Publish and then update the database with new status - var publishResult = _publishingStrategy.Publish(uow, 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 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); - repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - - if (published) - { - //Content Xml - repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); - } - - if (raiseEvents) - { - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); - } - - //Save xml to db and call following method to fire event through PublishingStrategy to update cache - if (published) - { - _publishingStrategy.PublishingFinalized(uow, 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)) - { - //TODO: Horrible for performance if there are lots of descendents! We should page if anything but this is crazy - var descendants = GetPublishedDescendants(content); - _publishingStrategy.PublishingFinalized(uow, descendants, false); - } - - uow.Commit(); - - if (publishStatus.StatusType == PublishStatusType.Success) - { - Audit(uow, AuditType.Publish, "Save and Publish performed by user", userId, content.Id); - } - else - { - Audit(uow, AuditType.Save, "Save performed by user", userId, content.Id); - } - uow.Commit(); - - 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(); - - using (new WriteLock(Locker)) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(content, evtMsgs); - if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) - { - uow.Commit(); - return OperationStatus.Cancelled(evtMsgs); - } - - if (string.IsNullOrWhiteSpace(content.Name)) - { - throw new ArgumentException("Cannot save content with empty name."); - } - - 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)); - - if (raiseEvents) - { - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); - } - - Audit(uow, AuditType.Save, "Save Content performed by user", userId, content.Id); - uow.Commit(); - } - - return OperationStatus.Success(evtMsgs); - } - } - - 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; - } - - 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; - } - - if (content.ReleaseDate.HasValue && content.ReleaseDate.Value > DateTime.MinValue && content.ReleaseDate.Value > DateTime.Now) - { - Logger.Info( - string.Format( - "Content '{0}' with Id '{1}' is awaiting release and could not be published.", - content.Name, content.Id)); - return PublishStatusType.FailedAwaitingRelease; - } - - return PublishStatusType.Success; - } - - private PublishStatusType CheckAndLogIsValid(IContent content) - { - //Content contains invalid property values and can therefore not be published - fire event? - if (content.IsValid() == false) - { - Logger.Info( - string.Format( - "Content '{0}' with Id '{1}' could not be published because of invalid properties.", - content.Name, content.Id)); - return PublishStatusType.FailedContentInvalid; - } - - return PublishStatusType.Success; - } - - private IContentType FindContentTypeByAlias(string contentTypeAlias) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateContentTypeRepository(uow); - - 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; - - /// - /// Occurs after a blueprint has been saved. - /// - public static event TypedEventHandler> SavedBlueprint; - - /// - /// Occurs after a blueprint has been deleted. - /// - public static event TypedEventHandler> DeletedBlueprint; - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Xml; +using System.Xml.Linq; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Publishing; + +namespace Umbraco.Core.Services +{ + /// + /// Represents the Content Service, which is an easy access to operations involving + /// + public class ContentService : ScopeRepositoryService, IContentService, IContentServiceOperations + { + private readonly IPublishingStrategy2 _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, + IDataTypeService dataTypeService, + IUserService userService) + : base(provider, repositoryFactory, logger, eventMessagesFactory) + { + if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); + if (userService == null) throw new ArgumentNullException("userService"); + _publishingStrategy = new PublishingStrategy(UowProvider.ScopeProvider, eventMessagesFactory, logger); + _dataTypeService = dataTypeService; + _userService = userService; + } + + #region Static Queries + + private IQuery _notTrashedQuery; + + #endregion + + public int CountPublished(string contentTypeAlias = null) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.CountPublished(contentTypeAlias); + } + } + + public int Count(string contentTypeAlias = null) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.Count(contentTypeAlias); + } + } + + public int CountChildren(int parentId, string contentTypeAlias = null) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.CountChildren(parentId, contentTypeAlias); + } + } + + public int CountDescendants(int parentId, string contentTypeAlias = null) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + 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) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + repository.ReplaceContentPermissions(permissionSet); + uow.Commit(); + } + } + + /// + /// Assigns a single permission to the current content item for the specified group ids + /// + /// + /// + /// + public void AssignContentPermission(IContent entity, char permission, IEnumerable groupIds) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + repository.AssignEntityPermission(entity, permission, groupIds); + uow.Commit(); + } + } + + /// + /// Returns implicit/inherited permissions assigned to the content item for all user groups + /// + /// + /// + public EntityPermissionCollection GetPermissionsForEntity(IContent content) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + 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, Guid parentId, string contentTypeAlias, int userId = 0) + { + var parent = GetById(parentId); + return CreateContent(name, parent, contentTypeAlias, userId); + } + + /// + /// 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); + + using (var uow = UowProvider.GetUnitOfWork()) + { + var newEventArgs = new NewEventArgs(content, contentTypeAlias, parentId); + if (uow.Events.DispatchCancelable(Creating, this, newEventArgs)) + { + uow.Commit(); + content.WasCancelled = true; + return content; + } + + content.CreatorId = userId; + content.WriterId = userId; + newEventArgs.CanCancel = false; + uow.Events.Dispatch(Created, this, newEventArgs); + + 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); + + using (var uow = UowProvider.GetUnitOfWork()) + { + var newEventArgs = new NewEventArgs(content, contentTypeAlias, parent); + if (uow.Events.DispatchCancelable(Creating, this, newEventArgs)) + { + uow.Commit(); + content.WasCancelled = true; + return content; + } + + content.CreatorId = userId; + content.WriterId = userId; + newEventArgs.CanCancel = false; + uow.Events.Dispatch(Created, this, newEventArgs); + + uow.Commit(); + } + + 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); + + using (var uow = UowProvider.GetUnitOfWork()) + { + //NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found + // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. + var newEventArgs = new NewEventArgs(content, contentTypeAlias, parentId); + if (uow.Events.DispatchCancelable(Creating, this, newEventArgs)) + { + uow.Commit(); + content.WasCancelled = true; + return content; + } + + var saveEventArgs = new SaveEventArgs(content); + if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) + { + uow.Commit(); + content.WasCancelled = true; + return content; + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + content.CreatorId = userId; + content.WriterId = userId; + + repository.AddOrUpdate(content); + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + newEventArgs.CanCancel = false; + uow.Events.Dispatch(Created, this, newEventArgs); + + Audit(uow, AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id); + uow.Commit(); + } + + return content; + } + + public IList GetAnchorValuesFromRTEs(int id) + { + var result = new List(); + + var content = GetById(id); + + foreach (var contentProperty in content.Properties) + { + if (string.Equals(contentProperty.PropertyType.PropertyEditorAlias, Constants.PropertyEditors.TinyMCEAlias)) + { + var value = contentProperty.Value.ToString(); + + result.AddRange(GetAnchorValuesFromRTEContent(value)); + } + } + + + return result; + } + + public IList GetAnchorValuesFromRTEContent(string rteContent) + { + var result = new List(); + var regex = new Regex(""); + var matches = regex.Matches(rteContent); + + foreach (Match match in matches) + { + result.Add(match.Value.Split('\"')[1]); + } + + return result; + } + + /// + /// 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); + + using (var uow = UowProvider.GetUnitOfWork()) + { + //NOTE: I really hate the notion of these Creating/Created events - they are so inconsistent, I've only just found + // out that in these 'WithIdentity' methods, the Saving/Saved events were not fired, wtf. Anyways, they're added now. + var newEventArgs = new NewEventArgs(content, contentTypeAlias, parent); + if (uow.Events.DispatchCancelable(Creating, this, newEventArgs)) + { + uow.Commit(); + content.WasCancelled = true; + return content; + } + + var saveEventArgs = new SaveEventArgs(content); + if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) + { + uow.Commit(); + content.WasCancelled = true; + return content; + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + content.CreatorId = userId; + content.WriterId = userId; + + repository.AddOrUpdate(content); + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + newEventArgs.CanCancel = false; + uow.Events.Dispatch(Created, this, newEventArgs); + + Audit(uow, AuditType.New, string.Format("Content '{0}' was created with Id {1}", name, content.Id), content.CreatorId, content.Id); + uow.Commit(); + } + + return content; + } + + /// + /// Gets an object by Id + /// + /// Id of the Content to retrieve + /// + public IContent GetById(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.Get(id); + } + } + + /// + /// Gets objects by Ids + /// + /// Ids of the Content to retrieve + /// + public IEnumerable GetByIds(IEnumerable ids) + { + var idsArray = ids.ToArray(); + if (idsArray.Length == 0) return Enumerable.Empty(); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + // ensure that the result has the order based on the ids passed in + var result = repository.GetAll(idsArray); + var content = result.ToDictionary(x => x.Id, x => x); + + var sortedResult = idsArray.Select(x => + { + IContent c; + return content.TryGetValue(x, out c) ? c : null; + }).WhereNotNull(); + + return sortedResult; + } + } + + /// + /// Gets objects by Ids + /// + /// Ids of the Content to retrieve + /// + public IEnumerable GetByIds(IEnumerable ids) + { + var idsArray = ids.ToArray(); + if (idsArray.Length == 0) return Enumerable.Empty(); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + // ensure that the result has the order based on the ids passed in + var result = repository.GetAll(idsArray); + var content = result.ToDictionary(x => x.Key, x => x); + + var sortedResult = idsArray.Select(x => + { + IContent c; + return content.TryGetValue(x, out c) ? c : null; + }).WhereNotNull(); + + return sortedResult; + } + } + + /// + /// Gets an object by its 'UniqueId' + /// + /// Guid key of the Content to retrieve + /// + public IContent GetById(Guid key) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.Get(key); + } + } + + /// + /// 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.ContentTypeId == id); + return repository.GetByQuery(query); + } + } + + internal IEnumerable GetPublishedContentOfContentType(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.ContentTypeId == id); + return repository.GetByPublishedVersion(query); + } + } + + /// + /// 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.Level == level && x.Path.StartsWith(Constants.System.RecycleBinContent.ToInvariantString()) == false); + return repository.GetByQuery(query); + } + } + + /// + /// Gets a specific version of an item. + /// + /// Id of the version to retrieve + /// An item + public IContent GetByVersion(Guid versionId) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.GetAllVersions(id); + } + } + + /// + /// Gets a list of all version Ids for the given content item ordered so latest is first + /// + /// + /// The maximum number of rows to return + /// + public IEnumerable GetVersionIds(int id, int maxRows) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.GetVersionIds(id, maxRows); + } + } + + /// + /// 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.ParentId == id); + return repository.GetByQuery(query).OrderBy(x => x.SortOrder); + } + } + + [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 = "") + { + long total; + var result = GetPagedChildren(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); + totalChildren = Convert.ToInt32(total); + 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 + /// + /// 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, string filter) + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + var query = Query.Builder; + // always check for a parent - else it will also get decendants (and then you should use the GetPagedDescendants method) + query.Where(x => x.ParentId == id); + + IQuery filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) + { + filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); + } + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filterQuery); + } + } + + [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 = "") + { + long total; + var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); + totalChildren = Convert.ToInt32(total); + 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 + /// + /// 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, Direction orderDirection, bool orderBySystemField, string filter) + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + // get query - if the id is System Root, then just get all + var query = Query.Builder; + if (id != Constants.System.Root) + { + var entityRepository = RepositoryFactory.CreateEntityRepository(uow); + var contentPath = entityRepository.GetAllPaths(Constants.ObjectTypes.DocumentGuid, id).ToArray(); + if (contentPath.Length == 0) + { + totalChildren = 0; + return Enumerable.Empty(); + } + query.Where(x => x.Path.SqlStartsWith(string.Format("{0},", contentPath[0].Path), TextColumnType.NVarchar)); + } + + + // get filter + IQuery filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) + filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); + + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filterQuery); + } + } + + /// + /// 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 filter + /// An Enumerable list of objects + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + // get query - if the id is System Root, then just get all + var query = Query.Builder; + if (id != Constants.System.Root) + { + var entityRepository = RepositoryFactory.CreateEntityRepository(uow); + var contentPath = entityRepository.GetAllPaths(Constants.ObjectTypes.DocumentGuid, id).ToArray(); + if (contentPath.Length == 0) + { + totalChildren = 0; + return Enumerable.Empty(); + } + query.Where(x => x.Path.SqlStartsWith(string.Format("{0},", contentPath[0].Path), TextColumnType.NVarchar)); + } + + return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + } + } + + /// + /// 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + var query = Query.Builder.Where(x => x.ParentId == parentId && x.Name.Contains(name)); + return repository.GetByQuery(query); + } + } + + /// + /// 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); + return content == null ? Enumerable.Empty() : 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) + { + //This is a check to ensure that the path is correct for this entity to avoid problems like: http://issues.umbraco.org/issue/U4-9336 due to data corruption + if (content.ValidatePath() == false) + throw new InvalidDataException(string.Format("The content item {0} has an invalid path: {1} with parentID: {2}", content.Id, content.Path, content.ParentId)); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + var pathMatch = content.Path + ","; + var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != content.Id); + return repository.GetByQuery(query); + } + } + + /// + /// 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); + } + + /// + /// 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + var query = Query.Builder.Where(x => x.ParentId == Constants.System.Root); + return repository.GetByQuery(query); + } + } + + /// + /// Gets all published content items + /// + /// + internal IEnumerable GetAllPublished() + { + //create it once if it is needed (no need for locking here) + if (_notTrashedQuery == null) + { + _notTrashedQuery = Query.Builder.Where(x => x.Trashed == false); + } + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.GetByPublishedVersion(_notTrashedQuery); + } + } + + /// + /// 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.ExpireDate <= DateTime.Now); + return repository.GetByQuery(query).Where(x => x.HasPublishedVersion); + } + } + + /// + /// 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.Published == false && x.ReleaseDate <= DateTime.Now); + return repository.GetByQuery(query); + } + } + + /// + /// Gets a collection of an objects, which resides in the Recycle Bin + /// + /// An Enumerable list of objects + public IEnumerable GetContentInRecycleBin() + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.Path.StartsWith(Constants.System.RecycleBinContentPathPrefix)); + return repository.GetByQuery(query); + } + } + + /// + /// 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.ParentId == id); + return repository.Count(query); + } + } + + /// + /// 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 uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var query = Query.Builder.Where(x => x.Published == true && x.Id == id && x.Trashed == false); + return repository.Count(query) > 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) + { + int[] ids; + if (content.HasIdentity) + { + // get ids from path (we have identity) + // skip the first one that has to be -1 - and we don't care + // skip the last one that has to be "this" - and it's ok to stop at the parent + ids = content.Path.Split(',').Skip(1).SkipLast().Select(int.Parse).ToArray(); + } + else + { + // no path yet (no identity), have to move up to parent + // skip the first one that has to be -1 - and we don't care + // don't skip the last one that is "parent" + var parent = GetById(content.ParentId); + if (parent == null) return false; + ids = parent.Path.Split(',').Skip(1).Select(int.Parse).ToArray(); + } + if (ids.Length == 0) + return false; + + // if the first one is recycle bin, fail fast + if (ids[0] == Constants.System.RecycleBinContent) + return false; + + // fixme - move to repository? + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var sql = new Sql(@" + SELECT id + FROM umbracoNode + JOIN cmsDocument ON umbracoNode.id=cmsDocument.nodeId AND cmsDocument.published=@0 + WHERE umbracoNode.trashed=@1 AND umbracoNode.id IN (@2)", + true, false, ids); + var x = uow.Database.Fetch(sql); + return ids.Length == x.Count; + } + } + + /// + /// 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) + { + return MoveToRecycleBinDo(content, userId, false); + } + + /// + /// 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 + /// + /// A boolean indicating to ignore this item's descendant list from also being moved to the recycle bin. This is required for the DeleteContentOfTypes method + /// because it has already looked up all descendant nodes that will need to be recycled + /// TODO: Fix all of this, it will require a reasonable refactor and most of this stuff should be done at the repo level instead of service sub operations + /// + private Attempt MoveToRecycleBinDo(IContent content, int userId, bool ignoreDescendants) + { + var evtMsgs = EventMessagesFactory.Get(); + using (new WriteLock(Locker)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + //Hack: this ensures that the entity's path is valid and if not it fixes/persists it + //see: http://issues.umbraco.org/issue/U4-9336 + content.EnsureValidPath(Logger, entity => GetById(entity.ParentId), QuickUpdate); + var originalPath = content.Path; + var moveEventInfo = new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent); + var moveEventArgs = new MoveEventArgs(evtMsgs, moveEventInfo); + if (uow.Events.DispatchCancelable(Trashing, this, moveEventArgs, "Trashing")) + { + uow.Commit(); + return OperationStatus.Cancelled(evtMsgs); + } + var moveInfo = new List> + { + moveEventInfo + }; + + //get descendents to process of the content item that is being moved to trash - must be done before changing the state below + //must be processed with shallowest levels first + var descendants = ignoreDescendants ? Enumerable.Empty() : GetDescendants(content).OrderBy(x => x.Level); + + //Do the updates for this item + var repository = RepositoryFactory.CreateContentRepository(uow); + //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); + } + 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) + { + //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); + descendant.WriterId = userId; + descendant.ChangeTrashedState(true, descendant.ParentId); + repository.AddOrUpdate(descendant); + + moveInfo.Add(new MoveEventInfo(descendant, descendant.Path, descendant.ParentId)); + } + + moveEventArgs.CanCancel = false; + moveEventArgs.MoveInfoCollection = moveInfo; + uow.Events.Dispatch(Trashed, this, moveEventArgs, "Trashed"); + + Audit(uow, AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id); + uow.Commit(); + } + + 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) + { + var attempt = ((IContentServiceOperations)this).UnPublish(content, userId); + LogHelper.Debug(string.Format("Result of unpublish attempt: {0}", attempt.Result.StatusType)); + return attempt.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); + } + + public IContent GetBlueprintById(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); + var blueprint = repository.Get(id); + if (blueprint != null) + ((Content) blueprint).IsBlueprint = true; + return blueprint; + } + } + + public IContent GetBlueprintById(Guid id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); + var blueprint = repository.Get(id); + if (blueprint != null) + ((Content)blueprint).IsBlueprint = true; + return blueprint; + } + } + + public void SaveBlueprint(IContent content, int userId = 0) + { + //always ensure the blueprint is at the root + if (content.ParentId != -1) + content.ParentId = -1; + + ((Content) content).IsBlueprint = true; + + using (new WriteLock(Locker)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + if (string.IsNullOrWhiteSpace(content.Name)) + { + throw new ArgumentException("Cannot save content blueprint with empty name."); + } + + var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); + + if (content.HasIdentity == false) + { + content.CreatorId = userId; + } + content.WriterId = userId; + + repository.AddOrUpdate(content); + + uow.Events.Dispatch(SavedBlueprint, this, new SaveEventArgs(content), "SavedBlueprint"); + + uow.Commit(); + } + } + } + + public void DeleteBlueprint(IContent content, int userId = 0) + { + using (new WriteLock(Locker)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); + repository.Delete(content); + uow.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(content), "DeletedBlueprint"); + uow.Commit(); + } + } + } + + public IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = 0) + { + if (blueprint == null) throw new ArgumentNullException("blueprint"); + + var contentType = blueprint.ContentType; + var content = new Content(name, -1, contentType); + content.Path = string.Concat(content.ParentId.ToString(), ",", content.Id); + + content.CreatorId = userId; + content.WriterId = userId; + + foreach (var property in blueprint.Properties) + content.SetValue(property.Alias, property.Value); + + return content; + } + + public void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = 0) + { + using (new WriteLock(Locker)) + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); + + var contentTypeIdsA = contentTypeIds.ToArray(); + var query = new Query(); + if (contentTypeIdsA.Length > 0) + { + query.Where(x => contentTypeIdsA.Contains(x.ContentTypeId)); + } + var blueprints = repository.GetByQuery(query).Select(x => + { + ((Content) x).IsBlueprint = true; + return x; + }).ToArray(); + + foreach (var blueprint in blueprints) + { + repository.Delete(blueprint); + } + + uow.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(blueprints), "DeletedBlueprint"); + uow.Commit(); + } + } + + public void DeleteBlueprintsOfType(int contentTypeId, int userId = 0) + { + DeleteBlueprintsOfTypes(new[] {contentTypeId}, 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. + 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(); + + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(asArray, evtMsgs); + if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) + { + uow.Commit(); + return OperationStatus.Cancelled(evtMsgs); + } + + // todo - understand what's a lock in a scope? + // (though, these locks are refactored in v8) + using (new WriteLock(Locker)) + { + var containsNew = asArray.Any(x => x.HasIdentity == false); + 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)); + } + } + } + + if (raiseEvents) + { + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + } + + Audit(uow, AuditType.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, Constants.System.Root); + uow.Commit(); + + 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)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var deleteEventArgs = new DeleteEventArgs(content, evtMsgs); + if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs, "Deleting")) + { + uow.Commit(); + 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 repository = RepositoryFactory.CreateContentRepository(uow); + + repository.Delete(content); + + deleteEventArgs.CanCancel = false; + uow.Events.Dispatch(Deleted, this, deleteEventArgs, "Deleted"); + + Audit(uow, AuditType.Delete, "Delete Content performed by user", userId, content.Id); + uow.Commit(); + } + + 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 the specified types. All Descendants of deleted content that is not of these types is moved to Recycle Bin. + /// + /// Id of the + /// Optional Id of the user issueing the delete operation + public void DeleteContentOfTypes(IEnumerable contentTypeIds, int userId = 0) + { + using (new WriteLock(Locker)) + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + //track the 'root' items of the collection of nodes discovered to delete, we need to use + //these items to lookup descendants that are not of this doc type so they can be transfered + //to the recycle bin + IDictionary rootItems; + var contentToDelete = this.TrackDeletionsForDeleteContentOfTypes(contentTypeIds, repository, out rootItems).ToArray(); + + if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(contentToDelete), "Deleting")) + { + uow.Commit(); + return; + } + + //Determine the items that will need to be recycled (that are children of these content items but not of these content types) + var contentToRecycle = this.TrackTrashedForDeleteContentOfTypes(contentTypeIds, rootItems, repository); + + //move each item to the bin starting with the deepest items + foreach (var child in contentToRecycle.OrderByDescending(x => x.Level)) + { + MoveToRecycleBinDo(child, userId, true); + } + + foreach (var content in contentToDelete) + { + Delete(content, userId); + } + + Audit(uow, AuditType.Delete, + string.Format("Delete Content of Types {0} performed by user", string.Join(",", contentTypeIds)), + userId, Constants.System.Root); + uow.Commit(); + } + } + + /// + /// 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) + { + DeleteContentOfTypes(new[] {contentTypeId}, userId); + } + + /// + /// 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) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var deleteRevisionsEventArgs = new DeleteRevisionsEventArgs(id, dateToRetain: versionDate); + if (uow.Events.DispatchCancelable(DeletingVersions, this, deleteRevisionsEventArgs, "DeletingVersions")) + { + uow.Commit(); + return; + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + repository.DeleteVersions(id, versionDate); + deleteRevisionsEventArgs.CanCancel = false; + uow.Events.Dispatch(DeletedVersions, this, deleteRevisionsEventArgs, "DeletedVersions"); + + Audit(uow, AuditType.Delete, "Delete Content by version date performed by user", userId, Constants.System.Root); + uow.Commit(); + } + } + + /// + /// 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)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + if (uow.Events.DispatchCancelable(DeletingVersions, this, new DeleteRevisionsEventArgs(id, specificVersion: versionId), "DeletingVersions")) + { + uow.Commit(); + return; + } + + if (deletePriorVersions) + { + var content = GetByVersion(versionId); + DeleteVersions(id, content.UpdateDate, userId); + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + repository.DeleteVersion(versionId); + + uow.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false, specificVersion: versionId), "DeletedVersions"); + + Audit(uow, AuditType.Delete, "Delete Content by version performed by user", userId, Constants.System.Root); + uow.Commit(); + } + } + } + + /// + /// 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; + } + + using (var uow = UowProvider.GetUnitOfWork()) + { + var moveEventInfo = new MoveEventInfo(content, content.Path, parentId); + var moveEventArgs = new MoveEventArgs(moveEventInfo); + if (uow.Events.DispatchCancelable(Moving, this, moveEventArgs, "Moving")) + { + uow.Commit(); + 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); + + moveEventArgs.MoveInfoCollection = moveInfo; + moveEventArgs.CanCancel = false; + uow.Events.Dispatch(Moved, this, moveEventArgs, "Moved"); + + Audit(uow, AuditType.Move, "Move Content performed by user", userId, content.Id); + uow.Commit(); + } + } + } + + /// + /// Empties the Recycle Bin by deleting all that resides in the bin + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")] + public void EmptyRecycleBin() => EmptyRecycleBin(0); + + /// + /// Empties the Recycle Bin by deleting all that resides in the bin + /// + /// Optional Id of the User emptying the Recycle Bin + public void EmptyRecycleBin(int userId = 0) + { + using (new WriteLock(Locker)) + { + var nodeObjectType = Constants.ObjectTypes.DocumentGuid; + + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + //Create a dictionary of ids -> dictionary of property aliases + values + var entities = repository.GetEntitiesInRecycleBin() + .ToDictionary( + key => key.Id, + val => (IEnumerable)val.Properties); + + var files = ((ContentRepository)repository).GetFilesInRecycleBinForUploadField(); + + var recycleBinEventArgs = new RecycleBinEventArgs(nodeObjectType, entities, files); + if (uow.Events.DispatchCancelable(EmptyingRecycleBin, this, recycleBinEventArgs)) + { + uow.Commit(); + return; + } + + var success = repository.EmptyRecycleBin(); + recycleBinEventArgs.CanCancel = false; + recycleBinEventArgs.RecycleBinEmptiedSuccessfully = success; + uow.Events.Dispatch(EmptiedRecycleBin, this, recycleBinEventArgs); + + Audit(uow, AuditType.Delete, "Empty Content Recycle Bin performed by user", userId, Constants.System.RecycleBinContent); + uow.Commit(); + } + } + } + + /// + /// 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); + + using (var uow = UowProvider.GetUnitOfWork()) + { + var copyEventArgs = new CopyEventArgs(content, copy, true, parentId, relateToOriginal); + if (uow.Events.DispatchCancelable(Copying, this, copyEventArgs)) + { + uow.Commit(); + return null; + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + + // Update the create author and last edit author + copy.CreatorId = userId; + copy.WriterId = userId; + + //get the current permissions, if there are any explicit ones they need to be copied + var currentPermissions = GetPermissionsForEntity(content); + currentPermissions.RemoveWhere(p => p.IsDefaultPermissions); + + repository.AddOrUpdate(copy); + repository.AddOrUpdatePreviewXml(copy, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + + //add permissions + if (currentPermissions.Count > 0) + { + var permissionSet = new ContentPermissionSet(copy, currentPermissions); + repository.AddOrUpdatePermissions(permissionSet); + } + + uow.Commit(); // todo - this should flush, not 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 + }); + } + uow.Commit(); // todo - this should flush, not commit + + 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); + } + } + copyEventArgs.CanCancel = false; + uow.Events.Dispatch(Copied, this, copyEventArgs); + Audit(uow, AuditType.Copy, "Copy Content performed by user", userId, content.Id); + uow.Commit(); + } + + 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) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var sendToPublishEventArgs = new SendToPublishEventArgs(content); + if (uow.Events.DispatchCancelable(SendingToPublish, this, sendToPublishEventArgs)) + { + uow.Commit(); + return false; + } + + //Save before raising event + Save(content, userId); + sendToPublishEventArgs.CanCancel = false; + uow.Events.Dispatch(SentToPublish, this, sendToPublishEventArgs); + + Audit(uow, AuditType.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id); + uow.Commit(); + + 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); + + using (var uow = UowProvider.GetUnitOfWork()) + { + var rollbackEventArgs = new RollbackEventArgs(content); + if (uow.Events.DispatchCancelable(RollingBack, this, rollbackEventArgs)) + { + uow.Commit(); + return content; + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + + content.WriterId = userId; + content.CreatorId = userId; + content.ChangePublishedState(PublishedState.Unpublished); + + repository.AddOrUpdate(content); + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + rollbackEventArgs.CanCancel = false; + uow.Events.Dispatch(RolledBack, this, rollbackEventArgs); + + Audit(uow, AuditType.RollBack, "Content rollback performed by user", content.WriterId, content.Id); + uow.Commit(); + } + + 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) + { + var shouldBePublished = new List(); + var shouldBeSaved = new List(); + + using (new WriteLock(Locker)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var asArray = items.ToArray(); + var saveEventArgs = new SaveEventArgs(asArray); + if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) + { + uow.Commit(); + return false; + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + + var 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(uow, content, userId).Success; + 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)); + } + + if (raiseEvents) + { + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + } + + 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(uow, shouldBePublished, false); + } + + Audit(uow, AuditType.Sort, "Sorting content performed by user", userId, 0); + uow.Commit(); + } + } + + return true; + } + + /// + /// Sorts a collection of objects by updating the SortOrder according + /// to the ordering of node Ids 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(int[] ids, int userId = 0, bool raiseEvents = true) + { + var shouldBePublished = new List(); + var shouldBeSaved = new List(); + + using (new WriteLock(Locker)) + { + var allContent = GetByIds(ids).ToDictionary(x => x.Id, x => x); + if (allContent.Any() == false) + { + return false; + } + var items = ids.Select(x => allContent[x]).ToArray(); + + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(items); + if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) + { + uow.Commit(); + return false; + } + + var repository = RepositoryFactory.CreateContentRepository(uow); + + var i = 0; + foreach (var content in items) + { + //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(uow, content, userId).Success; + 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)); + } + + if (raiseEvents) + { + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + } + + 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(uow, shouldBePublished, false); + } + + Audit(uow, AuditType.Sort, "Sort child items performed by user", userId, items.First().ParentId); + uow.Commit(); + } + } + + return true; + } + + public IEnumerable GetBlueprintsForContentTypes(params int[] documentTypeIds) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); + + var query = new Query(); + if (documentTypeIds.Length > 0) + { + query.Where(x => documentTypeIds.Contains(x.ContentTypeId)); + } + return repository.GetByQuery(query).Select(x => + { + ((Content) x).IsBlueprint = true; + return x; + }); + } + } + + /// + /// Gets paged content descendants as XML by path + /// + /// Path starts with + /// Page number + /// Page size + /// Total records the query would return without paging + /// A paged enumerable of XML entries of content items + public IEnumerable GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords) + { + Mandate.ParameterCondition(pageIndex >= 0, "pageIndex"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, + //This order by is VERY important! This allows us to figure out what is implicitly not published, see ContentRepository.BuildXmlCache and + // UmbracoContentIndexer.PerformIndexAll which uses the logic based on this sort order + new[] { "level", "parentID", "sortOrder" }, + out totalRecords); + return contents; + } + } + + /// + /// This builds the Xml document used for the XML cache + /// + /// + public XmlDocument BuildXmlCache() + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var result = repository.BuildXmlCache(); + uow.Commit(); + return result; + } + } + + public XmlDocument BuildPreviewXmlCache() + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + var result = repository.BuildPreviewXmlCache(); + uow.Commit(); + return result; + } + } + + /// + /// Rebuilds all xml content in the cmsContentXml table for all documents + /// + /// + /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures + /// for all content + /// + public void RebuildXmlStructures(params int[] contentTypeIds) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + repository.RebuildXmlStructures( + content => _entitySerializer.Serialize(this, _dataTypeService, _userService, content), + contentTypeIds: contentTypeIds.Length == 0 ? null : contentTypeIds); + + Audit(uow, AuditType.Publish, "ContentService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, Constants.System.Root); + uow.Commit(); + } + } + + #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 uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + var query = Query.Builder.Where(x => x.Id != content.Id && x.Path.StartsWith(content.Path) && x.Trashed == false); + return repository.GetByPublishedVersion(query); + } + } + + #endregion + + #region Private Methods + + /// + /// Hack: This is used to fix some data if an entity's properties are invalid/corrupt + /// + /// + private void QuickUpdate(IContent content) + { + if (content == null) throw new ArgumentNullException("content"); + if (content.HasIdentity == false) throw new InvalidOperationException("Cannot update an entity without an Identity"); + + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + repository.AddOrUpdate(content); + uow.Commit(); + } + } + + private void Audit(IScopeUnitOfWork uow, AuditType type, string message, int userId, int objectId) + { + var auditRepo = RepositoryFactory.CreateAuditRepository(uow); + auditRepo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); + } + + //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)); + uow.Commit(); + } + } + } + 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)) + { + //Hack: this ensures that the entity's path is valid and if not it fixes/persists it + //see: http://issues.umbraco.org/issue/U4-9336 + content.EnsureValidPath(Logger, entity => GetById(entity.ParentId), QuickUpdate); + + 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; + + using (var uow = UowProvider.GetUnitOfWork()) + { + //Publish and then update the database with new status + var publishedOutcome = internalStrategy.PublishWithChildren(uow, 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 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); + } + + //Save xml to db and call following method to fire event: + _publishingStrategy.PublishingFinalized(uow, updated, false); + + Audit(uow, AuditType.Publish, "Publish with Children performed by user", userId, content.Id); + uow.Commit(); + + 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 + } + + using (var uow = UowProvider.GetUnitOfWork()) + { + var unpublished = _publishingStrategy.UnPublish(uow, content, userId); + if (unpublished == false) + { + uow.Commit(); + return Attempt.Fail(new UnPublishStatus(content, UnPublishedStatusType.FailedCancelledByEvent, evtMsgs)); + } + + 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); + + //Delete xml from db? and call following method to fire event through PublishingStrategy to update cache + if (omitCacheRefresh == false) + _publishingStrategy.UnPublishingFinalized(uow, content); + + Audit(uow, AuditType.UnPublish, "UnPublish performed by user", userId, content.Id); + uow.Commit(); + } + + 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(); + + using (new WriteLock(Locker)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(content, evtMsgs); + if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) + { + uow.Commit(); + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent, evtMsgs)); + } + + //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) + { + //Publish and then update the database with new status + var publishResult = _publishingStrategy.Publish(uow, 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 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); + repository.AddOrUpdatePreviewXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + + if (published) + { + //Content Xml + repository.AddOrUpdateContentXml(content, c => _entitySerializer.Serialize(this, _dataTypeService, _userService, c)); + } + + if (raiseEvents) + { + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + } + + //Save xml to db and call following method to fire event through PublishingStrategy to update cache + if (published) + { + _publishingStrategy.PublishingFinalized(uow, 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)) + { + //TODO: Horrible for performance if there are lots of descendents! We should page if anything but this is crazy + var descendants = GetPublishedDescendants(content); + _publishingStrategy.PublishingFinalized(uow, descendants, false); + } + + uow.Commit(); + + if (publishStatus.StatusType == PublishStatusType.Success) + { + Audit(uow, AuditType.Publish, "Save and Publish performed by user", userId, content.Id); + } + else + { + Audit(uow, AuditType.Save, "Save performed by user", userId, content.Id); + } + uow.Commit(); + + 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(); + + using (new WriteLock(Locker)) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(content, evtMsgs); + if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) + { + uow.Commit(); + return OperationStatus.Cancelled(evtMsgs); + } + + if (string.IsNullOrWhiteSpace(content.Name)) + { + throw new ArgumentException("Cannot save content with empty name."); + } + + 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)); + + if (raiseEvents) + { + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); + } + + Audit(uow, AuditType.Save, "Save Content performed by user", userId, content.Id); + uow.Commit(); + } + + return OperationStatus.Success(evtMsgs); + } + } + + 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; + } + + 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; + } + + if (content.ReleaseDate.HasValue && content.ReleaseDate.Value > DateTime.MinValue && content.ReleaseDate.Value > DateTime.Now) + { + Logger.Info( + string.Format( + "Content '{0}' with Id '{1}' is awaiting release and could not be published.", + content.Name, content.Id)); + return PublishStatusType.FailedAwaitingRelease; + } + + return PublishStatusType.Success; + } + + private PublishStatusType CheckAndLogIsValid(IContent content) + { + //Content contains invalid property values and can therefore not be published - fire event? + if (content.IsValid() == false) + { + Logger.Info( + string.Format( + "Content '{0}' with Id '{1}' could not be published because of invalid properties.", + content.Name, content.Id)); + return PublishStatusType.FailedContentInvalid; + } + + return PublishStatusType.Success; + } + + private IContentType FindContentTypeByAlias(string contentTypeAlias) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentTypeRepository(uow); + + 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; + + /// + /// Occurs after a blueprint has been saved. + /// + public static event TypedEventHandler> SavedBlueprint; + + /// + /// Occurs after a blueprint has been deleted. + /// + public static event TypedEventHandler> DeletedBlueprint; + + #endregion + } +} diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 5a626c5d36..8c063890fd 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -1,717 +1,720 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Xml; -using System.Xml.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Querying; -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 : IContentServiceBase - { - IEnumerable GetBlueprintsForContentTypes(params int[] documentTypeIds); - IContent GetBlueprintById(int id); - IContent GetBlueprintById(Guid id); - void SaveBlueprint(IContent content, int userId = 0); - void DeleteBlueprint(IContent content, int userId = 0); - IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = 0); - void DeleteBlueprintsOfType(int contentTypeId, int userId = 0); - void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = 0); - - /// - /// Gets all XML entries found in the cmsContentXml table based on the given path - /// - /// Path starts with - /// Page number - /// Page size - /// Total records the query would return without paging - /// A paged enumerable of XML entries of content items - /// - /// If -1 is passed, then this will return all content xml entries, otherwise will return all descendents from the path - /// - IEnumerable GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords); - - /// - /// This builds the Xml document used for the XML cache - /// - /// - XmlDocument BuildXmlCache(); - - XmlDocument BuildPreviewXmlCache(); - - /// - /// 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 group id & permission pairs. - /// - /// - void ReplaceContentPermissions(EntityPermissionSet permissionSet); - - /// - /// Assigns a single permission to the current content item for the specified user group ids - /// - /// - /// - /// - void AssignContentPermission(IContent entity, char permission, IEnumerable groupIds); - - /// - /// Returns implicit/inherited permissions assigned to the content item for all user groups - /// - /// - /// - EntityPermissionCollection GetPermissionsForEntity(IContent content); - - bool SendToPublication(IContent content, int userId = 0); - - IEnumerable GetByIds(IEnumerable ids); - 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, Guid 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 - /// 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 = ""); - - /// - /// 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, 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 - /// - /// 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, Direction orderDirection, bool orderBySystemField, 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 - /// - /// An Enumerable list of objects - IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter); - - /// - /// Gets a collection of an objects versions by its Id - /// - /// - /// An Enumerable list of objects - IEnumerable GetVersions(int id); - - /// - /// Gets a list of all version Ids for the given content item ordered so latest is first - /// - /// - /// The maximum number of rows to return - /// - IEnumerable GetVersionIds(int id, int maxRows); - - /// - /// 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); - - /// - /// Deletes all content of the specified types. All Descendants of deleted content that is not of these types is moved to Recycle Bin. - /// - /// This needs extra care and attention as its potentially a dangerous and extensive operation - /// Ids of the s - /// Optional Id of the user issueing the delete operation - void DeleteContentOfTypes(IEnumerable contentTypeIds, 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 - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")] - void EmptyRecycleBin(); - - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - /// Optional Id of the User emptying the Recycle Bin - void EmptyRecycleBin(int userId = 0); - - /// - /// 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); - - /// - /// Sorts a collection of objects by updating the SortOrder according - /// to the ordering of node Ids 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(int[] ids, 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); - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Xml; +using System.Xml.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; +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 : IContentServiceBase + { + IEnumerable GetBlueprintsForContentTypes(params int[] documentTypeIds); + IContent GetBlueprintById(int id); + IContent GetBlueprintById(Guid id); + void SaveBlueprint(IContent content, int userId = 0); + void DeleteBlueprint(IContent content, int userId = 0); + IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = 0); + void DeleteBlueprintsOfType(int contentTypeId, int userId = 0); + void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = 0); + + /// + /// Gets all XML entries found in the cmsContentXml table based on the given path + /// + /// Path starts with + /// Page number + /// Page size + /// Total records the query would return without paging + /// A paged enumerable of XML entries of content items + /// + /// If -1 is passed, then this will return all content xml entries, otherwise will return all descendents from the path + /// + IEnumerable GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords); + + /// + /// This builds the Xml document used for the XML cache + /// + /// + XmlDocument BuildXmlCache(); + + XmlDocument BuildPreviewXmlCache(); + + /// + /// 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 group id & permission pairs. + /// + /// + void ReplaceContentPermissions(EntityPermissionSet permissionSet); + + /// + /// Assigns a single permission to the current content item for the specified user group ids + /// + /// + /// + /// + void AssignContentPermission(IContent entity, char permission, IEnumerable groupIds); + + /// + /// Returns implicit/inherited permissions assigned to the content item for all user groups + /// + /// + /// + EntityPermissionCollection GetPermissionsForEntity(IContent content); + + bool SendToPublication(IContent content, int userId = 0); + + IEnumerable GetByIds(IEnumerable ids); + 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, Guid 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 + /// 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 = ""); + + /// + /// 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, 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 + /// + /// 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, Direction orderDirection, bool orderBySystemField, 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 + /// + /// An Enumerable list of objects + IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter); + + /// + /// Gets a collection of an objects versions by its Id + /// + /// + /// An Enumerable list of objects + IEnumerable GetVersions(int id); + + /// + /// Gets a list of all version Ids for the given content item ordered so latest is first + /// + /// + /// The maximum number of rows to return + /// + IEnumerable GetVersionIds(int id, int maxRows); + + /// + /// 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); + + /// + /// Deletes all content of the specified types. All Descendants of deleted content that is not of these types is moved to Recycle Bin. + /// + /// This needs extra care and attention as its potentially a dangerous and extensive operation + /// Ids of the s + /// Optional Id of the user issueing the delete operation + void DeleteContentOfTypes(IEnumerable contentTypeIds, 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 + /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")] + void EmptyRecycleBin(); + + /// + /// Empties the Recycle Bin by deleting all that resides in the bin + /// + /// Optional Id of the User emptying the Recycle Bin + void EmptyRecycleBin(int userId = 0); + + /// + /// 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); + + /// + /// Sorts a collection of objects by updating the SortOrder according + /// to the ordering of node Ids 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(int[] ids, 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); + + IList GetAnchorValuesFromRTEs(int id); + IList GetAnchorValuesFromRTEContent(string rteContent); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 31ff7424ac..db5e9dd404 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -160,6 +160,38 @@ function entityResource($q, $http, umbRequestHelper) { 'Failed to retrieve entity data for id ' + id); }, + + getUrlAndAnchors: function (id) { + + if (id === -1 || id === "-1") { + return null; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetUrlAndAnchors", + [{ id: id }])), + 'Failed to retrieve url and anchors data for id ' + id); + }, + + + getAnchors: function (rteContent) { + + if (rteContent == null || rteContent.length === 0) { + return []; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetAnchors", + [{ rteContent: rteContent }])), + 'Failed to anchors data for rte content ' + rteContent); + }, + /** * @ngdoc method * @name umbraco.resources.entityResource#getByIds @@ -488,6 +520,7 @@ function entityResource($q, $http, umbRequestHelper) { 'Failed to retrieve child data for id ' + parentId); }, + /** * @ngdoc method * @name umbraco.resources.entityResource#search @@ -583,6 +616,8 @@ function entityResource($q, $http, umbRequestHelper) { 'Failed to retrieve entity data for query ' + query); } + + }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 7edd462a57..f721c31337 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -1,879 +1,850 @@ -/** - * @ngdoc service - * @name umbraco.services.tinyMceService - * - * - * @description - * A service containing all logic for all of the Umbraco TinyMCE plugins - */ -function tinyMceService($log, imageHelper, $http, $timeout, macroResource, macroService, $routeParams, umbRequestHelper, angularHelper, userService) { - return { - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#configuration - * @methodOf umbraco.services.tinyMceService - * - * @description - * Returns a collection of plugins available to the tinyMCE editor - * - */ - configuration: function () { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "rteApiBaseUrl", - "GetConfiguration"), { - cache: true - }), - 'Failed to retrieve tinymce configuration'); - }, - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#defaultPrevalues - * @methodOf umbraco.services.tinyMceService - * - * @description - * Returns a default configration to fallback on in case none is provided - * - */ - defaultPrevalues: function () { - var cfg = {}; - cfg.toolbar = ["code", "bold", "italic", "styleselect", "alignleft", "aligncenter", "alignright", "bullist", "numlist", "outdent", "indent", "link", "image", "umbmediapicker", "umbembeddialog", "umbmacro"]; - cfg.stylesheets = []; - cfg.dimensions = { - height: 500 - }; - cfg.maxImageSize = 500; - return cfg; - }, - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#createInsertEmbeddedMedia - * @methodOf umbraco.services.tinyMceService - * - * @description - * Creates the umbrco insert embedded media tinymce plugin - * - * @param {Object} editor the TinyMCE editor instance - * @param {Object} $scope the current controller scope - */ - createInsertEmbeddedMedia: function (editor, scope, callback) { - editor.addButton('umbembeddialog', { - icon: 'custom icon-tv', - tooltip: 'Embed', - onclick: function () { - if (callback) { - callback(); - } - } - }); - }, - - insertEmbeddedMediaInEditor: function (editor, preview) { - editor.insertContent(preview); - }, - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#createMediaPicker - * @methodOf umbraco.services.tinyMceService - * - * @description - * Creates the umbrco insert media tinymce plugin - * - * @param {Object} editor the TinyMCE editor instance - * @param {Object} $scope the current controller scope - */ - createMediaPicker: function (editor, scope, callback) { - editor.addButton('umbmediapicker', { - icon: 'custom icon-picture', - tooltip: 'Media Picker', - stateSelector: 'img', - onclick: function () { - - var selectedElm = editor.selection.getNode(), - currentTarget; - - - if (selectedElm.nodeName === 'IMG') { - var img = $(selectedElm); - - var hasUdi = img.attr("data-udi") ? true : false; - - currentTarget = { - altText: img.attr("alt"), - url: img.attr("src") - }; - - if (hasUdi) { - currentTarget["udi"] = img.attr("data-udi"); - } else { - currentTarget["id"] = img.attr("rel"); - } - } - - userService.getCurrentUser().then(function (userData) { - if (callback) { - callback(currentTarget, userData); - } - }); - - } - }); - }, - - insertMediaInEditor: function (editor, img) { - if (img) { - - var hasUdi = img.udi ? true : false; - - var data = { - alt: img.altText || "", - src: (img.url) ? img.url : "nothing.jpg", - id: '__mcenew' - }; - - if (hasUdi) { - data["data-udi"] = img.udi; - } else { - //Considering these fixed because UDI will now be used and thus - // we have no need for rel http://issues.umbraco.org/issue/U4-6228, http://issues.umbraco.org/issue/U4-6595 - data["rel"] = img.id; - data["data-id"] = img.id; - } - - editor.insertContent(editor.dom.createHTML('img', data)); - - $timeout(function () { - var imgElm = editor.dom.get('__mcenew'); - var size = editor.dom.getSize(imgElm); - - if (editor.settings.maxImageSize && editor.settings.maxImageSize !== 0) { - var newSize = imageHelper.scaleToMaxSize(editor.settings.maxImageSize, size.w, size.h); - - var s = "width: " + newSize.width + "px; height:" + newSize.height + "px;"; - editor.dom.setAttrib(imgElm, 'style', s); - - if (img.url) { - var src = img.url + "?width=" + newSize.width + "&height=" + newSize.height; - editor.dom.setAttrib(imgElm, 'data-mce-src', src); - } - } - editor.dom.setAttrib(imgElm, 'id', null); - }, 500); - } - }, - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#createUmbracoMacro - * @methodOf umbraco.services.tinyMceService - * - * @description - * Creates the insert umbrco macro tinymce plugin - * - * @param {Object} editor the TinyMCE editor instance - * @param {Object} $scope the current controller scope - */ - createInsertMacro: function (editor, $scope, callback) { - - var createInsertMacroScope = this; - - /** Adds custom rules for the macro plugin and custom serialization */ - editor.on('preInit', function (args) { - //this is requires so that we tell the serializer that a 'div' is actually allowed in the root, otherwise the cleanup will strip it out - editor.serializer.addRules('div'); - - /** This checks if the div is a macro container, if so, checks if its wrapped in a p tag and then unwraps it (removes p tag) */ - editor.serializer.addNodeFilter('div', function (nodes, name) { - for (var i = 0; i < nodes.length; i++) { - if (nodes[i].attr("class") === "umb-macro-holder" && nodes[i].parent && nodes[i].parent.name.toUpperCase() === "P") { - nodes[i].parent.unwrap(); - } - } - }); - - }); - - /** - * Because the macro gets wrapped in a P tag because of the way 'enter' works, this - * method will return the macro element if not wrapped in a p, or the p if the macro - * element is the only one inside of it even if we are deep inside an element inside the macro - */ - function getRealMacroElem(element) { - var e = $(element).closest(".umb-macro-holder"); - if (e.length > 0) { - if (e.get(0).parentNode.nodeName === "P") { - //now check if we're the only element - if (element.parentNode.childNodes.length === 1) { - return e.get(0).parentNode; - } - } - return e.get(0); - } - return null; - } - - /** Adds the button instance */ - editor.addButton('umbmacro', { - icon: 'custom icon-settings-alt', - tooltip: 'Insert macro', - onPostRender: function () { - - var ctrl = this; - var isOnMacroElement = false; - - /** - if the selection comes from a different element that is not the macro's - we need to check if the selection includes part of the macro, if so we'll force the selection - to clear to the next element since if people can select part of the macro markup they can then modify it. - */ - function handleSelectionChange() { - - if (!editor.selection.isCollapsed()) { - var endSelection = tinymce.activeEditor.selection.getEnd(); - var startSelection = tinymce.activeEditor.selection.getStart(); - //don't proceed if it's an entire element selected - if (endSelection !== startSelection) { - - //if the end selection is a macro then move the cursor - //NOTE: we don't have to handle when the selection comes from a previous parent because - // that is automatically taken care of with the normal onNodeChanged logic since the - // evt.element will be the macro once it becomes part of the selection. - var $testForMacro = $(endSelection).closest(".umb-macro-holder"); - if ($testForMacro.length > 0) { - - //it came from before so move after, if there is no after then select ourselves - var next = $testForMacro.next(); - if (next.length > 0) { - editor.selection.setCursorLocation($testForMacro.next().get(0)); - } else { - selectMacroElement($testForMacro.get(0)); - } - - } - } - } - } - - /** helper method to select the macro element */ - function selectMacroElement(macroElement) { - - // move selection to top element to ensure we can't edit this - editor.selection.select(macroElement); - - // check if the current selection *is* the element (ie bug) - var currentSelection = editor.selection.getStart(); - if (tinymce.isIE) { - if (!editor.dom.hasClass(currentSelection, 'umb-macro-holder')) { - while (!editor.dom.hasClass(currentSelection, 'umb-macro-holder') && currentSelection.parentNode) { - currentSelection = currentSelection.parentNode; - } - editor.selection.select(currentSelection); - } - } - } - - /** - * Add a node change handler, test if we're editing a macro and select the whole thing, then set our isOnMacroElement flag. - * If we change the selection inside this method, then we end up in an infinite loop, so we have to remove ourselves - * from the event listener before changing selection, however, it seems that putting a break point in this method - * will always cause an 'infinite' loop as the caret keeps changing. - */ - function onNodeChanged(evt) { - - //set our macro button active when on a node of class umb-macro-holder - var $macroElement = $(evt.element).closest(".umb-macro-holder"); - - handleSelectionChange(); - - //set the button active - ctrl.active($macroElement.length !== 0); - - if ($macroElement.length > 0) { - var macroElement = $macroElement.get(0); - - //remove the event listener before re-selecting - editor.off('NodeChange', onNodeChanged); - - selectMacroElement(macroElement); - - //set the flag - isOnMacroElement = true; - - //re-add the event listener - editor.on('NodeChange', onNodeChanged); - } else { - isOnMacroElement = false; - } - - } - - /** when the contents load we need to find any macros declared and load in their content */ - editor.on("LoadContent", function (o) { - - //get all macro divs and load their content - $(editor.dom.select(".umb-macro-holder.mceNonEditable")).each(function () { - createInsertMacroScope.loadMacroContent($(this), null, $scope); - }); - - }); - - /** This prevents any other commands from executing when the current element is the macro so the content cannot be edited */ - editor.on('BeforeExecCommand', function (o) { - if (isOnMacroElement) { - if (o.preventDefault) { - o.preventDefault(); - } - if (o.stopImmediatePropagation) { - o.stopImmediatePropagation(); - } - return; - } - }); - - /** This double checks and ensures you can't paste content into the rendered macro */ - editor.on("Paste", function (o) { - if (isOnMacroElement) { - if (o.preventDefault) { - o.preventDefault(); - } - if (o.stopImmediatePropagation) { - o.stopImmediatePropagation(); - } - return; - } - }); - - //set onNodeChanged event listener - editor.on('NodeChange', onNodeChanged); - - /** - * Listen for the keydown in the editor, we'll check if we are currently on a macro element, if so - * we'll check if the key down is a supported key which requires an action, otherwise we ignore the request - * so the macro cannot be edited. - */ - editor.on('KeyDown', function (e) { - if (isOnMacroElement) { - var macroElement = editor.selection.getNode(); - - //get the 'real' element (either p or the real one) - macroElement = getRealMacroElem(macroElement); - - //prevent editing - e.preventDefault(); - e.stopPropagation(); - - var moveSibling = function (element, isNext) { - var $e = $(element); - var $sibling = isNext ? $e.next() : $e.prev(); - if ($sibling.length > 0) { - editor.selection.select($sibling.get(0)); - editor.selection.collapse(true); - } else { - //if we're moving previous and there is no sibling, then lets recurse and just select the next one - if (!isNext) { - moveSibling(element, true); - return; - } - - //if there is no sibling we'll generate a new p at the end and select it - editor.setContent(editor.getContent() + "

 

"); - editor.selection.select($(editor.dom.getRoot()).children().last().get(0)); - editor.selection.collapse(true); - - } - }; - - //supported keys to move to the next or prev element (13-enter, 27-esc, 38-up, 40-down, 39-right, 37-left) - //supported keys to remove the macro (8-backspace, 46-delete) - //TODO: Should we make the enter key insert a line break before or leave it as moving to the next element? - if ($.inArray(e.keyCode, [13, 40, 39]) !== -1) { - //move to next element - moveSibling(macroElement, true); - } else if ($.inArray(e.keyCode, [27, 38, 37]) !== -1) { - //move to prev element - moveSibling(macroElement, false); - } else if ($.inArray(e.keyCode, [8, 46]) !== -1) { - //delete macro element - - //move first, then delete - moveSibling(macroElement, false); - editor.dom.remove(macroElement); - } - return; - } - }); - - }, - - /** The insert macro button click event handler */ - onclick: function () { - - var dialogData = { - //flag for use in rte so we only show macros flagged for the editor - richTextEditor: true - }; - - //when we click we could have a macro already selected and in that case we'll want to edit the current parameters - //so we'll need to extract them and submit them to the dialog. - var macroElement = editor.selection.getNode(); - macroElement = getRealMacroElem(macroElement); - if (macroElement) { - //we have a macro selected so we'll need to parse it's alias and parameters - var contents = $(macroElement).contents(); - var comment = _.find(contents, function (item) { - return item.nodeType === 8; - }); - if (!comment) { - throw "Cannot parse the current macro, the syntax in the editor is invalid"; - } - var syntax = comment.textContent.trim(); - var parsed = macroService.parseMacroSyntax(syntax); - dialogData = { - macroData: parsed - }; - } - - if (callback) { - callback(dialogData); - } - - } - }); - }, - - insertMacroInEditor: function (editor, macroObject, $scope) { - - //put the macro syntax in comments, we will parse this out on the server side to be used - //for persisting. - var macroSyntaxComment = ""; - //create an id class for this element so we can re-select it after inserting - var uniqueId = "umb-macro-" + editor.dom.uniqueId(); - var macroDiv = editor.dom.create('div', { - 'class': 'umb-macro-holder ' + macroObject.macroAlias + ' mceNonEditable ' + uniqueId - }, - macroSyntaxComment + 'Macro alias: ' + macroObject.macroAlias + ''); - - editor.selection.setNode(macroDiv); - - var $macroDiv = $(editor.dom.select("div.umb-macro-holder." + uniqueId)); - - //async load the macro content - this.loadMacroContent($macroDiv, macroObject, $scope); - - }, - - /** loads in the macro content async from the server */ - loadMacroContent: function ($macroDiv, macroData, $scope) { - - //if we don't have the macroData, then we'll need to parse it from the macro div - if (!macroData) { - var contents = $macroDiv.contents(); - var comment = _.find(contents, function (item) { - return item.nodeType === 8; - }); - if (!comment) { - throw "Cannot parse the current macro, the syntax in the editor is invalid"; - } - var syntax = comment.textContent.trim(); - var parsed = macroService.parseMacroSyntax(syntax); - macroData = parsed; - } - - var $ins = $macroDiv.find("ins"); - - //show the throbber - $macroDiv.addClass("loading"); - - var contentId = $routeParams.id; - - //need to wrap in safe apply since this might be occuring outside of angular - angularHelper.safeApply($scope, function () { - macroResource.getMacroResultAsHtmlForEditor(macroData.macroAlias, contentId, macroData.macroParamsDictionary) - .then(function (htmlResult) { - - $macroDiv.removeClass("loading"); - htmlResult = htmlResult.trim(); - if (htmlResult !== "") { - $ins.html(htmlResult); - } - }); - }); - - }, - - createLinkPicker: function (editor, $scope, onClick) { - - function createLinkList(callback) { - return function () { - var linkList = editor.settings.link_list; - - if (typeof (linkList) === "string") { - tinymce.util.XHR.send({ - url: linkList, - success: function (text) { - callback(tinymce.util.JSON.parse(text)); - } - }); - } else { - callback(linkList); - } - }; - } - - function showDialog(linkList) { - var data = {}, - selection = editor.selection, - dom = editor.dom, - selectedElm, anchorElm, initialText; - var win, linkListCtrl, relListCtrl, targetListCtrl; - - function linkListChangeHandler(e) { - var textCtrl = win.find('#text'); - - if (!textCtrl.value() || (e.lastControl && textCtrl.value() === e.lastControl.text())) { - textCtrl.value(e.control.text()); - } - - win.find('#href').value(e.control.value()); - } - - function buildLinkList() { - var linkListItems = [{ - text: 'None', - value: '' - }]; - - tinymce.each(linkList, function (link) { - linkListItems.push({ - text: link.text || link.title, - value: link.value || link.url, - menu: link.menu - }); - }); - - return linkListItems; - } - - function buildRelList(relValue) { - var relListItems = [{ - text: 'None', - value: '' - }]; - - tinymce.each(editor.settings.rel_list, function (rel) { - relListItems.push({ - text: rel.text || rel.title, - value: rel.value, - selected: relValue === rel.value - }); - }); - - return relListItems; - } - - function buildTargetList(targetValue) { - var targetListItems = [{ - text: 'None', - value: '' - }]; - - if (!editor.settings.target_list) { - targetListItems.push({ - text: 'New window', - value: '_blank' - }); - } - - tinymce.each(editor.settings.target_list, function (target) { - targetListItems.push({ - text: target.text || target.title, - value: target.value, - selected: targetValue === target.value - }); - }); - - return targetListItems; - } - - function buildAnchorListControl(url) { - var anchorList = []; - - tinymce.each(editor.dom.select('a:not([href])'), function (anchor) { - var id = anchor.name || anchor.id; - - if (id) { - anchorList.push({ - text: id, - value: '#' + id, - selected: url.indexOf('#' + id) !== -1 - }); - } - }); - - if (anchorList.length) { - anchorList.unshift({ - text: 'None', - value: '' - }); - - return { - name: 'anchor', - type: 'listbox', - label: 'Anchors', - values: anchorList, - onselect: linkListChangeHandler - }; - } - } - - function updateText() { - if (!initialText && data.text.length === 0) { - this.parent().parent().find('#text')[0].value(this.value()); - } - } - - selectedElm = selection.getNode(); - anchorElm = dom.getParent(selectedElm, 'a[href]'); - - data.text = initialText = anchorElm ? (anchorElm.innerText || anchorElm.textContent) : selection.getContent({ - format: 'text' - }); - data.href = anchorElm ? dom.getAttrib(anchorElm, 'href') : ''; - data.target = anchorElm ? dom.getAttrib(anchorElm, 'target') : ''; - data.rel = anchorElm ? dom.getAttrib(anchorElm, 'rel') : ''; - - if (selectedElm.nodeName === "IMG") { - data.text = initialText = " "; - } - - if (linkList) { - linkListCtrl = { - type: 'listbox', - label: 'Link list', - values: buildLinkList(), - onselect: linkListChangeHandler - }; - } - - if (editor.settings.target_list !== false) { - targetListCtrl = { - name: 'target', - type: 'listbox', - label: 'Target', - values: buildTargetList(data.target) - }; - } - - if (editor.settings.rel_list) { - relListCtrl = { - name: 'rel', - type: 'listbox', - label: 'Rel', - values: buildRelList(data.rel) - }; - } - - var currentTarget = null; - - //if we already have a link selected, we want to pass that data over to the dialog - if (anchorElm) { - var anchor = $(anchorElm); - currentTarget = { - name: anchor.attr("title"), - url: anchor.attr("href"), - target: anchor.attr("target") - }; - - // drop the lead char from the anchor text, if it has a value - var anchorVal = anchor[0].dataset.anchor; - if (anchorVal) { - currentTarget.anchor = anchorVal.substring(1); - } - - //locallink detection, we do this here, to avoid poluting the dialogservice - //so the dialog service can just expect to get a node-like structure - if (currentTarget.url.indexOf("localLink:") > 0) { - // if the current link has an anchor, it needs to be considered when getting the udi/id - // if an anchor exists, reduce the substring max by its length plus two to offset the removed prefix and trailing curly brace - var linkId = currentTarget.url.substring(currentTarget.url.indexOf(":") + 1, currentTarget.url.lastIndexOf("}")); - - //we need to check if this is an INT or a UDI - var parsedIntId = parseInt(linkId, 10); - if (isNaN(parsedIntId)) { - //it's a UDI - currentTarget.udi = linkId; - } else { - currentTarget.id = linkId; - } - } - } - - if (onClick) { - onClick(currentTarget, anchorElm); - } - - } - - editor.addButton('link', { - icon: 'link', - tooltip: 'Insert/edit link', - shortcut: 'Ctrl+K', - onclick: createLinkList(showDialog), - stateSelector: 'a[href]' - }); - - editor.addButton('unlink', { - icon: 'unlink', - tooltip: 'Remove link', - cmd: 'unlink', - stateSelector: 'a[href]' - }); - - editor.addShortcut('Ctrl+K', '', createLinkList(showDialog)); - this.showDialog = showDialog; - - editor.addMenuItem('link', { - icon: 'link', - text: 'Insert link', - shortcut: 'Ctrl+K', - onclick: createLinkList(showDialog), - stateSelector: 'a[href]', - context: 'insert', - prependToContext: true - }); - - }, - - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#getAnchorNames - * @methodOf umbraco.services.tinyMceService - * - * @description - * From the given string, generates a string array where each item is the id attribute value from a named anchor - * 'some string
with a named anchor' returns ['anchor'] - * - * @param {string} input the string to parse - */ - getAnchorNames: function (input) { - if (!input) return []; - - var anchorPattern = //gi; - var matches = input.match(anchorPattern); - var anchors = []; - - if (matches) { - anchors = matches.map(function (v) { - return v.substring(v.indexOf('"') + 1, v.lastIndexOf('\\')); - }); - } - - return anchors.filter(function(val, i, self) { - return self.indexOf(val) === i; - }); - }, - - insertLinkInEditor: function (editor, target, anchorElm) { - - var href = target.url; - // We want to use the Udi. If it is set, we use it, else fallback to id, and finally to null - var hasUdi = target.udi ? true : false; - var id = hasUdi ? target.udi : (target.id ? target.id : null); - - // if an anchor exists, check that it is appropriately prefixed - if (target.anchor && target.anchor[0] !== '?' && target.anchor[0] !== '#') { - target.anchor = (target.anchor.indexOf('=') === -1 ? '#' : '?') + target.anchor; - } - - // the href might be an external url, so check the value for an anchor/qs - // href has the anchor re-appended later, hence the reset here to avoid duplicating the anchor - if (!target.anchor) { - var urlParts = href.split(/(#|\?)/); - if (urlParts.length === 3) { - href = urlParts[0]; - target.anchor = urlParts[1] + urlParts[2]; - } - } - - //Create a json obj used to create the attributes for the tag - function createElemAttributes() { - var a = { - href: href, - title: target.name, - target: target.target ? target.target : null, - rel: target.rel ? target.rel : null - }; - - if (hasUdi) { - a["data-udi"] = target.udi; - } else if (target.id) { - a["data-id"] = target.id; - } - - if (target.anchor) { - a["data-anchor"] = target.anchor; - a.href = a.href + target.anchor; - } else { - a["data-anchor"] = null; - } - - return a; - } - - function insertLink() { - if (anchorElm) { - editor.dom.setAttribs(anchorElm, createElemAttributes()); - - editor.selection.select(anchorElm); - editor.execCommand('mceEndTyping'); - } else { - editor.execCommand('mceInsertLink', false, createElemAttributes()); - } - } - - if (!href && !target.anchor) { - editor.execCommand('unlink'); - return; - } - - //if we have an id, it must be a locallink:id, aslong as the isMedia flag is not set - if (id && (angular.isUndefined(target.isMedia) || !target.isMedia)) { - - href = "/{localLink:" + id + "}"; - - insertLink(); - return; - } - - if (!href) { - href = ""; - } - - // Is email and not //user@domain.com and protocol (e.g. mailto:, sip:) is not specified - if (href.indexOf('@') > 0 && href.indexOf('//') === -1 && href.indexOf(':') === -1) { - // assume it's a mailto link - href = 'mailto:' + href; - insertLink(); - return; - } - - // Is www. prefixed - if (/^\s*www\./i.test(href)) { - href = 'http://' + href; - insertLink(); - return; - } - - insertLink(); - - } - - }; -} - -angular.module('umbraco.services').factory('tinyMceService', tinyMceService); +/** + * @ngdoc service + * @name umbraco.services.tinyMceService + * + * + * @description + * A service containing all logic for all of the Umbraco TinyMCE plugins + */ +function tinyMceService($log, imageHelper, $http, $timeout, macroResource, macroService, $routeParams, umbRequestHelper, angularHelper, userService) { + return { + + /** + * @ngdoc method + * @name umbraco.services.tinyMceService#configuration + * @methodOf umbraco.services.tinyMceService + * + * @description + * Returns a collection of plugins available to the tinyMCE editor + * + */ + configuration: function () { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "rteApiBaseUrl", + "GetConfiguration"), { + cache: true + }), + 'Failed to retrieve tinymce configuration'); + }, + + /** + * @ngdoc method + * @name umbraco.services.tinyMceService#defaultPrevalues + * @methodOf umbraco.services.tinyMceService + * + * @description + * Returns a default configration to fallback on in case none is provided + * + */ + defaultPrevalues: function () { + var cfg = {}; + cfg.toolbar = ["code", "bold", "italic", "styleselect", "alignleft", "aligncenter", "alignright", "bullist", "numlist", "outdent", "indent", "link", "image", "umbmediapicker", "umbembeddialog", "umbmacro"]; + cfg.stylesheets = []; + cfg.dimensions = { + height: 500 + }; + cfg.maxImageSize = 500; + return cfg; + }, + + /** + * @ngdoc method + * @name umbraco.services.tinyMceService#createInsertEmbeddedMedia + * @methodOf umbraco.services.tinyMceService + * + * @description + * Creates the umbrco insert embedded media tinymce plugin + * + * @param {Object} editor the TinyMCE editor instance + * @param {Object} $scope the current controller scope + */ + createInsertEmbeddedMedia: function (editor, scope, callback) { + editor.addButton('umbembeddialog', { + icon: 'custom icon-tv', + tooltip: 'Embed', + onclick: function () { + if (callback) { + callback(); + } + } + }); + }, + + insertEmbeddedMediaInEditor: function (editor, preview) { + editor.insertContent(preview); + }, + + /** + * @ngdoc method + * @name umbraco.services.tinyMceService#createMediaPicker + * @methodOf umbraco.services.tinyMceService + * + * @description + * Creates the umbrco insert media tinymce plugin + * + * @param {Object} editor the TinyMCE editor instance + * @param {Object} $scope the current controller scope + */ + createMediaPicker: function (editor, scope, callback) { + editor.addButton('umbmediapicker', { + icon: 'custom icon-picture', + tooltip: 'Media Picker', + stateSelector: 'img', + onclick: function () { + + var selectedElm = editor.selection.getNode(), + currentTarget; + + + if (selectedElm.nodeName === 'IMG') { + var img = $(selectedElm); + + var hasUdi = img.attr("data-udi") ? true : false; + + currentTarget = { + altText: img.attr("alt"), + url: img.attr("src") + }; + + if (hasUdi) { + currentTarget["udi"] = img.attr("data-udi"); + } else { + currentTarget["id"] = img.attr("rel"); + } + } + + userService.getCurrentUser().then(function (userData) { + if (callback) { + callback(currentTarget, userData); + } + }); + + } + }); + }, + + insertMediaInEditor: function (editor, img) { + if (img) { + + var hasUdi = img.udi ? true : false; + + var data = { + alt: img.altText || "", + src: (img.url) ? img.url : "nothing.jpg", + id: '__mcenew' + }; + + if (hasUdi) { + data["data-udi"] = img.udi; + } else { + //Considering these fixed because UDI will now be used and thus + // we have no need for rel http://issues.umbraco.org/issue/U4-6228, http://issues.umbraco.org/issue/U4-6595 + data["rel"] = img.id; + data["data-id"] = img.id; + } + + editor.insertContent(editor.dom.createHTML('img', data)); + + $timeout(function () { + var imgElm = editor.dom.get('__mcenew'); + var size = editor.dom.getSize(imgElm); + + if (editor.settings.maxImageSize && editor.settings.maxImageSize !== 0) { + var newSize = imageHelper.scaleToMaxSize(editor.settings.maxImageSize, size.w, size.h); + + var s = "width: " + newSize.width + "px; height:" + newSize.height + "px;"; + editor.dom.setAttrib(imgElm, 'style', s); + + if (img.url) { + var src = img.url + "?width=" + newSize.width + "&height=" + newSize.height; + editor.dom.setAttrib(imgElm, 'data-mce-src', src); + } + } + editor.dom.setAttrib(imgElm, 'id', null); + }, 500); + } + }, + + /** + * @ngdoc method + * @name umbraco.services.tinyMceService#createUmbracoMacro + * @methodOf umbraco.services.tinyMceService + * + * @description + * Creates the insert umbrco macro tinymce plugin + * + * @param {Object} editor the TinyMCE editor instance + * @param {Object} $scope the current controller scope + */ + createInsertMacro: function (editor, $scope, callback) { + + var createInsertMacroScope = this; + + /** Adds custom rules for the macro plugin and custom serialization */ + editor.on('preInit', function (args) { + //this is requires so that we tell the serializer that a 'div' is actually allowed in the root, otherwise the cleanup will strip it out + editor.serializer.addRules('div'); + + /** This checks if the div is a macro container, if so, checks if its wrapped in a p tag and then unwraps it (removes p tag) */ + editor.serializer.addNodeFilter('div', function (nodes, name) { + for (var i = 0; i < nodes.length; i++) { + if (nodes[i].attr("class") === "umb-macro-holder" && nodes[i].parent && nodes[i].parent.name.toUpperCase() === "P") { + nodes[i].parent.unwrap(); + } + } + }); + + }); + + /** + * Because the macro gets wrapped in a P tag because of the way 'enter' works, this + * method will return the macro element if not wrapped in a p, or the p if the macro + * element is the only one inside of it even if we are deep inside an element inside the macro + */ + function getRealMacroElem(element) { + var e = $(element).closest(".umb-macro-holder"); + if (e.length > 0) { + if (e.get(0).parentNode.nodeName === "P") { + //now check if we're the only element + if (element.parentNode.childNodes.length === 1) { + return e.get(0).parentNode; + } + } + return e.get(0); + } + return null; + } + + /** Adds the button instance */ + editor.addButton('umbmacro', { + icon: 'custom icon-settings-alt', + tooltip: 'Insert macro', + onPostRender: function () { + + var ctrl = this; + var isOnMacroElement = false; + + /** + if the selection comes from a different element that is not the macro's + we need to check if the selection includes part of the macro, if so we'll force the selection + to clear to the next element since if people can select part of the macro markup they can then modify it. + */ + function handleSelectionChange() { + + if (!editor.selection.isCollapsed()) { + var endSelection = tinymce.activeEditor.selection.getEnd(); + var startSelection = tinymce.activeEditor.selection.getStart(); + //don't proceed if it's an entire element selected + if (endSelection !== startSelection) { + + //if the end selection is a macro then move the cursor + //NOTE: we don't have to handle when the selection comes from a previous parent because + // that is automatically taken care of with the normal onNodeChanged logic since the + // evt.element will be the macro once it becomes part of the selection. + var $testForMacro = $(endSelection).closest(".umb-macro-holder"); + if ($testForMacro.length > 0) { + + //it came from before so move after, if there is no after then select ourselves + var next = $testForMacro.next(); + if (next.length > 0) { + editor.selection.setCursorLocation($testForMacro.next().get(0)); + } else { + selectMacroElement($testForMacro.get(0)); + } + + } + } + } + } + + /** helper method to select the macro element */ + function selectMacroElement(macroElement) { + + // move selection to top element to ensure we can't edit this + editor.selection.select(macroElement); + + // check if the current selection *is* the element (ie bug) + var currentSelection = editor.selection.getStart(); + if (tinymce.isIE) { + if (!editor.dom.hasClass(currentSelection, 'umb-macro-holder')) { + while (!editor.dom.hasClass(currentSelection, 'umb-macro-holder') && currentSelection.parentNode) { + currentSelection = currentSelection.parentNode; + } + editor.selection.select(currentSelection); + } + } + } + + /** + * Add a node change handler, test if we're editing a macro and select the whole thing, then set our isOnMacroElement flag. + * If we change the selection inside this method, then we end up in an infinite loop, so we have to remove ourselves + * from the event listener before changing selection, however, it seems that putting a break point in this method + * will always cause an 'infinite' loop as the caret keeps changing. + */ + function onNodeChanged(evt) { + + //set our macro button active when on a node of class umb-macro-holder + var $macroElement = $(evt.element).closest(".umb-macro-holder"); + + handleSelectionChange(); + + //set the button active + ctrl.active($macroElement.length !== 0); + + if ($macroElement.length > 0) { + var macroElement = $macroElement.get(0); + + //remove the event listener before re-selecting + editor.off('NodeChange', onNodeChanged); + + selectMacroElement(macroElement); + + //set the flag + isOnMacroElement = true; + + //re-add the event listener + editor.on('NodeChange', onNodeChanged); + } else { + isOnMacroElement = false; + } + + } + + /** when the contents load we need to find any macros declared and load in their content */ + editor.on("LoadContent", function (o) { + + //get all macro divs and load their content + $(editor.dom.select(".umb-macro-holder.mceNonEditable")).each(function () { + createInsertMacroScope.loadMacroContent($(this), null, $scope); + }); + + }); + + /** This prevents any other commands from executing when the current element is the macro so the content cannot be edited */ + editor.on('BeforeExecCommand', function (o) { + if (isOnMacroElement) { + if (o.preventDefault) { + o.preventDefault(); + } + if (o.stopImmediatePropagation) { + o.stopImmediatePropagation(); + } + return; + } + }); + + /** This double checks and ensures you can't paste content into the rendered macro */ + editor.on("Paste", function (o) { + if (isOnMacroElement) { + if (o.preventDefault) { + o.preventDefault(); + } + if (o.stopImmediatePropagation) { + o.stopImmediatePropagation(); + } + return; + } + }); + + //set onNodeChanged event listener + editor.on('NodeChange', onNodeChanged); + + /** + * Listen for the keydown in the editor, we'll check if we are currently on a macro element, if so + * we'll check if the key down is a supported key which requires an action, otherwise we ignore the request + * so the macro cannot be edited. + */ + editor.on('KeyDown', function (e) { + if (isOnMacroElement) { + var macroElement = editor.selection.getNode(); + + //get the 'real' element (either p or the real one) + macroElement = getRealMacroElem(macroElement); + + //prevent editing + e.preventDefault(); + e.stopPropagation(); + + var moveSibling = function (element, isNext) { + var $e = $(element); + var $sibling = isNext ? $e.next() : $e.prev(); + if ($sibling.length > 0) { + editor.selection.select($sibling.get(0)); + editor.selection.collapse(true); + } else { + //if we're moving previous and there is no sibling, then lets recurse and just select the next one + if (!isNext) { + moveSibling(element, true); + return; + } + + //if there is no sibling we'll generate a new p at the end and select it + editor.setContent(editor.getContent() + "

 

"); + editor.selection.select($(editor.dom.getRoot()).children().last().get(0)); + editor.selection.collapse(true); + + } + }; + + //supported keys to move to the next or prev element (13-enter, 27-esc, 38-up, 40-down, 39-right, 37-left) + //supported keys to remove the macro (8-backspace, 46-delete) + //TODO: Should we make the enter key insert a line break before or leave it as moving to the next element? + if ($.inArray(e.keyCode, [13, 40, 39]) !== -1) { + //move to next element + moveSibling(macroElement, true); + } else if ($.inArray(e.keyCode, [27, 38, 37]) !== -1) { + //move to prev element + moveSibling(macroElement, false); + } else if ($.inArray(e.keyCode, [8, 46]) !== -1) { + //delete macro element + + //move first, then delete + moveSibling(macroElement, false); + editor.dom.remove(macroElement); + } + return; + } + }); + + }, + + /** The insert macro button click event handler */ + onclick: function () { + + var dialogData = { + //flag for use in rte so we only show macros flagged for the editor + richTextEditor: true + }; + + //when we click we could have a macro already selected and in that case we'll want to edit the current parameters + //so we'll need to extract them and submit them to the dialog. + var macroElement = editor.selection.getNode(); + macroElement = getRealMacroElem(macroElement); + if (macroElement) { + //we have a macro selected so we'll need to parse it's alias and parameters + var contents = $(macroElement).contents(); + var comment = _.find(contents, function (item) { + return item.nodeType === 8; + }); + if (!comment) { + throw "Cannot parse the current macro, the syntax in the editor is invalid"; + } + var syntax = comment.textContent.trim(); + var parsed = macroService.parseMacroSyntax(syntax); + dialogData = { + macroData: parsed + }; + } + + if (callback) { + callback(dialogData); + } + + } + }); + }, + + insertMacroInEditor: function (editor, macroObject, $scope) { + + //put the macro syntax in comments, we will parse this out on the server side to be used + //for persisting. + var macroSyntaxComment = ""; + //create an id class for this element so we can re-select it after inserting + var uniqueId = "umb-macro-" + editor.dom.uniqueId(); + var macroDiv = editor.dom.create('div', { + 'class': 'umb-macro-holder ' + macroObject.macroAlias + ' mceNonEditable ' + uniqueId + }, + macroSyntaxComment + 'Macro alias: ' + macroObject.macroAlias + ''); + + editor.selection.setNode(macroDiv); + + var $macroDiv = $(editor.dom.select("div.umb-macro-holder." + uniqueId)); + + //async load the macro content + this.loadMacroContent($macroDiv, macroObject, $scope); + + }, + + /** loads in the macro content async from the server */ + loadMacroContent: function ($macroDiv, macroData, $scope) { + + //if we don't have the macroData, then we'll need to parse it from the macro div + if (!macroData) { + var contents = $macroDiv.contents(); + var comment = _.find(contents, function (item) { + return item.nodeType === 8; + }); + if (!comment) { + throw "Cannot parse the current macro, the syntax in the editor is invalid"; + } + var syntax = comment.textContent.trim(); + var parsed = macroService.parseMacroSyntax(syntax); + macroData = parsed; + } + + var $ins = $macroDiv.find("ins"); + + //show the throbber + $macroDiv.addClass("loading"); + + var contentId = $routeParams.id; + + //need to wrap in safe apply since this might be occuring outside of angular + angularHelper.safeApply($scope, function () { + macroResource.getMacroResultAsHtmlForEditor(macroData.macroAlias, contentId, macroData.macroParamsDictionary) + .then(function (htmlResult) { + + $macroDiv.removeClass("loading"); + htmlResult = htmlResult.trim(); + if (htmlResult !== "") { + $ins.html(htmlResult); + } + }); + }); + + }, + + createLinkPicker: function (editor, $scope, onClick) { + + function createLinkList(callback) { + return function () { + var linkList = editor.settings.link_list; + + if (typeof (linkList) === "string") { + tinymce.util.XHR.send({ + url: linkList, + success: function (text) { + callback(tinymce.util.JSON.parse(text)); + } + }); + } else { + callback(linkList); + } + }; + } + + function showDialog(linkList) { + var data = {}, + selection = editor.selection, + dom = editor.dom, + selectedElm, anchorElm, initialText; + var win, linkListCtrl, relListCtrl, targetListCtrl; + + function linkListChangeHandler(e) { + var textCtrl = win.find('#text'); + + if (!textCtrl.value() || (e.lastControl && textCtrl.value() === e.lastControl.text())) { + textCtrl.value(e.control.text()); + } + + win.find('#href').value(e.control.value()); + } + + function buildLinkList() { + var linkListItems = [{ + text: 'None', + value: '' + }]; + + tinymce.each(linkList, function (link) { + linkListItems.push({ + text: link.text || link.title, + value: link.value || link.url, + menu: link.menu + }); + }); + + return linkListItems; + } + + function buildRelList(relValue) { + var relListItems = [{ + text: 'None', + value: '' + }]; + + tinymce.each(editor.settings.rel_list, function (rel) { + relListItems.push({ + text: rel.text || rel.title, + value: rel.value, + selected: relValue === rel.value + }); + }); + + return relListItems; + } + + function buildTargetList(targetValue) { + var targetListItems = [{ + text: 'None', + value: '' + }]; + + if (!editor.settings.target_list) { + targetListItems.push({ + text: 'New window', + value: '_blank' + }); + } + + tinymce.each(editor.settings.target_list, function (target) { + targetListItems.push({ + text: target.text || target.title, + value: target.value, + selected: targetValue === target.value + }); + }); + + return targetListItems; + } + + function buildAnchorListControl(url) { + var anchorList = []; + + tinymce.each(editor.dom.select('a:not([href])'), function (anchor) { + var id = anchor.name || anchor.id; + + if (id) { + anchorList.push({ + text: id, + value: '#' + id, + selected: url.indexOf('#' + id) !== -1 + }); + } + }); + + if (anchorList.length) { + anchorList.unshift({ + text: 'None', + value: '' + }); + + return { + name: 'anchor', + type: 'listbox', + label: 'Anchors', + values: anchorList, + onselect: linkListChangeHandler + }; + } + } + + function updateText() { + if (!initialText && data.text.length === 0) { + this.parent().parent().find('#text')[0].value(this.value()); + } + } + + selectedElm = selection.getNode(); + anchorElm = dom.getParent(selectedElm, 'a[href]'); + + data.text = initialText = anchorElm ? (anchorElm.innerText || anchorElm.textContent) : selection.getContent({ + format: 'text' + }); + data.href = anchorElm ? dom.getAttrib(anchorElm, 'href') : ''; + data.target = anchorElm ? dom.getAttrib(anchorElm, 'target') : ''; + data.rel = anchorElm ? dom.getAttrib(anchorElm, 'rel') : ''; + + if (selectedElm.nodeName === "IMG") { + data.text = initialText = " "; + } + + if (linkList) { + linkListCtrl = { + type: 'listbox', + label: 'Link list', + values: buildLinkList(), + onselect: linkListChangeHandler + }; + } + + if (editor.settings.target_list !== false) { + targetListCtrl = { + name: 'target', + type: 'listbox', + label: 'Target', + values: buildTargetList(data.target) + }; + } + + if (editor.settings.rel_list) { + relListCtrl = { + name: 'rel', + type: 'listbox', + label: 'Rel', + values: buildRelList(data.rel) + }; + } + + var currentTarget = null; + + //if we already have a link selected, we want to pass that data over to the dialog + if (anchorElm) { + var anchor = $(anchorElm); + currentTarget = { + name: anchor.attr("title"), + url: anchor.attr("href"), + target: anchor.attr("target") + }; + + // drop the lead char from the anchor text, if it has a value + var anchorVal = anchor[0].dataset.anchor; + if (anchorVal) { + currentTarget.anchor = anchorVal.substring(1); + } + + //locallink detection, we do this here, to avoid poluting the dialogservice + //so the dialog service can just expect to get a node-like structure + if (currentTarget.url.indexOf("localLink:") > 0) { + // if the current link has an anchor, it needs to be considered when getting the udi/id + // if an anchor exists, reduce the substring max by its length plus two to offset the removed prefix and trailing curly brace + var linkId = currentTarget.url.substring(currentTarget.url.indexOf(":") + 1, currentTarget.url.lastIndexOf("}")); + + //we need to check if this is an INT or a UDI + var parsedIntId = parseInt(linkId, 10); + if (isNaN(parsedIntId)) { + //it's a UDI + currentTarget.udi = linkId; + } else { + currentTarget.id = linkId; + } + } + } + + if (onClick) { + onClick(currentTarget, anchorElm); + } + + } + + editor.addButton('link', { + icon: 'link', + tooltip: 'Insert/edit link', + shortcut: 'Ctrl+K', + onclick: createLinkList(showDialog), + stateSelector: 'a[href]' + }); + + editor.addButton('unlink', { + icon: 'unlink', + tooltip: 'Remove link', + cmd: 'unlink', + stateSelector: 'a[href]' + }); + + editor.addShortcut('Ctrl+K', '', createLinkList(showDialog)); + this.showDialog = showDialog; + + editor.addMenuItem('link', { + icon: 'link', + text: 'Insert link', + shortcut: 'Ctrl+K', + onclick: createLinkList(showDialog), + stateSelector: 'a[href]', + context: 'insert', + prependToContext: true + }); + + }, + + insertLinkInEditor: function (editor, target, anchorElm) { + + var href = target.url; + // We want to use the Udi. If it is set, we use it, else fallback to id, and finally to null + var hasUdi = target.udi ? true : false; + var id = hasUdi ? target.udi : (target.id ? target.id : null); + + // if an anchor exists, check that it is appropriately prefixed + if (target.anchor && target.anchor[0] !== '?' && target.anchor[0] !== '#') { + target.anchor = (target.anchor.indexOf('=') === -1 ? '#' : '?') + target.anchor; + } + + // the href might be an external url, so check the value for an anchor/qs + // href has the anchor re-appended later, hence the reset here to avoid duplicating the anchor + if (!target.anchor) { + var urlParts = href.split(/(#|\?)/); + if (urlParts.length === 3) { + href = urlParts[0]; + target.anchor = urlParts[1] + urlParts[2]; + } + } + + //Create a json obj used to create the attributes for the tag + function createElemAttributes() { + var a = { + href: href, + title: target.name, + target: target.target ? target.target : null, + rel: target.rel ? target.rel : null + }; + + if (hasUdi) { + a["data-udi"] = target.udi; + } else if (target.id) { + a["data-id"] = target.id; + } + + if (target.anchor) { + a["data-anchor"] = target.anchor; + a.href = a.href + target.anchor; + } else { + a["data-anchor"] = null; + } + + return a; + } + + function insertLink() { + if (anchorElm) { + editor.dom.setAttribs(anchorElm, createElemAttributes()); + + editor.selection.select(anchorElm); + editor.execCommand('mceEndTyping'); + } else { + editor.execCommand('mceInsertLink', false, createElemAttributes()); + } + } + + if (!href && !target.anchor) { + editor.execCommand('unlink'); + return; + } + + //if we have an id, it must be a locallink:id, aslong as the isMedia flag is not set + if (id && (angular.isUndefined(target.isMedia) || !target.isMedia)) { + + href = "/{localLink:" + id + "}"; + + insertLink(); + return; + } + + if (!href) { + href = ""; + } + + // Is email and not //user@domain.com and protocol (e.g. mailto:, sip:) is not specified + if (href.indexOf('@') > 0 && href.indexOf('//') === -1 && href.indexOf(':') === -1) { + // assume it's a mailto link + href = 'mailto:' + href; + insertLink(); + return; + } + + // Is www. prefixed + if (/^\s*www\./i.test(href)) { + href = 'http://' + href; + insertLink(); + return; + } + + insertLink(); + + } + + }; +} + +angular.module('umbraco.services').factory('tinyMceService', tinyMceService); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js index 67d62de223..b1fa038c44 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js @@ -1,6 +1,6 @@ //used for the media picker dialog angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", - function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) { + function ($scope, eventsService, dialogService, entityResource, mediaHelper, userService, localizationService, tinyMceService) { var dialogOptions = $scope.dialogOptions; var searchText = "Search..."; @@ -40,10 +40,10 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", } // if a link exists, get the properties to build the anchor name list - contentResource.getById(id).then(function (resp) { - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - $scope.target.url = resp.urls[0]; - }); + entityResource.getUrlAndAnchors(id).then(function(resp){ + $scope.anchorValues = resp.anchorValues; + $scope.target.url = resp.url; + }); } else if ($scope.target.url.length) { // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs // only do the substring if there's a # or a ? @@ -88,10 +88,10 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", if (args.node.id < 0) { $scope.target.url = "/"; } else { - contentResource.getById(args.node.id).then(function (resp) { - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - $scope.target.url = resp.urls[0]; - }); + entityResource.getUrlAndAnchors(args.node.id).then(function(resp){ + $scope.anchorValues = resp.anchorValues; + $scope.target.url = resp.url; + }); } if (!angular.isUndefined($scope.target.isMedia)) { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js index 8a1c3eb9a9..787fe4186e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js @@ -1,6 +1,6 @@ //used for the media picker dialog angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", - function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) { + function ($scope, eventsService, dialogService, entityResource, mediaHelper, userService, localizationService, tinyMceService) { var dialogOptions = $scope.model; var searchText = "Search..."; @@ -45,10 +45,10 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", }); }); - // if a link exists, get the properties to build the anchor name list - contentResource.getById(id, { dataTypeId: dialogOptions.dataTypeId }).then(function (resp) { - $scope.model.target.url = resp.urls[0]; - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); + + entityResource.getUrlAndAnchors(id).then(function(resp){ + $scope.anchorValues = resp.anchorValues; + $scope.model.target.url = resp.url; }); } } else if ($scope.model.target.url.length) { @@ -88,9 +88,9 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", if (args.node.id < 0) { $scope.model.target.url = "/"; } else { - contentResource.getById(args.node.id, { dataTypeId: dialogOptions.dataTypeId }).then(function (resp) { - $scope.model.target.url = resp.urls[0]; - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); + entityResource.getUrlAndAnchors(args.node.id).then(function(resp){ + $scope.anchorValues = resp.anchorValues; + $scope.model.target.url = resp.url; }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js index e3e5d5e072..30ea1eaef6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js @@ -12,19 +12,22 @@ function openLinkPicker(editor, currentTarget, anchorElement) { - vm.linkPickerOverlay = { - view: "linkpicker", - currentTarget: currentTarget, - anchors: tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)), - dataTypeId: $scope.model.dataTypeId, - ignoreUserStartNodes : $scope.model.config.ignoreUserStartNodes, - show: true, - submit: function(model) { - tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); - vm.linkPickerOverlay.show = false; - vm.linkPickerOverlay = null; - } - }; + entityResource.getAnchors($scope.model.value).then(function(anchorValues) { + vm.linkPickerOverlay = { + view: "linkpicker", + currentTarget: currentTarget, + anchors: anchorValues, + dataTypeId: $scope.model.dataTypeId, + ignoreUserStartNodes : $scope.model.config.ignoreUserStartNodes, + show: true, + submit: function(model) { + tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); + vm.linkPickerOverlay.show = false; + vm.linkPickerOverlay = null; + } + }; + }); + } function openMediaPicker(editor, currentTarget, userData) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index cadbc5f57a..dd9fb404c1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -1,6 +1,6 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.RTEController", - function ($rootScope, $scope, $q, $locale, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService, editorState) { + function ($rootScope, $scope, $q, $locale, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService, editorState, entityResource) { $scope.isLoading = true; @@ -270,19 +270,25 @@ angular.module("umbraco") }); tinyMceService.createLinkPicker(editor, $scope, function(currentTarget, anchorElement) { - $scope.linkPickerOverlay = { - view: "linkpicker", - currentTarget: currentTarget, - anchors: editorState.current ? tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)) : [], - dataTypeId: $scope.model.dataTypeId, - ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes, - show: true, - submit: function(model) { - tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); - $scope.linkPickerOverlay.show = false; - $scope.linkPickerOverlay = null; - } - }; + + entityResource.getAnchors($scope.model.value).then(function(anchorValues){ + $scope.linkPickerOverlay = { + view: "linkpicker", + currentTarget: currentTarget, + anchors: anchorValues, + dataTypeId: $scope.model.dataTypeId, + ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes, + show: true, + submit: function(model) { + tinyMceService.insertLinkInEditor(editor, model.target, anchorElement); + $scope.linkPickerOverlay.show = false; + $scope.linkPickerOverlay = null; + } + }; + }); + + + }); //Create the insert media plugin diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 36f4465e96..d6e997b57e 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -289,6 +289,26 @@ namespace Umbraco.Web.Editors publishedContentExists: i => Umbraco.TypedContent(i) != null); } + + [HttpGet] + public UrlAndAnchors GetUrlAndAnchors(int id) + { + var x = GetResultForId(id, UmbracoEntityTypes.Document); + + var url = Umbraco.Url(id); + var anchorValues = Services.ContentService.GetAnchorValuesFromRTEs(id); + return new UrlAndAnchors(url, anchorValues); + } + + [HttpPost] + public IList GetAnchors(string rteContent) + { + + var anchorValues = Services.ContentService.GetAnchorValuesFromRTEContent(rteContent); + return anchorValues; + } + + #region GetById /// diff --git a/src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs b/src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs new file mode 100644 index 0000000000..a8cf2e26ee --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "urlAndAnchors", Namespace = "")] + public class UrlAndAnchors + { + public UrlAndAnchors(string url, IList anchorValues) + { + Url = url; + AnchorValues = anchorValues; + } + + [DataMember(Name = "url")] + public string Url { get; } + + [DataMember(Name = "anchorValues")] + public IList AnchorValues { get; } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 90624df5f2..d4fd27c323 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1,2027 +1,2028 @@ - - - - - 9.0.30729 - 2.0 - {651E1350-91B6-44B7-BD60-7207006D7003} - Debug - AnyCPU - - - - - umbraco - - - JScript - Grid - IE50 - false - Library - Umbraco.Web - OnBuildSuccess - - - - - - - - - - - - - - - 4.0 - v4.5.2 - - ..\ - true - latest - - - bin\Debug\ - false - 285212672 - false - - - DEBUG;TRACE - - - true - 4096 - false - - - false - false - false - false - 4 - full - prompt - AllRules.ruleset - false - Off - latest - - - bin\Release\ - false - 285212672 - false - - - TRACE - bin\Release\umbraco.xml - true - 4096 - false - - - true - false - false - false - 4 - pdbonly - prompt - AllRules.ruleset - false - Off - - - - {07fbc26b-2927-4a22-8d96-d644c667fecc} - UmbracoExamine - - - ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll - True - - - ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll - True - - - ..\packages\ClientDependency.1.9.7\lib\net45\ClientDependency.Core.dll - - - ..\packages\dotless.1.5.2\lib\dotless.Core.dll - - - ..\packages\Examine.0.1.90\lib\net45\Examine.dll - - - ..\packages\HtmlAgilityPack.1.8.8\lib\Net45\HtmlAgilityPack.dll - - - ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - - - ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll - - - ..\packages\Markdown.1.14.7\lib\net45\MarkdownSharp.dll - True - - - ..\packages\Microsoft.AspNet.Identity.Core.2.2.2\lib\net45\Microsoft.AspNet.Identity.Core.dll - - - ..\packages\Microsoft.AspNet.Identity.Owin.2.2.2\lib\net45\Microsoft.AspNet.Identity.Owin.dll - - - ..\packages\Microsoft.AspNet.SignalR.Core.2.4.1\lib\net45\Microsoft.AspNet.SignalR.Core.dll - - - - ..\packages\Microsoft.Owin.4.0.1\lib\net45\Microsoft.Owin.dll - - - ..\packages\Microsoft.Owin.Host.SystemWeb.4.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - - - ..\packages\Microsoft.Owin.Security.4.0.1\lib\net45\Microsoft.Owin.Security.dll - - - ..\packages\Microsoft.Owin.Security.Cookies.4.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll - - - ..\packages\Microsoft.Owin.Security.OAuth.4.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll - - - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - True - - - ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll - True - - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - ..\packages\semver.1.1.2\lib\net451\Semver.dll - - - System - - - - - - System.Data - - - - - System.Drawing - - - - - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll - - - - - - ..\packages\System.Threading.Tasks.Dataflow.4.9.0\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll - - - ..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll - - - - 3.5 - - - - - - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.Helpers.dll - - - ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll - - - ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.dll - - - ..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll - - - ..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll - - - System.Web.Services - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.dll - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Deployment.dll - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Razor.dll - - - - System.XML - - - - {5BA5425F-27A7-4677-865E-82246498AA2E} - SqlCE4Umbraco - - - {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} - Umbraco.Core - - - {6EDD2061-82F2-461B-BB6E-879245A832DE} - umbraco.controls - - - umbraco.businesslogic - {E469A9CE-1BEC-423F-AC44-713CD72457EA} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - {CCD75EC3-63DB-4184-B49D-51C1DD337230} - umbraco.cms - - - {C7CB79F0-1C97-4B33-BFA7-00731B579AE2} - umbraco.datalayer - - - umbraco.interfaces - {511F6D8D-7717-440A-9A57-A507E9A8B27F} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - {D7636876-0756-43CB-A192-138C6F0D5E42} - umbraco.providers - - - - - Properties\SolutionInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - True - True - Reference.map - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - AssignDomain2.aspx - ASPXCodeBehind - - - AssignDomain2.aspx - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - True - True - Strings.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - - Code - - - Code - - - Code - - - Code - - - Code - - - - Code - - - True - True - Settings.settings - - - Code - - - - - Code - - - Code - - - Code - - - Code - - - - ASPXCodeBehind - - - Code - - - Code - - - Code - - - - delete.aspx - ASPXCodeBehind - - - delete.aspx - - - editContent.aspx - ASPXCodeBehind - - - editContent.aspx - - - preview.aspx - ASPXCodeBehind - - - preview.aspx - - - publish.aspx - ASPXCodeBehind - - - publish.aspx - - - - - - ASPXCodeBehind - - - - - - - ProgressBar.ascx - ASPXCodeBehind - - - ProgressBar.ascx - - - - ASPXCodeBehind - - - Component - - - - - Code - - - - - - - - - - FeedProxy.aspx - ASPXCodeBehind - - - FeedProxy.aspx - - - EditRelationType.aspx - ASPXCodeBehind - - - EditRelationType.aspx - - - NewRelationType.aspx - ASPXCodeBehind - - - NewRelationType.aspx - - - - RelationTypesWebService.asmx - Component - - - - - Preview.aspx - ASPXCodeBehind - - - Preview.aspx - - - MemberSearch.ascx - ASPXCodeBehind - - - MemberSearch.ascx - - - - - - xsltVisualize.aspx - ASPXCodeBehind - - - xsltVisualize.aspx - - - insertMasterpageContent.aspx - ASPXCodeBehind - - - insertMasterpageContent.aspx - - - insertMasterpagePlaceholder.aspx - ASPXCodeBehind - - - insertMasterpagePlaceholder.aspx - - - republish.aspx - ASPXCodeBehind - - - republish.aspx - - - search.aspx - ASPXCodeBehind - - - search.aspx - - - SendPublish.aspx - ASPXCodeBehind - - - SendPublish.aspx - - - Code - - - Code - - - assemblyBrowser.aspx - ASPXCodeBehind - - - assemblyBrowser.aspx - - - editPackage.aspx - ASPXCodeBehind - - - editPackage.aspx - - - getXsltStatus.asmx - Component - - - xsltChooseExtension.aspx - ASPXCodeBehind - - - xsltChooseExtension.aspx - - - xsltInsertValueOf.aspx - ASPXCodeBehind - - - xsltInsertValueOf.aspx - - - exportDocumenttype.aspx - ASPXCodeBehind - - - importDocumenttype.aspx - ASPXCodeBehind - - - rollBack.aspx - ASPXCodeBehind - - - rollBack.aspx - - - sendToTranslation.aspx - ASPXCodeBehind - - - sendToTranslation.aspx - - - viewAuditTrail.aspx - ASPXCodeBehind - - - viewAuditTrail.aspx - - - EditMemberGroup.aspx - ASPXCodeBehind - - - EditMemberGroup.aspx - - - search.aspx - ASPXCodeBehind - - - search.aspx - - - ViewMembers.aspx - ASPXCodeBehind - - - ViewMembers.aspx - - - Code - - - - - - - - - - tinymce3tinymceCompress.aspx - ASPXCodeBehind - - - tinymce3tinymceCompress.aspx - - - QuickSearchHandler.ashx - - - DictionaryItemList.aspx - ASPXCodeBehind - - - DictionaryItemList.aspx - - - editLanguage.aspx - ASPXCodeBehind - - - editLanguage.aspx - - - - - - Code - - - - - - - - - - - - - - - - - MacroContainerService.asmx - Component - - - TagsAutoCompleteHandler.ashx - - - TreeClientService.asmx - Component - - - - - - - - True - True - Resources.resx - - - default.aspx - ASPXCodeBehind - - - default.aspx - - - details.aspx - ASPXCodeBehind - - - details.aspx - - - preview.aspx - ASPXCodeBehind - - - preview.aspx - - - xml.aspx - ASPXCodeBehind - - - xml.aspx - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - TreeDataService.ashx - - - - - - - XmlTree.xsd - - - - - CacheRefresher.asmx - Component - - - CheckForUpgrade.asmx - Component - - - CMSNode.asmx - Component - - - codeEditorSave.asmx - Component - - - legacyAjaxCalls.asmx - Component - - - nodeSorter.asmx - Component - - - progressStatus.asmx - Component - - - publication.asmx - Component - - - - ASPXCodeBehind - - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - - - - - - True - True - Reference.map - - - - - - - - - - - - Component - - - - Component - - - - - Mvc\web.config - - - - MSDiscoCodeGenerator - Reference.cs - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - ASPXCodeBehind - - - - - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - - - - - - - Reference.map - - - Reference.map - - - SettingsSingleFileGenerator - Settings1.Designer.cs - - - - - - ASPXCodeBehind - - - - - - - - - - Form - - - Designer - - - - - Form - - - - - - - Form - - - - - - - - - - XmlTree.xsd - - - - - - MSDiscoCodeGenerator - Reference.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - ResXFileCodeGenerator - Strings.Designer.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - - - - package.xsd - - - umbraco.xsd - - - umbraco.xsd - - - umbraco.xsd - - - - - - - - Dynamic - Web References\org.umbraco.our\ - https://our.umbraco.com/umbraco/webservices/api/repository.asmx - - - - - Settings - umbraco_org_umbraco_our_Repository - - - Dynamic - Web References\org.umbraco.update\ - http://update.umbraco.org/checkforupgrade.asmx - - - - - Settings - umbraco_org_umbraco_update_CheckForUpgrade - - - - - - - 11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v15.0 - - - - - - - - - - - - - - - + + + + + 9.0.30729 + 2.0 + {651E1350-91B6-44B7-BD60-7207006D7003} + Debug + AnyCPU + + + + + umbraco + + + JScript + Grid + IE50 + false + Library + Umbraco.Web + OnBuildSuccess + + + + + + + + + + + + + + + 4.0 + v4.5.2 + + ..\ + true + latest + + + bin\Debug\ + false + 285212672 + false + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + false + 4 + full + prompt + AllRules.ruleset + false + Off + latest + + + bin\Release\ + false + 285212672 + false + + + TRACE + bin\Release\umbraco.xml + true + 4096 + false + + + true + false + false + false + 4 + pdbonly + prompt + AllRules.ruleset + false + Off + + + + {07fbc26b-2927-4a22-8d96-d644c667fecc} + UmbracoExamine + + + ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll + True + + + ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll + True + + + ..\packages\ClientDependency.1.9.7\lib\net45\ClientDependency.Core.dll + + + ..\packages\dotless.1.5.2\lib\dotless.Core.dll + + + ..\packages\Examine.0.1.90\lib\net45\Examine.dll + + + ..\packages\HtmlAgilityPack.1.8.8\lib\Net45\HtmlAgilityPack.dll + + + ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll + + + ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll + + + ..\packages\Markdown.1.14.7\lib\net45\MarkdownSharp.dll + True + + + ..\packages\Microsoft.AspNet.Identity.Core.2.2.2\lib\net45\Microsoft.AspNet.Identity.Core.dll + + + ..\packages\Microsoft.AspNet.Identity.Owin.2.2.2\lib\net45\Microsoft.AspNet.Identity.Owin.dll + + + ..\packages\Microsoft.AspNet.SignalR.Core.2.4.1\lib\net45\Microsoft.AspNet.SignalR.Core.dll + + + + ..\packages\Microsoft.Owin.4.0.1\lib\net45\Microsoft.Owin.dll + + + ..\packages\Microsoft.Owin.Host.SystemWeb.4.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + + + ..\packages\Microsoft.Owin.Security.4.0.1\lib\net45\Microsoft.Owin.Security.dll + + + ..\packages\Microsoft.Owin.Security.Cookies.4.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll + + + ..\packages\Microsoft.Owin.Security.OAuth.4.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll + + + ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + True + + + ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll + True + + + ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + ..\packages\semver.1.1.2\lib\net451\Semver.dll + + + System + + + + + + System.Data + + + + + System.Drawing + + + + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll + + + + + + ..\packages\System.Threading.Tasks.Dataflow.4.9.0\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll + + + + 3.5 + + + + + + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.Helpers.dll + + + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll + + + ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.dll + + + ..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll + + + ..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll + + + System.Web.Services + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.dll + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Deployment.dll + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Razor.dll + + + + System.XML + + + + {5BA5425F-27A7-4677-865E-82246498AA2E} + SqlCE4Umbraco + + + {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} + Umbraco.Core + + + {6EDD2061-82F2-461B-BB6E-879245A832DE} + umbraco.controls + + + umbraco.businesslogic + {E469A9CE-1BEC-423F-AC44-713CD72457EA} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + {CCD75EC3-63DB-4184-B49D-51C1DD337230} + umbraco.cms + + + {C7CB79F0-1C97-4B33-BFA7-00731B579AE2} + umbraco.datalayer + + + umbraco.interfaces + {511F6D8D-7717-440A-9A57-A507E9A8B27F} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + {D7636876-0756-43CB-A192-138C6F0D5E42} + umbraco.providers + + + + + Properties\SolutionInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + True + True + Reference.map + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + AssignDomain2.aspx + ASPXCodeBehind + + + AssignDomain2.aspx + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + True + True + Strings.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + + Code + + + Code + + + Code + + + Code + + + Code + + + + Code + + + True + True + Settings.settings + + + Code + + + + + Code + + + Code + + + Code + + + Code + + + + ASPXCodeBehind + + + Code + + + Code + + + Code + + + + delete.aspx + ASPXCodeBehind + + + delete.aspx + + + editContent.aspx + ASPXCodeBehind + + + editContent.aspx + + + preview.aspx + ASPXCodeBehind + + + preview.aspx + + + publish.aspx + ASPXCodeBehind + + + publish.aspx + + + + + + ASPXCodeBehind + + + + + + + ProgressBar.ascx + ASPXCodeBehind + + + ProgressBar.ascx + + + + ASPXCodeBehind + + + Component + + + + + Code + + + + + + + + + + FeedProxy.aspx + ASPXCodeBehind + + + FeedProxy.aspx + + + EditRelationType.aspx + ASPXCodeBehind + + + EditRelationType.aspx + + + NewRelationType.aspx + ASPXCodeBehind + + + NewRelationType.aspx + + + + RelationTypesWebService.asmx + Component + + + + + Preview.aspx + ASPXCodeBehind + + + Preview.aspx + + + MemberSearch.ascx + ASPXCodeBehind + + + MemberSearch.ascx + + + + + + xsltVisualize.aspx + ASPXCodeBehind + + + xsltVisualize.aspx + + + insertMasterpageContent.aspx + ASPXCodeBehind + + + insertMasterpageContent.aspx + + + insertMasterpagePlaceholder.aspx + ASPXCodeBehind + + + insertMasterpagePlaceholder.aspx + + + republish.aspx + ASPXCodeBehind + + + republish.aspx + + + search.aspx + ASPXCodeBehind + + + search.aspx + + + SendPublish.aspx + ASPXCodeBehind + + + SendPublish.aspx + + + Code + + + Code + + + assemblyBrowser.aspx + ASPXCodeBehind + + + assemblyBrowser.aspx + + + editPackage.aspx + ASPXCodeBehind + + + editPackage.aspx + + + getXsltStatus.asmx + Component + + + xsltChooseExtension.aspx + ASPXCodeBehind + + + xsltChooseExtension.aspx + + + xsltInsertValueOf.aspx + ASPXCodeBehind + + + xsltInsertValueOf.aspx + + + exportDocumenttype.aspx + ASPXCodeBehind + + + importDocumenttype.aspx + ASPXCodeBehind + + + rollBack.aspx + ASPXCodeBehind + + + rollBack.aspx + + + sendToTranslation.aspx + ASPXCodeBehind + + + sendToTranslation.aspx + + + viewAuditTrail.aspx + ASPXCodeBehind + + + viewAuditTrail.aspx + + + EditMemberGroup.aspx + ASPXCodeBehind + + + EditMemberGroup.aspx + + + search.aspx + ASPXCodeBehind + + + search.aspx + + + ViewMembers.aspx + ASPXCodeBehind + + + ViewMembers.aspx + + + Code + + + + + + + + + + tinymce3tinymceCompress.aspx + ASPXCodeBehind + + + tinymce3tinymceCompress.aspx + + + QuickSearchHandler.ashx + + + DictionaryItemList.aspx + ASPXCodeBehind + + + DictionaryItemList.aspx + + + editLanguage.aspx + ASPXCodeBehind + + + editLanguage.aspx + + + + + + Code + + + + + + + + + + + + + + + + + MacroContainerService.asmx + Component + + + TagsAutoCompleteHandler.ashx + + + TreeClientService.asmx + Component + + + + + + + + True + True + Resources.resx + + + default.aspx + ASPXCodeBehind + + + default.aspx + + + details.aspx + ASPXCodeBehind + + + details.aspx + + + preview.aspx + ASPXCodeBehind + + + preview.aspx + + + xml.aspx + ASPXCodeBehind + + + xml.aspx + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + TreeDataService.ashx + + + + + + + XmlTree.xsd + + + + + CacheRefresher.asmx + Component + + + CheckForUpgrade.asmx + Component + + + CMSNode.asmx + Component + + + codeEditorSave.asmx + Component + + + legacyAjaxCalls.asmx + Component + + + nodeSorter.asmx + Component + + + progressStatus.asmx + Component + + + publication.asmx + Component + + + + ASPXCodeBehind + + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + + + + + + True + True + Reference.map + + + + + + + + + + + + Component + + + + Component + + + + + Mvc\web.config + + + + MSDiscoCodeGenerator + Reference.cs + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + ASPXCodeBehind + + + + + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + + + + + + + Reference.map + + + Reference.map + + + SettingsSingleFileGenerator + Settings1.Designer.cs + + + + + + ASPXCodeBehind + + + + + + + + + + Form + + + Designer + + + + + Form + + + + + + + Form + + + + + + + + + + XmlTree.xsd + + + + + + MSDiscoCodeGenerator + Reference.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + ResXFileCodeGenerator + Strings.Designer.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + + + + + package.xsd + + + umbraco.xsd + + + umbraco.xsd + + + umbraco.xsd + + + + + + + + Dynamic + Web References\org.umbraco.our\ + https://our.umbraco.com/umbraco/webservices/api/repository.asmx + + + + + Settings + umbraco_org_umbraco_our_Repository + + + Dynamic + Web References\org.umbraco.update\ + http://update.umbraco.org/checkforupgrade.asmx + + + + + Settings + umbraco_org_umbraco_update_CheckForUpgrade + + + + + + + 11.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v15.0 + + + + + + + + + + + + + + + \ No newline at end of file From 60ed6f6b20f9830bebfa40933dd059fe94318ad4 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 24 Jun 2019 13:18:20 +0100 Subject: [PATCH 023/776] Remove snupkg aka newer Symbol nuget packages --- build/build.ps1 | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/build/build.ps1 b/build/build.ps1 index 55b686c98e..e4994d2c4a 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -15,7 +15,7 @@ [Parameter(Mandatory=$false)] [Alias("doc")] [switch] $docfx = $false, - + # keep the build directories, don't clear them [Parameter(Mandatory=$false)] [Alias("c")] @@ -392,13 +392,13 @@ &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.Core.nuspec" ` -Properties BuildTmp="$($this.BuildTemp)" ` -Version "$($this.Version.Semver.ToString())" ` - -Symbols -SymbolPackageFormat snupkg -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmscore.log" + -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmscore.log" if (-not $?) { throw "Failed to pack NuGet UmbracoCms.Core." } &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.Web.nuspec" ` -Properties BuildTmp="$($this.BuildTemp)" ` -Version "$($this.Version.Semver.ToString())" ` - -Symbols -SymbolPackageFormat snupkg -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmsweb.log" + -Verbosity detailed -outputDirectory "$($this.BuildOutput)" > "$($this.BuildTemp)\nupack.cmsweb.log" if (-not $?) { throw "Failed to pack NuGet UmbracoCms.Web." } &$this.BuildEnv.NuGet Pack "$nuspecs\UmbracoCms.nuspec" ` @@ -429,37 +429,37 @@ Write-Host "Prepare Azure Gallery" $this.CopyFile("$($this.SolutionRoot)\build\Azure\azuregalleryrelease.ps1", $this.BuildOutput) }) - + $ubuild.DefineMethod("PrepareCSharpDocs", { Write-Host "Prepare C# Documentation" - + $src = "$($this.SolutionRoot)\src" $tmp = $this.BuildTemp $out = $this.BuildOutput $DocFxJson = Join-Path -Path $src "\ApiDocs\docfx.json" $DocFxSiteOutput = Join-Path -Path $tmp "\_site\*.*" - + #restore nuget packages $this.RestoreNuGet() # run DocFx $DocFx = $this.BuildEnv.DocFx - + & $DocFx metadata $DocFxJson & $DocFx build $DocFxJson # zip it & $this.BuildEnv.Zip a -tzip -r "$out\csharp-docs.zip" $DocFxSiteOutput }) - + $ubuild.DefineMethod("PrepareAngularDocs", { Write-Host "Prepare Angular Documentation" - + $src = "$($this.SolutionRoot)\src" $out = $this.BuildOutput - + $this.CompileBelle() "Moving to Umbraco.Web.UI.Client folder" From 966b07ba1c969934b322b0266a6a9e0e1abda015 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 24 Jun 2019 21:12:38 +0200 Subject: [PATCH 024/776] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - Changed entity service to only return small model with media path. And updated media picker to use entity service when loading data. --- .../Repositories/EntityRepository.cs | 1630 ++++++++--------- src/Umbraco.Core/Services/EntityService.cs | 1536 ++++++++-------- src/Umbraco.Core/Services/IEntityService.cs | 578 +++--- src/Umbraco.Core/Services/IRelationService.cs | 604 +++--- src/Umbraco.Core/Services/RelationService.cs | 1488 ++++++++------- .../common/services/mediahelper.service.js | 76 +- .../common/dialogs/mediapicker.controller.js | 274 +-- .../mediaPicker/mediapicker.controller.js | 13 +- .../mediapicker/mediapicker.controller.js | 2 +- .../Models/Mapping/PreValueDisplayResolver.cs | 1 + .../FilterAllowedOutgoingMediaAttribute.cs | 2 +- src/umbraco.cms/businesslogic/web/Access.cs | 1162 ++++++------ 12 files changed, 3569 insertions(+), 3797 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index f28b226932..495c0109ad 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -1,891 +1,747 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Factories; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Persistence.Repositories -{ - /// - /// Represents the EntityRepository used to query objects. - /// - /// - /// This is limited to objects that are based in the umbracoNode-table. - /// - internal class EntityRepository : DisposableObjectSlim, IEntityRepository - { - private readonly IDatabaseUnitOfWork _work; - - public EntityRepository(IDatabaseUnitOfWork work) - { - _work = work; - } - - /// - /// Returns the Unit of Work added to the repository - /// - protected internal IDatabaseUnitOfWork UnitOfWork - { - get { return _work; } - } - - /// - /// Internal for testing purposes - /// - internal Guid UnitKey - { - get { return (Guid)_work.Key; } - } - - #region Query Methods - - public IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectTypeId, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, IQuery filter = null) - { - bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; - bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; - var factory = new UmbracoEntityFactory(); - - var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, sql => - { - if (filter != null) - { - foreach (var filterClause in filter.GetWhereClauses()) - { - sql.Where(filterClause.Item1, filterClause.Item2); - } - } - }, objectTypeId); - var translator = new SqlTranslator(sqlClause, query); - var entitySql = translator.Translate(); +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Factories; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence.Repositories +{ + /// + /// Represents the EntityRepository used to query objects. + /// + /// + /// This is limited to objects that are based in the umbracoNode-table. + /// + internal class EntityRepository : DisposableObjectSlim, IEntityRepository + { + private readonly IDatabaseUnitOfWork _work; + + public EntityRepository(IDatabaseUnitOfWork work) + { + _work = work; + } + + /// + /// Returns the Unit of Work added to the repository + /// + protected internal IDatabaseUnitOfWork UnitOfWork + { + get { return _work; } + } + + /// + /// Internal for testing purposes + /// + internal Guid UnitKey + { + get { return (Guid)_work.Key; } + } + + #region Query Methods + + public IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectTypeId, long pageIndex, int pageSize, out long totalRecords, + string orderBy, Direction orderDirection, IQuery filter = null) + { + bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; + bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; + var factory = new UmbracoEntityFactory(); + + var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, sql => + { + if (filter != null) + { + foreach (var filterClause in filter.GetWhereClauses()) + { + sql.Where(filterClause.Item1, filterClause.Item2); + } + } + }, objectTypeId); + var translator = new SqlTranslator(sqlClause, query); + var entitySql = translator.Translate(); var pagedSql = entitySql.Append(GetGroupBy(isContent, isMedia, false)); pagedSql = (orderDirection == Direction.Descending) ? pagedSql.OrderByDescending("umbracoNode.id") : pagedSql.OrderBy("umbracoNode.id"); - - IEnumerable result; - - if (isMedia) - { - //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag! - var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); - - var ids = pagedResult.Items.Select(x => (int) x.id).InGroupsOf(2000); - var entities = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); - - //Now we need to merge in the property data since we need paging and we can't do this the way that the big media query was working before - foreach (var idGroup in ids) - { - var propSql = GetPropertySql(Constants.ObjectTypes.Media) + + IEnumerable result; + + if (isMedia) + { + //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag! + var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); + + var ids = pagedResult.Items.Select(x => (int) x.id).InGroupsOf(2000); + var entities = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); + + //Now we need to merge in the property data since we need paging and we can't do this the way that the big media query was working before + foreach (var idGroup in ids) + { + var propSql = GetPropertySql(Constants.ObjectTypes.Media) .Where("contentNodeId IN (@ids)", new { ids = idGroup }); propSql = (orderDirection == Direction.Descending) ? propSql.OrderByDescending("contentNodeId") : propSql.OrderBy("contentNodeId"); - - //This does NOT fetch all data into memory in a list, this will read - // over the records as a data reader, this is much better for performance and memory, - // but it means that during the reading of this data set, nothing else can be read - // from SQL server otherwise we'll get an exception. - var allPropertyData = _work.Database.Query(propSql); - - //keep track of the current property data item being enumerated - var propertyDataSetEnumerator = allPropertyData.GetEnumerator(); - var hasCurrent = false; // initially there is no enumerator.Current - - try - { - //This must be sorted by node id (which is done by SQL) because this is how we are sorting the query to lookup property types above, - // which allows us to more efficiently iterate over the large data set of property values. - foreach (var entity in entities) - { - // assemble the dtos for this def - // use the available enumerator.Current if any else move to next - while (hasCurrent || propertyDataSetEnumerator.MoveNext()) - { - if (propertyDataSetEnumerator.Current.contentNodeId == entity.Id) - { - hasCurrent = false; // enumerator.Current is not available - - //the property data goes into the additional data - entity.AdditionalData[propertyDataSetEnumerator.Current.propertyTypeAlias] = new UmbracoEntity.EntityProperty - { - PropertyEditorAlias = propertyDataSetEnumerator.Current.propertyEditorAlias, - Value = StringExtensions.IsNullOrWhiteSpace(propertyDataSetEnumerator.Current.dataNtext) - ? propertyDataSetEnumerator.Current.dataNvarchar - : StringExtensions.ConvertToJsonIfPossible(propertyDataSetEnumerator.Current.dataNtext) - }; - } - else - { - hasCurrent = true; // enumerator.Current is available for another def - break; // no more propertyDataDto for this def - } - } - } - } - finally - { - propertyDataSetEnumerator.Dispose(); - } - } - - result = entities; - } - else - { - var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); - result = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); - } - - //The total items from the PetaPoco page query will be wrong due to the Outer join used on parent, depending on the search this will - //return duplicate results when the COUNT is used in conjuction with it, so we need to get the total on our own. - - //generate a query that does not contain the LEFT Join for parent, this would cause - //the COUNT(*) query to return the wrong - var sqlCountClause = GetBaseWhere( - (isC, isM, f) => GetBase(isC, isM, f, true), //true == is a count query - isContent, isMedia, sql => - { - if (filter != null) - { - foreach (var filterClause in filter.GetWhereClauses()) - { - sql.Where(filterClause.Item1, filterClause.Item2); - } - } - }, objectTypeId); - var translatorCount = new SqlTranslator(sqlCountClause, query); - var countSql = translatorCount.Translate(); - - totalRecords = _work.Database.ExecuteScalar(countSql); - - return result; - } - - public IUmbracoEntity GetByKey(Guid key) - { - var sql = GetBaseWhere(GetBase, false, false, key); - var nodeDto = _work.Database.FirstOrDefault(sql); - if (nodeDto == null) - return null; - - var factory = new UmbracoEntityFactory(); - var entity = factory.BuildEntityFromDynamic(nodeDto); - - return entity; - } - - public IUmbracoEntity GetByKey(Guid key, Guid objectTypeId) - { - bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; - bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; - - var sql = GetFullSqlForEntityType(key, isContent, isMedia, objectTypeId); - - var factory = new UmbracoEntityFactory(); - - if (isMedia) - { - //for now treat media differently and include all property data too - var entities = _work.Database.Fetch( - new UmbracoEntityRelator().Map, sql); - - return entities.FirstOrDefault(); - } - else - { - - //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(sql); - var collection = new EntityDefinitionCollection(); - foreach (var dto in dtos) - { - collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, false)); - } - var found = collection.FirstOrDefault(); - return found != null ? found.BuildFromDynamic() : null; - } - - - } - - public virtual IUmbracoEntity Get(int id) - { - var sql = GetBaseWhere(GetBase, false, false, id); - var nodeDto = _work.Database.FirstOrDefault(sql); - if (nodeDto == null) - return null; - - var factory = new UmbracoEntityFactory(); - var entity = factory.BuildEntityFromDynamic(nodeDto); - - return entity; - } - - public virtual IUmbracoEntity Get(int id, Guid objectTypeId) - { - bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; - bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; - - var sql = GetFullSqlForEntityType(id, isContent, isMedia, objectTypeId); - - var factory = new UmbracoEntityFactory(); - - if (isMedia) - { - //for now treat media differently and include all property data too - var entities = _work.Database.Fetch( - new UmbracoEntityRelator().Map, sql); - - return entities.FirstOrDefault(); - } - else - { - //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(sql); - var collection = new EntityDefinitionCollection(); - foreach (var dto in dtos) - { - collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, false)); - } - var found = collection.FirstOrDefault(); - return found != null ? found.BuildFromDynamic() : null; - } - } - - public virtual IEnumerable GetAll(Guid objectTypeId, params int[] ids) - { - return ids.Any() - ? PerformGetAll(objectTypeId, sql => sql.Where(" umbracoNode.id in (@ids)", new { ids })) - : PerformGetAll(objectTypeId); - } - - public virtual IEnumerable GetAll(Guid objectTypeId, params Guid[] keys) - { - return keys.Any() - ? PerformGetAll(objectTypeId, sql => sql.Where(" umbracoNode.uniqueID in (@keys)", new { keys })) - : PerformGetAll(objectTypeId); - } - - private IEnumerable PerformGetAll(Guid objectTypeId, Action filter = null) - { - var isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; - var isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; - var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, filter); - - var factory = new UmbracoEntityFactory(); - - if (isMedia) - { - //for now treat media differently and include all property data too - var entities = _work.Database.Fetch( - new UmbracoEntityRelator().Map, sql); - return entities; - } - else - { - //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(sql); - var collection = new EntityDefinitionCollection(); - foreach (var dto in dtos) - { - collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, false)); - } - return collection.Select(x => x.BuildFromDynamic()).ToList(); - } - } - - public virtual IEnumerable GetAllPaths(Guid objectTypeId, params int[] ids) - { - return ids.Any() - ? PerformGetAllPaths(objectTypeId, sql => sql.Append(" AND umbracoNode.id in (@ids)", new { ids })) - : PerformGetAllPaths(objectTypeId); - } - - public virtual IEnumerable GetAllPaths(Guid objectTypeId, params Guid[] keys) - { - return keys.Any() - ? PerformGetAllPaths(objectTypeId, sql => sql.Append(" AND umbracoNode.uniqueID in (@keys)", new { keys })) - : PerformGetAllPaths(objectTypeId); - } - - private IEnumerable PerformGetAllPaths(Guid objectTypeId, Action filter = null) - { - var sql = new Sql("SELECT id, path FROM umbracoNode WHERE umbracoNode.nodeObjectType=@type", new { type = objectTypeId }); - if (filter != null) filter(sql); - return _work.Database.Fetch(sql); - } - - public virtual IEnumerable GetByQuery(IQuery query) - { - var sqlClause = GetBase(false, false, null); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate().Append(GetGroupBy(false, false)); - - var dtos = _work.Database.Fetch(sql); - - var factory = new UmbracoEntityFactory(); - var list = dtos.Select(factory.BuildEntityFromDynamic).Cast().ToList(); - - return list; - } - - /// - /// Gets entities by query. - /// - /// - /// - /// - /// - /// Note that this will also fetch all property data for media items, which can cause performance problems - /// when used without paging, in sites with large amounts of data in cmsPropertyData. - /// - public virtual IEnumerable GetByQuery(IQuery query, Guid objectTypeId) - { - var isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; - var isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; - - var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, null, objectTypeId); - - var translator = new SqlTranslator(sqlClause, query); - var entitySql = translator.Translate(); - - var factory = new UmbracoEntityFactory(); - - if (isMedia) - { - var wheres = query.GetWhereClauses().ToArray(); - - var mediaSql = GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)), sql => - { - //adds the additional filters - foreach (var whereClause in wheres) - { - sql.Where(whereClause.Item1, whereClause.Item2); - } - }); - - //for now treat media differently and include all property data too - var entities = _work.Database.Fetch( - new UmbracoEntityRelator().Map, mediaSql); - return entities; - } - else - { - return GetByQueryInternal(entitySql, isContent, isMedia); - } - } - - /// - /// Gets entities by query without fetching property data. - /// - /// - /// - /// - /// - /// This is supposed to be internal and can be used when getting all entities without paging, without causing - /// performance issues. - /// - internal IEnumerable GetMediaByQueryWithoutPropertyData(IQuery query) - { - var sqlClause = GetBaseWhere(GetBase, false, true, null, UmbracoObjectTypes.Media.GetGuid()); - - var translator = new SqlTranslator(sqlClause, query); - var entitySql = translator.Translate(); - - return GetByQueryInternal(entitySql, false, true); - } - - internal IEnumerable GetByQueryInternal(Sql entitySql, bool isContent, bool isMedia) - { - var factory = new UmbracoEntityFactory(); - - //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, isMedia)); - - //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(finalSql); - var collection = new EntityDefinitionCollection(); - foreach (var dto in dtos) - { - collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, isMedia)); - } - return collection.Select(x => x.BuildFromDynamic()).ToList(); - } - - #endregion - - - #region Sql Statements - - 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))); - } - - 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))); - } - - 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); - } - - private Sql GetPropertySql(string nodeObjectType) - { - var sql = new Sql() - .Select("contentNodeId, versionId, dataNvarchar, dataNtext, propertyEditorAlias, alias as propertyTypeAlias") - .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 = nodeObjectType }); - - return sql; - } - - 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 = GetPropertySql(Constants.ObjectTypes.Media); - - if (filter != null) - { - filter(joinSql); - } - - //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 (") - .Append(entitySql) - .Append(new Sql(") tmpTbl LEFT JOIN (")) - .Append(joinSql) - .Append(new Sql(") as property ON id = property.contentNodeId")) - .OrderBy("sortOrder, id"); - - return wrappedSql; - } - - protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter) - { - return GetBase(isContent, isMedia, customFilter, false); - } - - protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter, bool isCount) - { - var columns = new List(); - if (isCount) - { - columns.Add("COUNT(*)"); - } - else - { - columns.AddRange(new List - { - "umbracoNode.id", - "umbracoNode.trashed", - "umbracoNode.parentID", - "umbracoNode.nodeUser", - "umbracoNode.level", - "umbracoNode.path", - "umbracoNode.sortOrder", - "umbracoNode.uniqueID", - "umbracoNode.text", - "umbracoNode.nodeObjectType", - "umbracoNode.createDate", - "COUNT(parent.parentID) as children" - }); - - if (isContent || isMedia) - { - if (isContent) - { - //only content has/needs this info - columns.Add("published.versionId as publishedVersion"); - columns.Add("document.versionId as newestVersion"); - columns.Add("contentversion.id as versionId"); - } - - columns.Add("contenttype.alias"); - columns.Add("contenttype.icon"); - columns.Add("contenttype.thumbnail"); - columns.Add("contenttype.isContainer"); - } - } - - //Creates an SQL query to return a single row for the entity - - var entitySql = new Sql() - .Select(columns.ToArray()) - .From("umbracoNode umbracoNode"); - - if (isContent || isMedia) - { - entitySql.InnerJoin("cmsContent content").On("content.nodeId = umbracoNode.id"); - - if (isContent) - { - //only content has/needs this info - entitySql - .InnerJoin("cmsDocument document").On("document.nodeId = umbracoNode.id") - .InnerJoin("cmsContentVersion contentversion").On("contentversion.VersionId = document.versionId") - .LeftJoin("(SELECT nodeId, versionId FROM cmsDocument WHERE published = 1) as published") - .On("umbracoNode.id = published.nodeId"); - } - - entitySql.LeftJoin("cmsContentType contenttype").On("contenttype.nodeId = content.contentType"); - } - - if (isCount == false) - { - entitySql.LeftJoin("umbracoNode parent").On("parent.parentID = umbracoNode.id"); - } - - if (customFilter != null) - { - customFilter(entitySql); - } - - return entitySql; - } - - 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 }); - - if (isContent) - { - sql.Where("document.newest = 1"); - } - - return sql; - } - - 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 }); - - if (isContent) - { - sql.Where("document.newest = 1"); - } - - sql.Append(GetGroupBy(isContent, isMedia)); - - return sql; - } - - 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}); - - if (isContent) - { - sql.Where("document.newest = 1"); - } - - sql.Append(GetGroupBy(isContent, isMedia)); - - return sql; - } - - 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", - new {Id = id, NodeObjectType = nodeObjectType}); - - if (isContent) - { - sql.Where("document.newest = 1"); - } - - return sql; - } - - 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", - new { UniqueID = key, NodeObjectType = nodeObjectType }); - - if (isContent) - { - sql.Where("document.newest = 1"); - } - - return sql; - } - - protected virtual Sql GetGroupBy(bool isContent, bool isMedia, bool includeSort = true) - { - var columns = new List - { - "umbracoNode.id", - "umbracoNode.trashed", - "umbracoNode.parentID", - "umbracoNode.nodeUser", - "umbracoNode.level", - "umbracoNode.path", - "umbracoNode.sortOrder", - "umbracoNode.uniqueID", - "umbracoNode.text", - "umbracoNode.nodeObjectType", - "umbracoNode.createDate" - }; - - if (isContent || isMedia) - { - if (isContent) - { - columns.Add("published.versionId"); - columns.Add("document.versionId"); - columns.Add("contentversion.id"); - } - columns.Add("contenttype.alias"); - columns.Add("contenttype.icon"); - columns.Add("contenttype.thumbnail"); - columns.Add("contenttype.isContainer"); - } - - var sql = new Sql() - .GroupBy(columns.ToArray()); - - if (includeSort) - { - sql = sql.OrderBy("umbracoNode.sortOrder"); - } - - return sql; - } - - #endregion - - /// - /// Dispose disposable properties - /// - /// - /// Ensure the unit of work is disposed - /// - protected override void DisposeResources() - { - UnitOfWork.DisposeIfDisposable(); - } - - public bool Exists(Guid key) - { - var sql = new Sql().Select("COUNT(*)").From("umbracoNode").Where("uniqueID=@uniqueID", new {uniqueID = key}); - return _work.Database.ExecuteScalar(sql) > 0; - } - - public bool Exists(int id) - { - var sql = new Sql().Select("COUNT(*)").From("umbracoNode").Where("id=@id", new { id = id }); - return _work.Database.ExecuteScalar(sql) > 0; - } - - #region private classes - - [ExplicitColumns] - internal class UmbracoPropertyDto - { - [Column("propertyEditorAlias")] - public string PropertyEditorAlias { get; set; } - - [Column("propertyTypeAlias")] - public string PropertyAlias { get; set; } - - [Column("dataNvarchar")] - public string NVarcharValue { get; set; } - - [Column("dataNtext")] - public string NTextValue { get; set; } - } - - /// - /// This is a special relator in that it is not returning a DTO but a real resolved entity and that it accepts - /// a dynamic instance. - /// - /// - /// We're doing this because when we query the db, we want to use dynamic so that it returns all available fields not just the ones - /// defined on the entity so we can them to additional data - /// - internal class UmbracoEntityRelator - { - internal UmbracoEntity Current; - private readonly UmbracoEntityFactory _factory = new UmbracoEntityFactory(); - - internal UmbracoEntity Map(dynamic a, UmbracoPropertyDto 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 UmbracoEntity as the current one we're processing - if (Current != null && Current.Key == a.uniqueID) - { - if (p != null && p.PropertyAlias.IsNullOrWhiteSpace() == false) - { - // Add this UmbracoProperty to the current additional data - Current.AdditionalData[p.PropertyAlias] = new UmbracoEntity.EntityProperty - { - PropertyEditorAlias = p.PropertyEditorAlias, - Value = p.NTextValue.IsNullOrWhiteSpace() - ? p.NVarcharValue - : p.NTextValue.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 - // 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) - { - //add the property/create the prop list if null - Current.AdditionalData[p.PropertyAlias] = new UmbracoEntity.EntityProperty - { - PropertyEditorAlias = p.PropertyEditorAlias, - Value = p.NTextValue.IsNullOrWhiteSpace() - ? p.NVarcharValue - : p.NTextValue.ConvertToJsonIfPossible() - }; - } - - // Return the now populated previous UmbracoEntity (or null if first time through) - return prev; - } - } - - private class EntityDefinitionCollection : KeyedCollection - { - protected override int GetKeyForItem(EntityDefinition item) - { - return item.Id; - } - - /// - /// if this key already exists if it does then we need to check - /// if the existing item is 'older' than the new item and if that is the case we'll replace the older one - /// - /// - /// - public bool AddOrUpdate(EntityDefinition item) - { - if (Dictionary == null) - { - base.Add(item); - return true; - } - - var key = GetKeyForItem(item); - EntityDefinition found; - if (TryGetValue(key, out found)) - { - //it already exists and it's older so we need to replace it - if (item.VersionId > found.VersionId) - { - var currIndex = Items.IndexOf(found); - if (currIndex == -1) - throw new IndexOutOfRangeException("Could not find the item in the list: " + found.Id); - - //replace the current one with the newer one - SetItem(currIndex, item); - return true; - } - //could not add or update - return false; - } - - base.Add(item); - return true; - } - - private bool TryGetValue(int key, out EntityDefinition val) - { - if (Dictionary == null) - { - val = null; - return false; - } - return Dictionary.TryGetValue(key, out val); - } - } - - private class EntityDefinition - { - private readonly UmbracoEntityFactory _factory; - private readonly dynamic _entity; - private readonly bool _isContent; - private readonly bool _isMedia; - - public EntityDefinition(UmbracoEntityFactory factory, dynamic entity, bool isContent, bool isMedia) - { - _factory = factory; - _entity = entity; - _isContent = isContent; - _isMedia = isMedia; - } - - public IUmbracoEntity BuildFromDynamic() - { - return _factory.BuildEntityFromDynamic(_entity); - } - - public int Id - { - get { return _entity.id; } - } - - public int VersionId - { - get - { - if (_isContent || _isMedia) - { - return _entity.versionId; - } - return _entity.id; - } - } - } - #endregion - } -} + + //This does NOT fetch all data into memory in a list, this will read + // over the records as a data reader, this is much better for performance and memory, + // but it means that during the reading of this data set, nothing else can be read + // from SQL server otherwise we'll get an exception. + var allPropertyData = _work.Database.Query(propSql); + + //keep track of the current property data item being enumerated + var propertyDataSetEnumerator = allPropertyData.GetEnumerator(); + var hasCurrent = false; // initially there is no enumerator.Current + + try + { + //This must be sorted by node id (which is done by SQL) because this is how we are sorting the query to lookup property types above, + // which allows us to more efficiently iterate over the large data set of property values. + foreach (var entity in entities) + { + // assemble the dtos for this def + // use the available enumerator.Current if any else move to next + while (hasCurrent || propertyDataSetEnumerator.MoveNext()) + { + if (propertyDataSetEnumerator.Current.contentNodeId == entity.Id) + { + hasCurrent = false; // enumerator.Current is not available + + //the property data goes into the additional data + entity.AdditionalData[propertyDataSetEnumerator.Current.propertyTypeAlias] = new UmbracoEntity.EntityProperty + { + PropertyEditorAlias = propertyDataSetEnumerator.Current.propertyEditorAlias, + Value = StringExtensions.IsNullOrWhiteSpace(propertyDataSetEnumerator.Current.dataNtext) + ? propertyDataSetEnumerator.Current.dataNvarchar + : StringExtensions.ConvertToJsonIfPossible(propertyDataSetEnumerator.Current.dataNtext) + }; + } + else + { + hasCurrent = true; // enumerator.Current is available for another def + break; // no more propertyDataDto for this def + } + } + } + } + finally + { + propertyDataSetEnumerator.Dispose(); + } + } + + result = entities; + } + else + { + var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); + result = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); + } + + //The total items from the PetaPoco page query will be wrong due to the Outer join used on parent, depending on the search this will + //return duplicate results when the COUNT is used in conjuction with it, so we need to get the total on our own. + + //generate a query that does not contain the LEFT Join for parent, this would cause + //the COUNT(*) query to return the wrong + var sqlCountClause = GetBaseWhere( + (isC, isM, f) => GetBase(isC, isM, f, true), //true == is a count query + isContent, isMedia, sql => + { + if (filter != null) + { + foreach (var filterClause in filter.GetWhereClauses()) + { + sql.Where(filterClause.Item1, filterClause.Item2); + } + } + }, objectTypeId); + var translatorCount = new SqlTranslator(sqlCountClause, query); + var countSql = translatorCount.Translate(); + + totalRecords = _work.Database.ExecuteScalar(countSql); + + return result; + } + + public IUmbracoEntity GetByKey(Guid key) + { + var sql = GetBaseWhere(GetBase, false, false, key); + var nodeDto = _work.Database.FirstOrDefault(sql); + if (nodeDto == null) + return null; + + var factory = new UmbracoEntityFactory(); + var entity = factory.BuildEntityFromDynamic(nodeDto); + + return entity; + } + + public IUmbracoEntity GetByKey(Guid key, Guid objectTypeId) + { + bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; + bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; + + var sql = GetFullSqlForEntityType(key, isContent, isMedia, objectTypeId); + + var factory = new UmbracoEntityFactory(); + + //query = read forward data reader, do not load everything into mem + var dtos = _work.Database.Query(sql); + var collection = new EntityDefinitionCollection(); + foreach (var dto in dtos) + { + collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, false)); + } + var found = collection.FirstOrDefault(); + return found != null ? found.BuildFromDynamic() : null; + + + } + + public virtual IUmbracoEntity Get(int id) + { + var sql = GetBaseWhere(GetBase, false, false, id); + var nodeDto = _work.Database.FirstOrDefault(sql); + if (nodeDto == null) + return null; + + var factory = new UmbracoEntityFactory(); + var entity = factory.BuildEntityFromDynamic(nodeDto); + + return entity; + } + + public virtual IUmbracoEntity Get(int id, Guid objectTypeId) + { + bool isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; + bool isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; + + var sql = GetFullSqlForEntityType(id, isContent, isMedia, objectTypeId); + + var factory = new UmbracoEntityFactory(); + + //query = read forward data reader, do not load everything into mem + var dtos = _work.Database.Query(sql); + var collection = new EntityDefinitionCollection(); + foreach (var dto in dtos) + { + collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, false)); + } + var found = collection.FirstOrDefault(); + return found != null ? found.BuildFromDynamic() : null; + + } + + public virtual IEnumerable GetAll(Guid objectTypeId, params int[] ids) + { + return ids.Any() + ? PerformGetAll(objectTypeId, sql => sql.Where(" umbracoNode.id in (@ids)", new { ids })) + : PerformGetAll(objectTypeId); + } + + public virtual IEnumerable GetAll(Guid objectTypeId, params Guid[] keys) + { + return keys.Any() + ? PerformGetAll(objectTypeId, sql => sql.Where(" umbracoNode.uniqueID in (@keys)", new { keys })) + : PerformGetAll(objectTypeId); + } + + private IEnumerable PerformGetAll(Guid objectTypeId, Action filter = null) + { + var isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; + var isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; + var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, filter); + + var factory = new UmbracoEntityFactory(); + + //query = read forward data reader, do not load everything into mem + var dtos = _work.Database.Query(sql); + var collection = new EntityDefinitionCollection(); + foreach (var dto in dtos) + { + collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, isMedia)); + } + return collection.Select(x => x.BuildFromDynamic()).ToList(); + } + + public virtual IEnumerable GetAllPaths(Guid objectTypeId, params int[] ids) + { + return ids.Any() + ? PerformGetAllPaths(objectTypeId, sql => sql.Append(" AND umbracoNode.id in (@ids)", new { ids })) + : PerformGetAllPaths(objectTypeId); + } + + public virtual IEnumerable GetAllPaths(Guid objectTypeId, params Guid[] keys) + { + return keys.Any() + ? PerformGetAllPaths(objectTypeId, sql => sql.Append(" AND umbracoNode.uniqueID in (@keys)", new { keys })) + : PerformGetAllPaths(objectTypeId); + } + + private IEnumerable PerformGetAllPaths(Guid objectTypeId, Action filter = null) + { + var sql = new Sql("SELECT id, path FROM umbracoNode WHERE umbracoNode.nodeObjectType=@type", new { type = objectTypeId }); + if (filter != null) filter(sql); + return _work.Database.Fetch(sql); + } + + public virtual IEnumerable GetByQuery(IQuery query) + { + var sqlClause = GetBase(false, false, null); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate().Append(GetGroupBy(false, false)); + + var dtos = _work.Database.Fetch(sql); + + var factory = new UmbracoEntityFactory(); + var list = dtos.Select(factory.BuildEntityFromDynamic).Cast().ToList(); + + return list; + } + + /// + /// Gets entities by query. + /// + /// + /// + /// + /// + /// Note that this will also fetch all property data for media items, which can cause performance problems + /// when used without paging, in sites with large amounts of data in cmsPropertyData. + /// + public virtual IEnumerable GetByQuery(IQuery query, Guid objectTypeId) + { + var isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; + var isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; + + var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, null, objectTypeId); + + var translator = new SqlTranslator(sqlClause, query); + var entitySql = translator.Translate(); + + return GetByQueryInternal(entitySql, isContent, isMedia); + } + + /// + /// Gets entities by query without fetching property data. + /// + /// + /// + /// + /// + /// This is supposed to be internal and can be used when getting all entities without paging, without causing + /// performance issues. + /// + internal IEnumerable GetMediaByQueryWithoutPropertyData(IQuery query) + { + var sqlClause = GetBaseWhere(GetBase, false, true, null, UmbracoObjectTypes.Media.GetGuid()); + + var translator = new SqlTranslator(sqlClause, query); + var entitySql = translator.Translate(); + + return GetByQueryInternal(entitySql, false, true); + } + + internal IEnumerable GetByQueryInternal(Sql entitySql, bool isContent, bool isMedia) + { + var factory = new UmbracoEntityFactory(); + + //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, isMedia)); + + //query = read forward data reader, do not load everything into mem + var dtos = _work.Database.Query(finalSql); + var collection = new EntityDefinitionCollection(); + foreach (var dto in dtos) + { + collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, isMedia)); + } + return collection.Select(x => x.BuildFromDynamic()).ToList(); + } + + #endregion + + + #region Sql Statements + + 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 entitySql.Append(GetGroupBy(isContent, true, false)); + } + + 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 entitySql.Append(GetGroupBy(isContent, true, false)); + } + + 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 entitySql.Append(GetGroupBy(isContent, true, false)); + } + + private Sql GetPropertySql(string nodeObjectType) + { + var sql = new Sql() + .Select("contentNodeId, versionId, dataNvarchar, dataNtext, propertyEditorAlias, alias as propertyTypeAlias") + .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 = nodeObjectType }); + + return sql; + } + + protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter) + { + return GetBase(isContent, isMedia, customFilter, false); + } + + protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter, bool isCount) + { + var columns = new List(); + if (isCount) + { + columns.Add("COUNT(*)"); + } + else + { + columns.AddRange(new List + { + "umbracoNode.id", + "umbracoNode.trashed", + "umbracoNode.parentID", + "umbracoNode.nodeUser", + "umbracoNode.level", + "umbracoNode.path", + "umbracoNode.sortOrder", + "umbracoNode.uniqueID", + "umbracoNode.text", + "umbracoNode.nodeObjectType", + "umbracoNode.createDate", + "COUNT(parent.parentID) as children" + }); + + if (isContent || isMedia) + { + if (isContent) + { + //only content has/needs this info + columns.Add("published.versionId as publishedVersion"); + columns.Add("document.versionId as newestVersion"); + columns.Add("contentversion.id as versionId"); + } + + columns.Add("contenttype.alias"); + columns.Add("contenttype.icon"); + columns.Add("contenttype.thumbnail"); + columns.Add("contenttype.isContainer"); + } + + if (isMedia) + { + columns.Add("media.mediaPath"); + } + } + + //Creates an SQL query to return a single row for the entity + + var entitySql = new Sql() + .Select(columns.ToArray()) + .From("umbracoNode umbracoNode"); + + if (isContent || isMedia) + { + entitySql.InnerJoin("cmsContent content").On("content.nodeId = umbracoNode.id"); + + if (isContent) + { + //only content has/needs this info + entitySql + .InnerJoin("cmsDocument document").On("document.nodeId = umbracoNode.id") + .InnerJoin("cmsContentVersion contentversion").On("contentversion.VersionId = document.versionId") + .LeftJoin("(SELECT nodeId, versionId FROM cmsDocument WHERE published = 1) as published") + .On("umbracoNode.id = published.nodeId"); + } + + if (isMedia) + { + entitySql.InnerJoin("cmsMedia media").On("media.nodeId = umbracoNode.id"); + } + + entitySql.LeftJoin("cmsContentType contenttype").On("contenttype.nodeId = content.contentType"); + } + + + + if (isCount == false) + { + entitySql.LeftJoin("umbracoNode parent").On("parent.parentID = umbracoNode.id"); + } + + if (customFilter != null) + { + customFilter(entitySql); + } + + return entitySql; + } + + 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 }); + + if (isContent) + { + sql.Where("document.newest = 1"); + } + + return sql; + } + + 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 }); + + if (isContent) + { + sql.Where("document.newest = 1"); + } + + sql.Append(GetGroupBy(isContent, isMedia)); + + return sql; + } + + 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}); + + if (isContent) + { + sql.Where("document.newest = 1"); + } + + sql.Append(GetGroupBy(isContent, isMedia)); + + return sql; + } + + 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", + new {Id = id, NodeObjectType = nodeObjectType}); + + if (isContent) + { + sql.Where("document.newest = 1"); + } + + return sql; + } + + 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", + new { UniqueID = key, NodeObjectType = nodeObjectType }); + + if (isContent) + { + sql.Where("document.newest = 1"); + } + + return sql; + } + + protected virtual Sql GetGroupBy(bool isContent, bool isMedia, bool includeSort = true) + { + var columns = new List + { + "umbracoNode.id", + "umbracoNode.trashed", + "umbracoNode.parentID", + "umbracoNode.nodeUser", + "umbracoNode.level", + "umbracoNode.path", + "umbracoNode.sortOrder", + "umbracoNode.uniqueID", + "umbracoNode.text", + "umbracoNode.nodeObjectType", + "umbracoNode.createDate" + }; + + if (isContent || isMedia) + { + if (isContent) + { + columns.Add("published.versionId"); + columns.Add("document.versionId"); + columns.Add("contentversion.id"); + } + + if (isMedia) + { + columns.Add("media.mediaPath"); + } + + columns.Add("contenttype.alias"); + columns.Add("contenttype.icon"); + columns.Add("contenttype.thumbnail"); + columns.Add("contenttype.isContainer"); + } + + var sql = new Sql() + .GroupBy(columns.ToArray()); + + if (includeSort) + { + sql = sql.OrderBy("umbracoNode.sortOrder"); + } + + return sql; + } + + #endregion + + /// + /// Dispose disposable properties + /// + /// + /// Ensure the unit of work is disposed + /// + protected override void DisposeResources() + { + UnitOfWork.DisposeIfDisposable(); + } + + public bool Exists(Guid key) + { + var sql = new Sql().Select("COUNT(*)").From("umbracoNode").Where("uniqueID=@uniqueID", new {uniqueID = key}); + return _work.Database.ExecuteScalar(sql) > 0; + } + + public bool Exists(int id) + { + var sql = new Sql().Select("COUNT(*)").From("umbracoNode").Where("id=@id", new { id = id }); + return _work.Database.ExecuteScalar(sql) > 0; + } + + #region private classes + + + private class EntityDefinitionCollection : KeyedCollection + { + protected override int GetKeyForItem(EntityDefinition item) + { + return item.Id; + } + + /// + /// if this key already exists if it does then we need to check + /// if the existing item is 'older' than the new item and if that is the case we'll replace the older one + /// + /// + /// + public bool AddOrUpdate(EntityDefinition item) + { + if (Dictionary == null) + { + base.Add(item); + return true; + } + + var key = GetKeyForItem(item); + EntityDefinition found; + if (TryGetValue(key, out found)) + { + //it already exists and it's older so we need to replace it + if (item.VersionId > found.VersionId) + { + var currIndex = Items.IndexOf(found); + if (currIndex == -1) + throw new IndexOutOfRangeException("Could not find the item in the list: " + found.Id); + + //replace the current one with the newer one + SetItem(currIndex, item); + return true; + } + //could not add or update + return false; + } + + base.Add(item); + return true; + } + + private bool TryGetValue(int key, out EntityDefinition val) + { + if (Dictionary == null) + { + val = null; + return false; + } + return Dictionary.TryGetValue(key, out val); + } + } + + private class EntityDefinition + { + private readonly UmbracoEntityFactory _factory; + private readonly dynamic _entity; + private readonly bool _isContent; + private readonly bool _isMedia; + + public EntityDefinition(UmbracoEntityFactory factory, dynamic entity, bool isContent, bool isMedia) + { + _factory = factory; + _entity = entity; + _isContent = isContent; + _isMedia = isMedia; + } + + public IUmbracoEntity BuildFromDynamic() + { + return _factory.BuildEntityFromDynamic(_entity); + } + + public int Id + { + get { return _entity.id; } + } + + public int VersionId + { + get + { + if (_isContent || _isMedia) + { + return _entity.versionId; + } + return _entity.id; + } + } + } + #endregion + } +} diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index 1e44bec909..906ece1081 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -1,802 +1,742 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using Umbraco.Core.CodeAnnotations; -using Umbraco.Core.Events; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Services -{ - public class EntityService : ScopeRepositoryService, IEntityService - { - private readonly Dictionary>> _supportedObjectTypes; - private readonly IdkMap _idkMap; - - public EntityService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, - IContentService contentService, IContentTypeService contentTypeService, IMediaService mediaService, IDataTypeService dataTypeService, - IMemberService memberService, IMemberTypeService memberTypeService, IdkMap idkMap) - : base(provider, repositoryFactory, logger, eventMessagesFactory) - { - _idkMap = idkMap; - - _supportedObjectTypes = new Dictionary>> - { - {typeof (IDataTypeDefinition).FullName, new Tuple>(UmbracoObjectTypes.DataType, dataTypeService.GetDataTypeDefinitionById)}, - {typeof (IContent).FullName, new Tuple>(UmbracoObjectTypes.Document, contentService.GetById)}, - {typeof (IContentType).FullName, new Tuple>(UmbracoObjectTypes.DocumentType, contentTypeService.GetContentType)}, - {typeof (IMedia).FullName, new Tuple>(UmbracoObjectTypes.Media, mediaService.GetById)}, - {typeof (IMediaType).FullName, new Tuple>(UmbracoObjectTypes.MediaType, contentTypeService.GetMediaType)}, - {typeof (IMember).FullName, new Tuple>(UmbracoObjectTypes.Member, memberService.GetById)}, - {typeof (IMemberType).FullName, new Tuple>(UmbracoObjectTypes.MemberType, memberTypeService.Get)}, - }; - } - - #region Static Queries - - private IQuery _rootEntityQuery; - - #endregion - - internal IdkMap IdkMap { get { return _idkMap; } } - - /// - /// Returns the integer id for a given GUID - /// - /// - /// - /// - public Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType) - { - return _idkMap.GetIdForKey(key, umbracoObjectType); - } - - public Attempt GetIdForUdi(Udi udi) - { - return _idkMap.GetIdForUdi(udi); - } - - /// - /// Returns the GUID for a given integer id - /// - /// - /// - /// - public Attempt GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType) - { - return _idkMap.GetKeyForId(id, umbracoObjectType); - } - - public IUmbracoEntity GetByKey(Guid key, bool loadBaseType = true) - { - if (loadBaseType) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var ret = repository.GetByKey(key); - uow.Commit(); - return ret; - } - } - - //SD: TODO: Need to enable this at some stage ... just need to ask Morten what the deal is with what this does. - throw new NotSupportedException(); - - //var objectType = GetObjectType(key); - //var entityType = GetEntityType(objectType); - //var typeFullName = entityType.FullName; - //var entity = _supportedObjectTypes[typeFullName].Item2(id); - - //return entity; - } - - /// - /// Gets an UmbracoEntity by its Id, and optionally loads the complete object graph. - /// - /// - /// By default this will load the base type with a minimum set of properties. - /// - /// Id of the object to retrieve - /// Optional bool to load the complete object graph when set to False. - /// An - public virtual IUmbracoEntity Get(int id, bool loadBaseType = true) - { - if (loadBaseType) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var ret = repository.Get(id); - return ret; - } - } - - var objectType = GetObjectType(id); - var entityType = GetEntityType(objectType); - var typeFullName = entityType.FullName; - var entity = _supportedObjectTypes[typeFullName].Item2(id); - - return entity; - } - - public IUmbracoEntity GetByKey(Guid key, UmbracoObjectTypes umbracoObjectType, bool loadBaseType = true) - { - if (loadBaseType) - { - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var ret = repository.GetByKey(key, objectTypeId); - return ret; - } - } - - //SD: TODO: Need to enable this at some stage ... just need to ask Morten what the deal is with what this does. - throw new NotSupportedException(); - - //var entityType = GetEntityType(umbracoObjectType); - //var typeFullName = entityType.FullName; - //var entity = _supportedObjectTypes[typeFullName].Item2(id); - - //return entity; - } - - /// - /// Gets an UmbracoEntity by its Id and UmbracoObjectType, and optionally loads the complete object graph. - /// - /// - /// By default this will load the base type with a minimum set of properties. - /// - /// Id of the object to retrieve - /// UmbracoObjectType of the entity to retrieve - /// Optional bool to load the complete object graph when set to False. - /// An - public virtual IUmbracoEntity Get(int id, UmbracoObjectTypes umbracoObjectType, bool loadBaseType = true) - { - if (loadBaseType) - { - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var ret = repository.Get(id, objectTypeId); - return ret; - } - } - - var entityType = GetEntityType(umbracoObjectType); - var typeFullName = entityType.FullName; - var entity = _supportedObjectTypes[typeFullName].Item2(id); - - return entity; - } - - public IUmbracoEntity GetByKey(Guid key, bool loadBaseType = true) where T : IUmbracoEntity - { - throw new NotImplementedException(); - } - - /// - /// Gets an UmbracoEntity by its Id and specified Type. Optionally loads the complete object graph. - /// - /// - /// By default this will load the base type with a minimum set of properties. - /// - /// Type of the model to retrieve. Must be based on an - /// Id of the object to retrieve - /// Optional bool to load the complete object graph when set to False. - /// An - public virtual IUmbracoEntity Get(int id, bool loadBaseType = true) where T : IUmbracoEntity - { - if (loadBaseType) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var ret = repository.Get(id); - return ret; - } - } - - var typeFullName = typeof(T).FullName; - Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => - { - throw new NotSupportedException - ("The passed in type is not supported"); - }); - var entity = _supportedObjectTypes[typeFullName].Item2(id); - - return entity; - } - - /// - /// Gets the parent of entity by its id - /// - /// Id of the entity to retrieve the Parent for - /// An - public virtual IUmbracoEntity GetParent(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var entity = repository.Get(id); - - if (entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21) - return null; - var ret = repository.Get(entity.ParentId); - return ret; - } - } - - /// - /// Gets the parent of entity by its id and UmbracoObjectType - /// - /// Id of the entity to retrieve the Parent for - /// UmbracoObjectType of the parent to retrieve - /// An - public virtual IUmbracoEntity GetParent(int id, UmbracoObjectTypes umbracoObjectType) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var entity = repository.Get(id); - - if (entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21) - return null; - var objectTypeId = umbracoObjectType.GetGuid(); - - var ret = repository.Get(entity.ParentId, objectTypeId); - return ret; - } - } - - /// - /// Gets a collection of children by the parents Id - /// - /// Id of the parent to retrieve children for - /// An enumerable list of objects - public virtual IEnumerable GetChildren(int parentId) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var query = Query.Builder.Where(x => x.ParentId == parentId); - - var contents = repository.GetByQuery(query); - return contents; - } - } - - /// - /// Gets a collection of children by the parents Id and UmbracoObjectType - /// - /// Id of the parent to retrieve children for - /// UmbracoObjectType of the children to retrieve - /// An enumerable list of objects - public virtual IEnumerable GetChildren(int parentId, UmbracoObjectTypes umbracoObjectType) - { - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var query = Query.Builder.Where(x => x.ParentId == parentId); - return repository.GetByQuery(query, objectTypeId); - } - } - - /// - /// Gets a collection of children by the parent's Id and UmbracoObjectType without adding property data - /// - /// Id of the parent to retrieve children for - /// An enumerable list of objects - internal IEnumerable GetMediaChildrenWithoutPropertyData(int parentId) - { - var objectTypeId = UmbracoObjectTypes.Media.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var query = Query.Builder.Where(x => x.ParentId == parentId); - - // Not pretty having to cast the repository, but it is the only way to get to use an internal method that we - // do not want to make public on the interface. Unfortunately also prevents this from being unit tested. - // See this issue for details on why we need this: - // https://github.com/umbraco/Umbraco-CMS/issues/3457 - return ((EntityRepository)repository).GetMediaByQueryWithoutPropertyData(query); - } - } - - /// - /// Returns a paged collection of children - /// - /// The parent id to return children for - /// - /// - /// - /// - /// - /// - /// - /// - public IEnumerable GetPagedChildren(int parentId, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = "") - { - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var query = Query.Builder.Where(x => x.ParentId == parentId && x.Trashed == false); - - IQuery filterQuery = null; - if (filter.IsNullOrWhiteSpace() == false) - { - filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); - } - - var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); - return contents; - } - } - - /// - /// Returns a paged collection of descendants - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") - { - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - - var query = Query.Builder; - //if the id is System Root, then just get all - if (id != Constants.System.Root) +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Umbraco.Core.CodeAnnotations; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Services +{ + public class EntityService : ScopeRepositoryService, IEntityService + { + private readonly Dictionary>> _supportedObjectTypes; + private readonly IdkMap _idkMap; + + public EntityService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, + IContentService contentService, IContentTypeService contentTypeService, IMediaService mediaService, IDataTypeService dataTypeService, + IMemberService memberService, IMemberTypeService memberTypeService, IdkMap idkMap) + : base(provider, repositoryFactory, logger, eventMessagesFactory) + { + _idkMap = idkMap; + + _supportedObjectTypes = new Dictionary>> + { + {typeof (IDataTypeDefinition).FullName, new Tuple>(UmbracoObjectTypes.DataType, dataTypeService.GetDataTypeDefinitionById)}, + {typeof (IContent).FullName, new Tuple>(UmbracoObjectTypes.Document, contentService.GetById)}, + {typeof (IContentType).FullName, new Tuple>(UmbracoObjectTypes.DocumentType, contentTypeService.GetContentType)}, + {typeof (IMedia).FullName, new Tuple>(UmbracoObjectTypes.Media, mediaService.GetById)}, + {typeof (IMediaType).FullName, new Tuple>(UmbracoObjectTypes.MediaType, contentTypeService.GetMediaType)}, + {typeof (IMember).FullName, new Tuple>(UmbracoObjectTypes.Member, memberService.GetById)}, + {typeof (IMemberType).FullName, new Tuple>(UmbracoObjectTypes.MemberType, memberTypeService.Get)}, + }; + } + + #region Static Queries + + private IQuery _rootEntityQuery; + + #endregion + + internal IdkMap IdkMap { get { return _idkMap; } } + + /// + /// Returns the integer id for a given GUID + /// + /// + /// + /// + public Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType) + { + return _idkMap.GetIdForKey(key, umbracoObjectType); + } + + public Attempt GetIdForUdi(Udi udi) + { + return _idkMap.GetIdForUdi(udi); + } + + /// + /// Returns the GUID for a given integer id + /// + /// + /// + /// + public Attempt GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType) + { + return _idkMap.GetKeyForId(id, umbracoObjectType); + } + + public IUmbracoEntity GetByKey(Guid key) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.GetByKey(key); + uow.Commit(); + return ret; + } + } + + /// + /// Gets an UmbracoEntity by its Id, and optionally loads the complete object graph. + /// + /// + /// By default this will load the base type with a minimum set of properties. + /// + /// Id of the object to retrieve + /// An + public virtual IUmbracoEntity Get(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.Get(id); + return ret; + } + } + + public IUmbracoEntity GetByKey(Guid key, UmbracoObjectTypes umbracoObjectType) + { + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.GetByKey(key, objectTypeId); + return ret; + } + } + + /// + /// Gets an UmbracoEntity by its Id and UmbracoObjectType, and optionally loads the complete object graph. + /// + /// + /// By default this will load the base type with a minimum set of properties. + /// + /// Id of the object to retrieve + /// UmbracoObjectType of the entity to retrieve + // An + public virtual IUmbracoEntity Get(int id, UmbracoObjectTypes umbracoObjectType) + { + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.Get(id, objectTypeId); + return ret; + } + } + + public IUmbracoEntity GetByKey(Guid key) where T : IUmbracoEntity + { + throw new NotImplementedException(); + } + + /// + /// Gets an UmbracoEntity by its Id and specified Type. Optionally loads the complete object graph. + /// + /// + /// By default this will load the base type with a minimum set of properties. + /// + /// Type of the model to retrieve. Must be based on an + /// Id of the object to retrieve + /// An + public virtual IUmbracoEntity Get(int id) where T : IUmbracoEntity + { + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.Get(id); + return ret; + } + } + + /// + /// Gets the parent of entity by its id + /// + /// Id of the entity to retrieve the Parent for + /// An + public virtual IUmbracoEntity GetParent(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var entity = repository.Get(id); + + if (entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21) + return null; + var ret = repository.Get(entity.ParentId); + return ret; + } + } + + /// + /// Gets the parent of entity by its id and UmbracoObjectType + /// + /// Id of the entity to retrieve the Parent for + /// UmbracoObjectType of the parent to retrieve + /// An + public virtual IUmbracoEntity GetParent(int id, UmbracoObjectTypes umbracoObjectType) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var entity = repository.Get(id); + + if (entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21) + return null; + var objectTypeId = umbracoObjectType.GetGuid(); + + var ret = repository.Get(entity.ParentId, objectTypeId); + return ret; + } + } + + /// + /// Gets a collection of children by the parents Id + /// + /// Id of the parent to retrieve children for + /// An enumerable list of objects + public virtual IEnumerable GetChildren(int parentId) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var query = Query.Builder.Where(x => x.ParentId == parentId); + + var contents = repository.GetByQuery(query); + return contents; + } + } + + /// + /// Gets a collection of children by the parents Id and UmbracoObjectType + /// + /// Id of the parent to retrieve children for + /// UmbracoObjectType of the children to retrieve + /// An enumerable list of objects + public virtual IEnumerable GetChildren(int parentId, UmbracoObjectTypes umbracoObjectType) + { + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var query = Query.Builder.Where(x => x.ParentId == parentId); + return repository.GetByQuery(query, objectTypeId); + } + } + + /// + /// Gets a collection of children by the parent's Id and UmbracoObjectType without adding property data + /// + /// Id of the parent to retrieve children for + /// An enumerable list of objects + internal IEnumerable GetMediaChildrenWithoutPropertyData(int parentId) + { + var objectTypeId = UmbracoObjectTypes.Media.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var query = Query.Builder.Where(x => x.ParentId == parentId); + + // Not pretty having to cast the repository, but it is the only way to get to use an internal method that we + // do not want to make public on the interface. Unfortunately also prevents this from being unit tested. + // See this issue for details on why we need this: + // https://github.com/umbraco/Umbraco-CMS/issues/3457 + return ((EntityRepository)repository).GetMediaByQueryWithoutPropertyData(query); + } + } + + /// + /// Returns a paged collection of children + /// + /// The parent id to return children for + /// + /// + /// + /// + /// + /// + /// + /// + public IEnumerable GetPagedChildren(int parentId, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = "") + { + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var query = Query.Builder.Where(x => x.ParentId == parentId && x.Trashed == false); + + IQuery filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) { - //lookup the path so we can use it in the prefix query below - var itemPaths = repository.GetAllPaths(objectTypeId, id).ToArray(); - if (itemPaths.Length == 0) - { - totalRecords = 0; - return Enumerable.Empty(); - } + filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); + } + + var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); + return contents; + } + } + + /// + /// Returns a paged collection of descendants + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") + { + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + + var query = Query.Builder; + //if the id is System Root, then just get all + if (id != Constants.System.Root) + { + //lookup the path so we can use it in the prefix query below + var itemPaths = repository.GetAllPaths(objectTypeId, id).ToArray(); + if (itemPaths.Length == 0) + { + totalRecords = 0; + return Enumerable.Empty(); + } var itemPath = itemPaths[0].Path; - - query.Where(x => x.Path.SqlStartsWith(string.Format("{0},", itemPath), TextColumnType.NVarchar)); - } - - IQuery filterQuery = null; - if (filter.IsNullOrWhiteSpace() == false) - { - filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); - } - - var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); - return contents; - } - } - /// - /// Returns a paged collection of descendants. - /// - public IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") - { - totalRecords = 0; - - var idsA = ids.ToArray(); - if (idsA.Length == 0) - return Enumerable.Empty(); - - var objectTypeId = umbracoObjectType.GetGuid(); - - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - - var query = Query.Builder; - if (idsA.All(x => x != Constants.System.Root)) - { - //lookup the paths so we can use it in the prefix query below - var itemPaths = repository.GetAllPaths(objectTypeId, idsA).ToArray(); - if (itemPaths.Length == 0) - { - totalRecords = 0; - return Enumerable.Empty(); - } - - var clauses = new List>>(); - foreach (var id in idsA) + + query.Where(x => x.Path.SqlStartsWith(string.Format("{0},", itemPath), TextColumnType.NVarchar)); + } + + IQuery filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) + { + filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); + } + + var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); + return contents; + } + } + /// + /// Returns a paged collection of descendants. + /// + public IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") + { + totalRecords = 0; + + var idsA = ids.ToArray(); + if (idsA.Length == 0) + return Enumerable.Empty(); + + var objectTypeId = umbracoObjectType.GetGuid(); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + + var query = Query.Builder; + if (idsA.All(x => x != Constants.System.Root)) + { + //lookup the paths so we can use it in the prefix query below + var itemPaths = repository.GetAllPaths(objectTypeId, idsA).ToArray(); + if (itemPaths.Length == 0) + { + totalRecords = 0; + return Enumerable.Empty(); + } + + var clauses = new List>>(); + foreach (var id in idsA) { //if the id is root then don't add any clauses - if (id != Constants.System.Root) - { - var itemPath = itemPaths.FirstOrDefault(x => x.Id == id); - if (itemPath == null) continue; - var path = itemPath.Path; - var qid = id; - clauses.Add(x => x.Path.SqlStartsWith(string.Format("{0},", path), TextColumnType.NVarchar) || x.Path.SqlEndsWith(string.Format(",{0}", qid), TextColumnType.NVarchar)); - } - } - query.WhereAny(clauses); - } - - IQuery filterQuery = null; - if (filter.IsNullOrWhiteSpace() == false) - { - filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); - } - - var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); - return contents; - } - } - - /// - /// Returns a paged collection of descendants from the root - /// - /// - /// - /// - /// - /// - /// - /// - /// true/false to include trashed objects - /// - public IEnumerable GetPagedDescendantsFromRoot(UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "", bool includeTrashed = true) - { - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - - var query = Query.Builder; - //don't include trashed if specfied - if (includeTrashed == false) - { - query.Where(x => x.Trashed == false); - } - - IQuery filterQuery = null; - if (filter.IsNullOrWhiteSpace() == false) - { - filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); - } - - var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); - return contents; - } - } - - /// - /// Gets a collection of descendents by the parents Id - /// - /// Id of entity to retrieve descendents for - /// An enumerable list of objects - public virtual IEnumerable GetDescendents(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var entity = repository.Get(id); - var pathMatch = entity.Path + ","; - var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != id); - - var entities = repository.GetByQuery(query); - return entities; - } - } - - /// - /// Gets a collection of descendents by the parents Id - /// - /// Id of entity to retrieve descendents for - /// UmbracoObjectType of the descendents to retrieve - /// An enumerable list of objects - public virtual IEnumerable GetDescendents(int id, UmbracoObjectTypes umbracoObjectType) - { - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var entity = repository.Get(id); - var query = Query.Builder.Where(x => x.Path.StartsWith(entity.Path) && x.Id != id); - - var entities = repository.GetByQuery(query, objectTypeId); - return entities; - } - } - - /// - /// Gets a collection of the entities at the root, which corresponds to the entities with a Parent Id of -1. - /// - /// UmbracoObjectType of the root entities to retrieve - /// An enumerable list of objects - public virtual IEnumerable GetRootEntities(UmbracoObjectTypes umbracoObjectType) - { - //create it once if it is needed (no need for locking here) - if (_rootEntityQuery == null) - { - _rootEntityQuery = Query.Builder.Where(x => x.ParentId == -1); - } - - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var entities = repository.GetByQuery(_rootEntityQuery, objectTypeId); - return entities; - } - } - - /// - /// Gets a collection of all of a given type. - /// - /// Type of the entities to retrieve - /// An enumerable list of objects - public virtual IEnumerable GetAll(params int[] ids) where T : IUmbracoEntity - { - var typeFullName = typeof(T).FullName; - Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => - { - throw new NotSupportedException - ("The passed in type is not supported"); - }); - var objectType = _supportedObjectTypes[typeFullName].Item1; - - return GetAll(objectType, ids); - } - - /// - /// Gets a collection of all of a given type. - /// - /// UmbracoObjectType of the entities to return - /// - /// An enumerable list of objects - public virtual IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType, params int[] ids) - { - var entityType = GetEntityType(umbracoObjectType); - var typeFullName = entityType.FullName; - Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => - { - throw new NotSupportedException - ("The passed in type is not supported"); - }); - - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var ret = repository.GetAll(objectTypeId, ids); - return ret; - } - } - - public IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType, Guid[] keys) - { - var entityType = GetEntityType(umbracoObjectType); - var typeFullName = entityType.FullName; - Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => - { - throw new NotSupportedException - ("The passed in type is not supported"); - }); - - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var ret = repository.GetAll(objectTypeId, keys); - return ret; - } - } - - public virtual IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params int[] ids) - { - var entityType = GetEntityType(umbracoObjectType); - var typeFullName = entityType.FullName; - Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => - { - throw new NotSupportedException - ("The passed in type is not supported"); - }); - - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - return repository.GetAllPaths(objectTypeId, ids); - } - } - - public virtual IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params Guid[] keys) - { - var entityType = GetEntityType(umbracoObjectType); - var typeFullName = entityType.FullName; - Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => - { - throw new NotSupportedException - ("The passed in type is not supported"); - }); - - var objectTypeId = umbracoObjectType.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - return repository.GetAllPaths(objectTypeId, keys); - } - } - - /// - /// Gets a collection of - /// - /// Guid id of the UmbracoObjectType - /// - /// An enumerable list of objects - public virtual IEnumerable GetAll(Guid objectTypeId, params int[] ids) - { - var umbracoObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); - var entityType = GetEntityType(umbracoObjectType); - var typeFullName = entityType.FullName; - Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => - { - throw new NotSupportedException - ("The passed in type is not supported"); - }); - - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var ret = repository.GetAll(objectTypeId, ids); - return ret; - } - } - - /// - /// Gets the UmbracoObjectType from the integer id of an IUmbracoEntity. - /// - /// Id of the entity - /// - public virtual UmbracoObjectTypes GetObjectType(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var sql = new Sql().Select("nodeObjectType").From().Where(x => x.NodeId == id); - var nodeObjectTypeId = uow.Database.ExecuteScalar(sql); - var objectTypeId = nodeObjectTypeId; - return UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); - } - } - - /// - /// Gets the UmbracoObjectType from the integer id of an IUmbracoEntity. - /// - /// Unique Id of the entity - /// - public virtual UmbracoObjectTypes GetObjectType(Guid key) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var sql = new Sql().Select("nodeObjectType").From().Where(x => x.UniqueId == key); - var nodeObjectTypeId = uow.Database.ExecuteScalar(sql); - var objectTypeId = nodeObjectTypeId; - return UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); - } - } - - /// - /// Gets the UmbracoObjectType from an IUmbracoEntity. - /// - /// - /// - public virtual UmbracoObjectTypes GetObjectType(IUmbracoEntity entity) - { - var entityImpl = entity as UmbracoEntity; - if (entityImpl == null) - return GetObjectType(entity.Id); - - return UmbracoObjectTypesExtensions.GetUmbracoObjectType(entityImpl.NodeObjectTypeId); - } - - /// - /// Gets the Type of an entity by its Id - /// - /// Id of the entity - /// Type of the entity - public virtual Type GetEntityType(int id) - { - var objectType = GetObjectType(id); - return GetEntityType(objectType); - } - - /// - /// Gets the Type of an entity by its - /// - /// - /// Type of the entity - public virtual Type GetEntityType(UmbracoObjectTypes umbracoObjectType) - { - var type = typeof(UmbracoObjectTypes); - var memInfo = type.GetMember(umbracoObjectType.ToString()); - var attributes = memInfo[0].GetCustomAttributes(typeof(UmbracoObjectTypeAttribute), - false); - - var attribute = ((UmbracoObjectTypeAttribute)attributes[0]); - if (attribute == null) - throw new NullReferenceException("The passed in UmbracoObjectType does not contain an UmbracoObjectTypeAttribute, which is used to retrieve the Type."); - - if (attribute.ModelType == null) - throw new NullReferenceException("The passed in UmbracoObjectType does not contain a Type definition"); - - return attribute.ModelType; - } - - public bool Exists(Guid key) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var exists = repository.Exists(key); - return exists; - } - } - - public bool Exists(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var exists = repository.Exists(id); - return exists; - } - } - - /// - public int ReserveId(Guid key) - { - NodeDto node; - using (var scope = UowProvider.ScopeProvider.CreateScope()) - { - var sql = new Sql("SELECT * FROM umbracoNode WHERE uniqueID=@0 AND nodeObjectType=@1", key, Constants.ObjectTypes.IdReservationGuid); - node = scope.Database.SingleOrDefault(sql); - if (node != null) throw new InvalidOperationException("An identifier has already been reserved for this Udi."); - node = new NodeDto - { - UniqueId = key, - Text = "RESERVED.ID", - NodeObjectType = Constants.ObjectTypes.IdReservationGuid, - - CreateDate = DateTime.Now, - UserId = 0, - ParentId = -1, - Level = 1, - Path = "-1", - SortOrder = 0, - Trashed = false - }; - scope.Database.Insert(node); - scope.Complete(); - } - return node.NodeId; - } - } -} + if (id != Constants.System.Root) + { + var itemPath = itemPaths.FirstOrDefault(x => x.Id == id); + if (itemPath == null) continue; + var path = itemPath.Path; + var qid = id; + clauses.Add(x => x.Path.SqlStartsWith(string.Format("{0},", path), TextColumnType.NVarchar) || x.Path.SqlEndsWith(string.Format(",{0}", qid), TextColumnType.NVarchar)); + } + } + query.WhereAny(clauses); + } + + IQuery filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) + { + filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); + } + + var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); + return contents; + } + } + + /// + /// Returns a paged collection of descendants from the root + /// + /// + /// + /// + /// + /// + /// + /// + /// true/false to include trashed objects + /// + public IEnumerable GetPagedDescendantsFromRoot(UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "", bool includeTrashed = true) + { + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + + var query = Query.Builder; + //don't include trashed if specfied + if (includeTrashed == false) + { + query.Where(x => x.Trashed == false); + } + + IQuery filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) + { + filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); + } + + var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); + return contents; + } + } + + /// + /// Gets a collection of descendents by the parents Id + /// + /// Id of entity to retrieve descendents for + /// An enumerable list of objects + public virtual IEnumerable GetDescendents(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var entity = repository.Get(id); + var pathMatch = entity.Path + ","; + var query = Query.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != id); + + var entities = repository.GetByQuery(query); + return entities; + } + } + + /// + /// Gets a collection of descendents by the parents Id + /// + /// Id of entity to retrieve descendents for + /// UmbracoObjectType of the descendents to retrieve + /// An enumerable list of objects + public virtual IEnumerable GetDescendents(int id, UmbracoObjectTypes umbracoObjectType) + { + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var entity = repository.Get(id); + var query = Query.Builder.Where(x => x.Path.StartsWith(entity.Path) && x.Id != id); + + var entities = repository.GetByQuery(query, objectTypeId); + return entities; + } + } + + /// + /// Gets a collection of the entities at the root, which corresponds to the entities with a Parent Id of -1. + /// + /// UmbracoObjectType of the root entities to retrieve + /// An enumerable list of objects + public virtual IEnumerable GetRootEntities(UmbracoObjectTypes umbracoObjectType) + { + //create it once if it is needed (no need for locking here) + if (_rootEntityQuery == null) + { + _rootEntityQuery = Query.Builder.Where(x => x.ParentId == -1); + } + + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var entities = repository.GetByQuery(_rootEntityQuery, objectTypeId); + return entities; + } + } + + /// + /// Gets a collection of all of a given type. + /// + /// Type of the entities to retrieve + /// An enumerable list of objects + public virtual IEnumerable GetAll(params int[] ids) where T : IUmbracoEntity + { + var typeFullName = typeof(T).FullName; + Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => + { + throw new NotSupportedException + ("The passed in type is not supported"); + }); + var objectType = _supportedObjectTypes[typeFullName].Item1; + + return GetAll(objectType, ids); + } + + /// + /// Gets a collection of all of a given type. + /// + /// UmbracoObjectType of the entities to return + /// + /// An enumerable list of objects + public virtual IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType, params int[] ids) + { + var entityType = GetEntityType(umbracoObjectType); + var typeFullName = entityType.FullName; + Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => + { + throw new NotSupportedException + ("The passed in type is not supported"); + }); + + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.GetAll(objectTypeId, ids); + return ret; + } + } + + public IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType, Guid[] keys) + { + var entityType = GetEntityType(umbracoObjectType); + var typeFullName = entityType.FullName; + Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => + { + throw new NotSupportedException + ("The passed in type is not supported"); + }); + + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.GetAll(objectTypeId, keys); + return ret; + } + } + + public virtual IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params int[] ids) + { + var entityType = GetEntityType(umbracoObjectType); + var typeFullName = entityType.FullName; + Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => + { + throw new NotSupportedException + ("The passed in type is not supported"); + }); + + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + return repository.GetAllPaths(objectTypeId, ids); + } + } + + public virtual IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params Guid[] keys) + { + var entityType = GetEntityType(umbracoObjectType); + var typeFullName = entityType.FullName; + Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => + { + throw new NotSupportedException + ("The passed in type is not supported"); + }); + + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + return repository.GetAllPaths(objectTypeId, keys); + } + } + + /// + /// Gets a collection of + /// + /// Guid id of the UmbracoObjectType + /// + /// An enumerable list of objects + public virtual IEnumerable GetAll(Guid objectTypeId, params int[] ids) + { + var umbracoObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); + var entityType = GetEntityType(umbracoObjectType); + var typeFullName = entityType.FullName; + Mandate.That(_supportedObjectTypes.ContainsKey(typeFullName), () => + { + throw new NotSupportedException + ("The passed in type is not supported"); + }); + + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var ret = repository.GetAll(objectTypeId, ids); + return ret; + } + } + + /// + /// Gets the UmbracoObjectType from the integer id of an IUmbracoEntity. + /// + /// Id of the entity + /// + public virtual UmbracoObjectTypes GetObjectType(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var sql = new Sql().Select("nodeObjectType").From().Where(x => x.NodeId == id); + var nodeObjectTypeId = uow.Database.ExecuteScalar(sql); + var objectTypeId = nodeObjectTypeId; + return UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); + } + } + + /// + /// Gets the UmbracoObjectType from the integer id of an IUmbracoEntity. + /// + /// Unique Id of the entity + /// + public virtual UmbracoObjectTypes GetObjectType(Guid key) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var sql = new Sql().Select("nodeObjectType").From().Where(x => x.UniqueId == key); + var nodeObjectTypeId = uow.Database.ExecuteScalar(sql); + var objectTypeId = nodeObjectTypeId; + return UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); + } + } + + /// + /// Gets the UmbracoObjectType from an IUmbracoEntity. + /// + /// + /// + public virtual UmbracoObjectTypes GetObjectType(IUmbracoEntity entity) + { + var entityImpl = entity as UmbracoEntity; + if (entityImpl == null) + return GetObjectType(entity.Id); + + return UmbracoObjectTypesExtensions.GetUmbracoObjectType(entityImpl.NodeObjectTypeId); + } + + /// + /// Gets the Type of an entity by its Id + /// + /// Id of the entity + /// Type of the entity + public virtual Type GetEntityType(int id) + { + var objectType = GetObjectType(id); + return GetEntityType(objectType); + } + + /// + /// Gets the Type of an entity by its + /// + /// + /// Type of the entity + public virtual Type GetEntityType(UmbracoObjectTypes umbracoObjectType) + { + var type = typeof(UmbracoObjectTypes); + var memInfo = type.GetMember(umbracoObjectType.ToString()); + var attributes = memInfo[0].GetCustomAttributes(typeof(UmbracoObjectTypeAttribute), + false); + + var attribute = ((UmbracoObjectTypeAttribute)attributes[0]); + if (attribute == null) + throw new NullReferenceException("The passed in UmbracoObjectType does not contain an UmbracoObjectTypeAttribute, which is used to retrieve the Type."); + + if (attribute.ModelType == null) + throw new NullReferenceException("The passed in UmbracoObjectType does not contain a Type definition"); + + return attribute.ModelType; + } + + public bool Exists(Guid key) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var exists = repository.Exists(key); + return exists; + } + } + + public bool Exists(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly:true)) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + var exists = repository.Exists(id); + return exists; + } + } + + /// + public int ReserveId(Guid key) + { + NodeDto node; + using (var scope = UowProvider.ScopeProvider.CreateScope()) + { + var sql = new Sql("SELECT * FROM umbracoNode WHERE uniqueID=@0 AND nodeObjectType=@1", key, Constants.ObjectTypes.IdReservationGuid); + node = scope.Database.SingleOrDefault(sql); + if (node != null) throw new InvalidOperationException("An identifier has already been reserved for this Udi."); + node = new NodeDto + { + UniqueId = key, + Text = "RESERVED.ID", + NodeObjectType = Constants.ObjectTypes.IdReservationGuid, + + CreateDate = DateTime.Now, + UserId = 0, + ParentId = -1, + Level = 1, + Path = "-1", + SortOrder = 0, + Trashed = false + }; + scope.Database.Insert(node); + scope.Complete(); + } + return node.NodeId; + } + } +} diff --git a/src/Umbraco.Core/Services/IEntityService.cs b/src/Umbraco.Core/Services/IEntityService.cs index d8a60a9cae..a6a2254138 100644 --- a/src/Umbraco.Core/Services/IEntityService.cs +++ b/src/Umbraco.Core/Services/IEntityService.cs @@ -1,298 +1,282 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; - -namespace Umbraco.Core.Services -{ - public interface IEntityService - { - /// - /// Returns true if the entity exists - /// - /// - /// - bool Exists(Guid key); - - /// - /// Returns true if the entity exists - /// - /// - /// - bool Exists(int id); - - /// - /// Returns the integer id for a given GUID - /// - /// - /// - /// +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; + +namespace Umbraco.Core.Services +{ + public interface IEntityService + { + /// + /// Returns true if the entity exists + /// + /// + /// + bool Exists(Guid key); + + /// + /// Returns true if the entity exists + /// + /// + /// + bool Exists(int id); + + /// + /// Returns the integer id for a given GUID + /// + /// + /// + /// Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType); - - /// - /// Returns the integer id for a given Udi - /// - /// - /// - Attempt GetIdForUdi(Udi udi); - - /// - /// Returns the GUID for a given integer id - /// - /// - /// - /// - Attempt GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType); - - /// - /// Gets an UmbracoEntity by its Id, and optionally loads the complete object graph. - /// - /// - /// By default this will load the base type with a minimum set of properties. - /// - /// Unique Id of the object to retrieve - /// Optional bool to load the complete object graph when set to False. - /// An - IUmbracoEntity GetByKey(Guid key, bool loadBaseType = true); - - /// - /// Gets an UmbracoEntity by its Id, and optionally loads the complete object graph. - /// - /// - /// By default this will load the base type with a minimum set of properties. - /// - /// Id of the object to retrieve - /// Optional bool to load the complete object graph when set to False. - /// An - IUmbracoEntity Get(int id, bool loadBaseType = true); - - /// - /// Gets an UmbracoEntity by its Id and UmbracoObjectType, and optionally loads the complete object graph. - /// - /// - /// By default this will load the base type with a minimum set of properties. - /// - /// Unique Id of the object to retrieve - /// UmbracoObjectType of the entity to retrieve - /// Optional bool to load the complete object graph when set to False. - /// An - IUmbracoEntity GetByKey(Guid key, UmbracoObjectTypes umbracoObjectType, bool loadBaseType = true); - - /// - /// Gets an UmbracoEntity by its Id and UmbracoObjectType, and optionally loads the complete object graph. - /// - /// - /// By default this will load the base type with a minimum set of properties. - /// - /// Id of the object to retrieve - /// UmbracoObjectType of the entity to retrieve - /// Optional bool to load the complete object graph when set to False. - /// An - IUmbracoEntity Get(int id, UmbracoObjectTypes umbracoObjectType, bool loadBaseType = true); - - /// - /// Gets an UmbracoEntity by its Id and specified Type. Optionally loads the complete object graph. - /// - /// - /// By default this will load the base type with a minimum set of properties. - /// - /// Type of the model to retrieve. Must be based on an - /// Unique Id of the object to retrieve - /// Optional bool to load the complete object graph when set to False. - /// An - IUmbracoEntity GetByKey(Guid key, bool loadBaseType = true) where T : IUmbracoEntity; - - /// - /// Gets an UmbracoEntity by its Id and specified Type. Optionally loads the complete object graph. - /// - /// - /// By default this will load the base type with a minimum set of properties. - /// - /// Type of the model to retrieve. Must be based on an - /// Id of the object to retrieve - /// Optional bool to load the complete object graph when set to False. - /// An - IUmbracoEntity Get(int id, bool loadBaseType = true) where T : IUmbracoEntity; - - /// - /// Gets the parent of entity by its id - /// - /// Id of the entity to retrieve the Parent for - /// An - IUmbracoEntity GetParent(int id); - - /// - /// Gets the parent of entity by its id and UmbracoObjectType - /// - /// Id of the entity to retrieve the Parent for - /// UmbracoObjectType of the parent to retrieve - /// An - IUmbracoEntity GetParent(int id, UmbracoObjectTypes umbracoObjectType); - - /// - /// Gets a collection of children by the parents Id - /// - /// Id of the parent to retrieve children for - /// An enumerable list of objects - IEnumerable GetChildren(int parentId); - - /// - /// Gets a collection of children by the parents Id and UmbracoObjectType - /// - /// Id of the parent to retrieve children for - /// UmbracoObjectType of the children to retrieve - /// An enumerable list of objects - IEnumerable GetChildren(int parentId, UmbracoObjectTypes umbracoObjectType); - - /// - /// Returns a paged collection of children - /// - /// The parent id to return children for - /// - /// - /// - /// - /// - /// - /// - /// - IEnumerable GetPagedChildren(int parentId, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); - - /// - /// Returns a paged collection of descendants - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); - - /// - /// Returns a paged collection of descendants - /// - IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); - - /// - /// Returns a paged collection of descendants from the root - /// - /// - /// - /// - /// - /// - /// - /// - /// true/false to include trashed objects - /// - IEnumerable GetPagedDescendantsFromRoot(UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "", bool includeTrashed = true); - - /// - /// Gets a collection of descendents by the parents Id - /// - /// Id of entity to retrieve descendents for - /// An enumerable list of objects - IEnumerable GetDescendents(int id); - - /// - /// Gets a collection of descendents by the parents Id - /// - /// Id of entity to retrieve descendents for - /// UmbracoObjectType of the descendents to retrieve - /// An enumerable list of objects - IEnumerable GetDescendents(int id, UmbracoObjectTypes umbracoObjectType); - - /// - /// Gets a collection of the entities at the root, which corresponds to the entities with a Parent Id of -1. - /// - /// UmbracoObjectType of the root entities to retrieve - /// An enumerable list of objects - IEnumerable GetRootEntities(UmbracoObjectTypes umbracoObjectType); - - /// - /// Gets a collection of all of a given type. - /// - /// Type of the entities to retrieve - /// An enumerable list of objects - IEnumerable GetAll(params int[] ids) where T : IUmbracoEntity; - - /// - /// Gets a collection of all of a given type. - /// - /// UmbracoObjectType of the entities to return - /// - /// An enumerable list of objects - IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType, params int[] ids); - - /// - /// Gets a collection of all of a given type. - /// - /// UmbracoObjectType of the entities to return - /// - /// An enumerable list of objects - IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType, Guid[] keys); - - /// - /// Gets a collection of - /// - /// Guid id of the UmbracoObjectType - /// - /// An enumerable list of objects - IEnumerable GetAll(Guid objectTypeId, params int[] ids); - - /// - /// Gets paths for entities. - /// - IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params int[] ids); - - /// - /// Gets paths for entities. - /// - IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params Guid[] keys); - - /// - /// Gets the UmbracoObjectType from the integer id of an IUmbracoEntity. - /// - /// Id of the entity - /// - UmbracoObjectTypes GetObjectType(int id); - - /// - /// Gets the UmbracoObjectType from an IUmbracoEntity. - /// - /// - /// - UmbracoObjectTypes GetObjectType(IUmbracoEntity entity); - - /// - /// Gets the Type of an entity by its Id - /// - /// Id of the entity - /// Type of the entity - Type GetEntityType(int id); - - /// - /// Gets the Type of an entity by its - /// - /// - /// Type of the entity - Type GetEntityType(UmbracoObjectTypes umbracoObjectType); - - /// - /// Reserves an identifier for a key. - /// - /// They key. - /// The identifier. - /// When a new content or a media is saved with the key, it will have the reserved identifier. - int ReserveId(Guid key); - } -} \ No newline at end of file + + /// + /// Returns the integer id for a given Udi + /// + /// + /// + Attempt GetIdForUdi(Udi udi); + + /// + /// Returns the GUID for a given integer id + /// + /// + /// + /// + Attempt GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType); + + /// + /// Gets an UmbracoEntity by its Id, and optionally loads the complete object graph. + /// + /// + /// By default this will load the base type with a minimum set of properties. + /// + /// Unique Id of the object to retrieve + /// An + IUmbracoEntity GetByKey(Guid key); + + /// + /// Gets an UmbracoEntity by its Id, and optionally loads the complete object graph. + /// + /// + /// By default this will load the base type with a minimum set of properties. + /// + /// Id of the object to retrieve + /// An + IUmbracoEntity Get(int id); + + /// + /// Gets an UmbracoEntity by its Id and UmbracoObjectType, and optionally loads the complete object graph. + /// + /// + /// By default this will load the base type with a minimum set of properties. + /// + /// Unique Id of the object to retrieve + /// UmbracoObjectType of the entity to retrieve + /// An + IUmbracoEntity GetByKey(Guid key, UmbracoObjectTypes umbracoObjectType); + + /// + /// Gets an UmbracoEntity by its Id and UmbracoObjectType, and optionally loads the complete object graph. + /// + /// + /// By default this will load the base type with a minimum set of properties. + /// + /// Id of the object to retrieve + /// UmbracoObjectType of the entity to retrieve + /// An + IUmbracoEntity Get(int id, UmbracoObjectTypes umbracoObjectType); + + + /// + /// Gets an UmbracoEntity by its Id and specified Type. Optionally loads the complete object graph. + /// + /// + /// By default this will load the base type with a minimum set of properties. + /// + /// Type of the model to retrieve. Must be based on an + /// Id of the object to retrieve + /// An + IUmbracoEntity Get(int id) where T : IUmbracoEntity; + + /// + /// Gets the parent of entity by its id + /// + /// Id of the entity to retrieve the Parent for + /// An + IUmbracoEntity GetParent(int id); + + /// + /// Gets the parent of entity by its id and UmbracoObjectType + /// + /// Id of the entity to retrieve the Parent for + /// UmbracoObjectType of the parent to retrieve + /// An + IUmbracoEntity GetParent(int id, UmbracoObjectTypes umbracoObjectType); + + /// + /// Gets a collection of children by the parents Id + /// + /// Id of the parent to retrieve children for + /// An enumerable list of objects + IEnumerable GetChildren(int parentId); + + /// + /// Gets a collection of children by the parents Id and UmbracoObjectType + /// + /// Id of the parent to retrieve children for + /// UmbracoObjectType of the children to retrieve + /// An enumerable list of objects + IEnumerable GetChildren(int parentId, UmbracoObjectTypes umbracoObjectType); + + /// + /// Returns a paged collection of children + /// + /// The parent id to return children for + /// + /// + /// + /// + /// + /// + /// + /// + IEnumerable GetPagedChildren(int parentId, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + + /// + /// Returns a paged collection of descendants + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); + + /// + /// Returns a paged collection of descendants + /// + IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); + + /// + /// Returns a paged collection of descendants from the root + /// + /// + /// + /// + /// + /// + /// + /// + /// true/false to include trashed objects + /// + IEnumerable GetPagedDescendantsFromRoot(UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "", bool includeTrashed = true); + + /// + /// Gets a collection of descendents by the parents Id + /// + /// Id of entity to retrieve descendents for + /// An enumerable list of objects + IEnumerable GetDescendents(int id); + + /// + /// Gets a collection of descendents by the parents Id + /// + /// Id of entity to retrieve descendents for + /// UmbracoObjectType of the descendents to retrieve + /// An enumerable list of objects + IEnumerable GetDescendents(int id, UmbracoObjectTypes umbracoObjectType); + + /// + /// Gets a collection of the entities at the root, which corresponds to the entities with a Parent Id of -1. + /// + /// UmbracoObjectType of the root entities to retrieve + /// An enumerable list of objects + IEnumerable GetRootEntities(UmbracoObjectTypes umbracoObjectType); + + /// + /// Gets a collection of all of a given type. + /// + /// Type of the entities to retrieve + /// An enumerable list of objects + IEnumerable GetAll(params int[] ids) where T : IUmbracoEntity; + + /// + /// Gets a collection of all of a given type. + /// + /// UmbracoObjectType of the entities to return + /// + /// An enumerable list of objects + IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType, params int[] ids); + + /// + /// Gets a collection of all of a given type. + /// + /// UmbracoObjectType of the entities to return + /// + /// An enumerable list of objects + IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType, Guid[] keys); + + /// + /// Gets a collection of + /// + /// Guid id of the UmbracoObjectType + /// + /// An enumerable list of objects + IEnumerable GetAll(Guid objectTypeId, params int[] ids); + + /// + /// Gets paths for entities. + /// + IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params int[] ids); + + /// + /// Gets paths for entities. + /// + IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params Guid[] keys); + + /// + /// Gets the UmbracoObjectType from the integer id of an IUmbracoEntity. + /// + /// Id of the entity + /// + UmbracoObjectTypes GetObjectType(int id); + + /// + /// Gets the UmbracoObjectType from an IUmbracoEntity. + /// + /// + /// + UmbracoObjectTypes GetObjectType(IUmbracoEntity entity); + + /// + /// Gets the Type of an entity by its Id + /// + /// Id of the entity + /// Type of the entity + Type GetEntityType(int id); + + /// + /// Gets the Type of an entity by its + /// + /// + /// Type of the entity + Type GetEntityType(UmbracoObjectTypes umbracoObjectType); + + /// + /// Reserves an identifier for a key. + /// + /// They key. + /// The identifier. + /// When a new content or a media is saved with the key, it will have the reserved identifier. + int ReserveId(Guid key); + } +} diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs index 6006a6b7c3..b5ca9ee019 100644 --- a/src/Umbraco.Core/Services/IRelationService.cs +++ b/src/Umbraco.Core/Services/IRelationService.cs @@ -1,306 +1,298 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; - -namespace Umbraco.Core.Services -{ - public interface IRelationService : IService - { - /// - /// Gets a by its Id - /// - /// Id of the - /// A object - IRelation GetById(int id); - - /// - /// Gets a by its Id - /// - /// Id of the - /// A object - IRelationType GetRelationTypeById(int id); - - /// - /// Gets a by its Id - /// - /// Id of the - /// A object - IRelationType GetRelationTypeById(Guid id); - - /// - /// Gets a by its Alias - /// - /// Alias of the - /// A object - IRelationType GetRelationTypeByAlias(string alias); - - /// - /// Gets all objects - /// - /// Optional array of integer ids to return relations for - /// An enumerable list of objects - IEnumerable GetAllRelations(params int[] ids); - - /// - /// Gets all objects by their - /// - /// to retrieve Relations for - /// An enumerable list of objects - IEnumerable GetAllRelationsByRelationType(RelationType relationType); - - /// - /// Gets all objects by their 's Id - /// - /// Id of the to retrieve Relations for - /// An enumerable list of objects - IEnumerable GetAllRelationsByRelationType(int relationTypeId); - - /// - /// Gets all objects - /// - /// Optional array of integer ids to return relationtypes for - /// An enumerable list of objects - IEnumerable GetAllRelationTypes(params int[] ids); - - /// - /// Gets a list of objects by their parent Id - /// - /// Id of the parent to retrieve relations for - /// An enumerable list of objects - IEnumerable GetByParentId(int id); - - /// - /// Gets a list of objects by their parent entity - /// - /// Parent Entity to retrieve relations for - /// An enumerable list of objects - IEnumerable GetByParent(IUmbracoEntity parent); - - /// - /// Gets a list of objects by their parent entity - /// - /// Parent Entity to retrieve relations for - /// Alias of the type of relation to retrieve - /// An enumerable list of objects - IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias); - - /// - /// Gets a list of objects by their child Id - /// - /// Id of the child to retrieve relations for - /// An enumerable list of objects - IEnumerable GetByChildId(int id); - - /// - /// Gets a list of objects by their child Entity - /// - /// Child Entity to retrieve relations for - /// An enumerable list of objects - IEnumerable GetByChild(IUmbracoEntity child); - - /// - /// Gets a list of objects by their child Entity - /// - /// Child Entity to retrieve relations for - /// Alias of the type of relation to retrieve - /// An enumerable list of objects - IEnumerable GetByChild(IUmbracoEntity child, string relationTypeAlias); - - /// - /// Gets a list of objects by their child or parent Id. - /// Using this method will get you all relations regards of it being a child or parent relation. - /// - /// Id of the child or parent to retrieve relations for - /// An enumerable list of objects - IEnumerable GetByParentOrChildId(int id); - - IEnumerable GetByParentOrChildId(int id, string relationTypeAlias); - - /// - /// Gets a list of objects by the Name of the - /// - /// Name of the to retrieve Relations for - /// An enumerable list of objects - IEnumerable GetByRelationTypeName(string relationTypeName); - - /// - /// Gets a list of objects by the Alias of the - /// - /// Alias of the to retrieve Relations for - /// An enumerable list of objects - IEnumerable GetByRelationTypeAlias(string relationTypeAlias); - - /// - /// Gets a list of objects by the Id of the - /// - /// Id of the to retrieve Relations for - /// An enumerable list of objects - IEnumerable GetByRelationTypeId(int relationTypeId); - - /// - /// Gets the Child object from a Relation as an - /// - /// Relation to retrieve child object from - /// Optional bool to load the complete object graph when set to False - /// An - IUmbracoEntity GetChildEntityFromRelation(IRelation relation, bool loadBaseType = false); - - /// - /// Gets the Parent object from a Relation as an - /// - /// Relation to retrieve parent object from - /// Optional bool to load the complete object graph when set to False - /// An - IUmbracoEntity GetParentEntityFromRelation(IRelation relation, bool loadBaseType = false); - - /// - /// Gets the Parent and Child objects from a Relation as a "/> with . - /// - /// Relation to retrieve parent and child object from - /// Optional bool to load the complete object graph when set to False - /// Returns a Tuple with Parent (item1) and Child (item2) - Tuple GetEntitiesFromRelation(IRelation relation, bool loadBaseType = false); - - /// - /// Gets the Child objects from a list of Relations as a list of objects. - /// - /// List of relations to retrieve child objects from - /// Optional bool to load the complete object graph when set to False - /// An enumerable list of - IEnumerable GetChildEntitiesFromRelations(IEnumerable relations, bool loadBaseType = false); - - /// - /// Gets the Parent objects from a list of Relations as a list of objects. - /// - /// List of relations to retrieve parent objects from - /// Optional bool to load the complete object graph when set to False - /// An enumerable list of - IEnumerable GetParentEntitiesFromRelations(IEnumerable relations, - bool loadBaseType = false); - - /// - /// Gets the Parent and Child objects from a list of Relations as a list of objects. - /// - /// List of relations to retrieve parent and child objects from - /// Optional bool to load the complete object graph when set to False - /// An enumerable list of with - IEnumerable> GetEntitiesFromRelations( - IEnumerable relations, - bool loadBaseType = false); - - /// - /// Relates two objects by their entity Ids. - /// - /// Id of the parent - /// Id of the child - /// The type of relation to create - /// The created - IRelation Relate(int parentId, int childId, IRelationType relationType); - - /// - /// Relates two objects that are based on the interface. - /// - /// Parent entity - /// Child entity - /// The type of relation to create - /// The created - IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType); - - /// - /// Relates two objects by their entity Ids. - /// - /// Id of the parent - /// Id of the child - /// Alias of the type of relation to create - /// The created - IRelation Relate(int parentId, int childId, string relationTypeAlias); - - /// - /// Relates two objects that are based on the interface. - /// - /// Parent entity - /// Child entity - /// Alias of the type of relation to create - /// The created - IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias); - - /// - /// Checks whether any relations exists for the passed in . - /// - /// to check for relations - /// Returns True if any relations exists for the given , otherwise False - bool HasRelations(IRelationType relationType); - - /// - /// Checks whether any relations exists for the passed in Id. - /// - /// Id of an object to check relations for - /// Returns True if any relations exists with the given Id, otherwise False - bool IsRelated(int id); - - /// - /// Checks whether two items are related - /// - /// Id of the Parent relation - /// Id of the Child relation - /// Returns True if any relations exists with the given Ids, otherwise False - bool AreRelated(int parentId, int childId); - - /// - /// Checks whether two items are related - /// - /// Parent entity - /// Child entity - /// Returns True if any relations exist between the entities, otherwise False - bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child); - - /// - /// Checks whether two items are related - /// - /// Parent entity - /// Child entity - /// Alias of the type of relation to create - /// Returns True if any relations exist between the entities, otherwise False - bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias); - - /// - /// Checks whether two items are related - /// - /// Id of the Parent relation - /// Id of the Child relation - /// Alias of the type of relation to create - /// Returns True if any relations exist between the entities, otherwise False - bool AreRelated(int parentId, int childId, string relationTypeAlias); - - /// - /// Saves a - /// - /// Relation to save - void Save(IRelation relation); - - /// - /// Saves a - /// - /// RelationType to Save - void Save(IRelationType relationType); - - /// - /// Deletes a - /// - /// Relation to Delete - void Delete(IRelation relation); - - /// - /// Deletes a - /// - /// RelationType to Delete - void Delete(IRelationType relationType); - - /// - /// Deletes all objects based on the passed in - /// - /// to Delete Relations for - void DeleteRelationsOfType(IRelationType relationType); - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Services +{ + public interface IRelationService : IService + { + /// + /// Gets a by its Id + /// + /// Id of the + /// A object + IRelation GetById(int id); + + /// + /// Gets a by its Id + /// + /// Id of the + /// A object + IRelationType GetRelationTypeById(int id); + + /// + /// Gets a by its Id + /// + /// Id of the + /// A object + IRelationType GetRelationTypeById(Guid id); + + /// + /// Gets a by its Alias + /// + /// Alias of the + /// A object + IRelationType GetRelationTypeByAlias(string alias); + + /// + /// Gets all objects + /// + /// Optional array of integer ids to return relations for + /// An enumerable list of objects + IEnumerable GetAllRelations(params int[] ids); + + /// + /// Gets all objects by their + /// + /// to retrieve Relations for + /// An enumerable list of objects + IEnumerable GetAllRelationsByRelationType(RelationType relationType); + + /// + /// Gets all objects by their 's Id + /// + /// Id of the to retrieve Relations for + /// An enumerable list of objects + IEnumerable GetAllRelationsByRelationType(int relationTypeId); + + /// + /// Gets all objects + /// + /// Optional array of integer ids to return relationtypes for + /// An enumerable list of objects + IEnumerable GetAllRelationTypes(params int[] ids); + + /// + /// Gets a list of objects by their parent Id + /// + /// Id of the parent to retrieve relations for + /// An enumerable list of objects + IEnumerable GetByParentId(int id); + + /// + /// Gets a list of objects by their parent entity + /// + /// Parent Entity to retrieve relations for + /// An enumerable list of objects + IEnumerable GetByParent(IUmbracoEntity parent); + + /// + /// Gets a list of objects by their parent entity + /// + /// Parent Entity to retrieve relations for + /// Alias of the type of relation to retrieve + /// An enumerable list of objects + IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias); + + /// + /// Gets a list of objects by their child Id + /// + /// Id of the child to retrieve relations for + /// An enumerable list of objects + IEnumerable GetByChildId(int id); + + /// + /// Gets a list of objects by their child Entity + /// + /// Child Entity to retrieve relations for + /// An enumerable list of objects + IEnumerable GetByChild(IUmbracoEntity child); + + /// + /// Gets a list of objects by their child Entity + /// + /// Child Entity to retrieve relations for + /// Alias of the type of relation to retrieve + /// An enumerable list of objects + IEnumerable GetByChild(IUmbracoEntity child, string relationTypeAlias); + + /// + /// Gets a list of objects by their child or parent Id. + /// Using this method will get you all relations regards of it being a child or parent relation. + /// + /// Id of the child or parent to retrieve relations for + /// An enumerable list of objects + IEnumerable GetByParentOrChildId(int id); + + IEnumerable GetByParentOrChildId(int id, string relationTypeAlias); + + /// + /// Gets a list of objects by the Name of the + /// + /// Name of the to retrieve Relations for + /// An enumerable list of objects + IEnumerable GetByRelationTypeName(string relationTypeName); + + /// + /// Gets a list of objects by the Alias of the + /// + /// Alias of the to retrieve Relations for + /// An enumerable list of objects + IEnumerable GetByRelationTypeAlias(string relationTypeAlias); + + /// + /// Gets a list of objects by the Id of the + /// + /// Id of the to retrieve Relations for + /// An enumerable list of objects + IEnumerable GetByRelationTypeId(int relationTypeId); + + /// + /// Gets the Child object from a Relation as an + /// + /// Relation to retrieve child object from + /// An + IUmbracoEntity GetChildEntityFromRelation(IRelation relation); + + /// + /// Gets the Parent object from a Relation as an + /// + /// Relation to retrieve parent object from + /// An + IUmbracoEntity GetParentEntityFromRelation(IRelation relation); + + /// + /// Gets the Parent and Child objects from a Relation as a "/> with . + /// + /// Relation to retrieve parent and child object from + /// Returns a Tuple with Parent (item1) and Child (item2) + Tuple GetEntitiesFromRelation(IRelation relation); + + /// + /// Gets the Child objects from a list of Relations as a list of objects. + /// + /// List of relations to retrieve child objects from + /// An enumerable list of + IEnumerable GetChildEntitiesFromRelations(IEnumerable relations); + + /// + /// Gets the Parent objects from a list of Relations as a list of objects. + /// + /// List of relations to retrieve parent objects from + /// An enumerable list of + IEnumerable GetParentEntitiesFromRelations(IEnumerable relations); + + /// + /// Gets the Parent and Child objects from a list of Relations as a list of objects. + /// + /// List of relations to retrieve parent and child objects from + /// Optional bool to load the complete object graph when set to False + IEnumerable> GetEntitiesFromRelations( + IEnumerable relations); + + /// + /// Relates two objects by their entity Ids. + /// + /// Id of the parent + /// Id of the child + /// The type of relation to create + /// The created + IRelation Relate(int parentId, int childId, IRelationType relationType); + + /// + /// Relates two objects that are based on the interface. + /// + /// Parent entity + /// Child entity + /// The type of relation to create + /// The created + IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType); + + /// + /// Relates two objects by their entity Ids. + /// + /// Id of the parent + /// Id of the child + /// Alias of the type of relation to create + /// The created + IRelation Relate(int parentId, int childId, string relationTypeAlias); + + /// + /// Relates two objects that are based on the interface. + /// + /// Parent entity + /// Child entity + /// Alias of the type of relation to create + /// The created + IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias); + + /// + /// Checks whether any relations exists for the passed in . + /// + /// to check for relations + /// Returns True if any relations exists for the given , otherwise False + bool HasRelations(IRelationType relationType); + + /// + /// Checks whether any relations exists for the passed in Id. + /// + /// Id of an object to check relations for + /// Returns True if any relations exists with the given Id, otherwise False + bool IsRelated(int id); + + /// + /// Checks whether two items are related + /// + /// Id of the Parent relation + /// Id of the Child relation + /// Returns True if any relations exists with the given Ids, otherwise False + bool AreRelated(int parentId, int childId); + + /// + /// Checks whether two items are related + /// + /// Parent entity + /// Child entity + /// Returns True if any relations exist between the entities, otherwise False + bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child); + + /// + /// Checks whether two items are related + /// + /// Parent entity + /// Child entity + /// Alias of the type of relation to create + /// Returns True if any relations exist between the entities, otherwise False + bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias); + + /// + /// Checks whether two items are related + /// + /// Id of the Parent relation + /// Id of the Child relation + /// Alias of the type of relation to create + /// Returns True if any relations exist between the entities, otherwise False + bool AreRelated(int parentId, int childId, string relationTypeAlias); + + /// + /// Saves a + /// + /// Relation to save + void Save(IRelation relation); + + /// + /// Saves a + /// + /// RelationType to Save + void Save(IRelationType relationType); + + /// + /// Deletes a + /// + /// Relation to Delete + void Delete(IRelation relation); + + /// + /// Deletes a + /// + /// RelationType to Delete + void Delete(IRelationType relationType); + + /// + /// Deletes all objects based on the passed in + /// + /// to Delete Relations for + void DeleteRelationsOfType(IRelationType relationType); + } +} diff --git a/src/Umbraco.Core/Services/RelationService.cs b/src/Umbraco.Core/Services/RelationService.cs index ccf86d425d..a940096ded 100644 --- a/src/Umbraco.Core/Services/RelationService.cs +++ b/src/Umbraco.Core/Services/RelationService.cs @@ -1,748 +1,740 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Events; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Services -{ - public class RelationService : ScopeRepositoryService, IRelationService - { - private readonly IEntityService _entityService; - - public RelationService(IDatabaseUnitOfWorkProvider uowProvider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IEntityService entityService) - : base(uowProvider, repositoryFactory, logger, eventMessagesFactory) - { - if (entityService == null) throw new ArgumentNullException("entityService"); - _entityService = entityService; - } - - /// - /// Gets a by its Id - /// - /// Id of the - /// A object - public IRelation GetById(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - return repository.Get(id); - } - } - - /// - /// Gets a by its Id - /// - /// Id of the - /// A object - public IRelationType GetRelationTypeById(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationTypeRepository(uow); - return repository.Get(id); - } - } - - /// - /// Gets a by its Id - /// - /// Id of the - /// A object - public IRelationType GetRelationTypeById(Guid id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationTypeRepository(uow); - return repository.Get(id); - } - } - - /// - /// Gets a by its Alias - /// - /// Alias of the - /// A object - public IRelationType GetRelationTypeByAlias(string alias) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationTypeRepository(uow); - var query = new Query().Where(x => x.Alias == alias); - return repository.GetByQuery(query).FirstOrDefault(); - } - } - - /// - /// Gets all objects - /// - /// Optional array of integer ids to return relations for - /// An enumerable list of objects - public IEnumerable GetAllRelations(params int[] ids) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - return repository.GetAll(ids); - } - } - - /// - /// Gets all objects by their - /// - /// to retrieve Relations for - /// An enumerable list of objects - public IEnumerable GetAllRelationsByRelationType(RelationType relationType) - { - return GetAllRelationsByRelationType(relationType.Id); - } - - /// - /// Gets all objects by their 's Id - /// - /// Id of the to retrieve Relations for - /// An enumerable list of objects - public IEnumerable GetAllRelationsByRelationType(int relationTypeId) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.RelationTypeId == relationTypeId); - return repository.GetByQuery(query); - } - } - - /// - /// Gets all objects - /// - /// Optional array of integer ids to return relationtypes for - /// An enumerable list of objects - public IEnumerable GetAllRelationTypes(params int[] ids) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationTypeRepository(uow); - return repository.GetAll(ids); - } - } - - /// - /// Gets a list of objects by their parent Id - /// - /// Id of the parent to retrieve relations for - /// An enumerable list of objects - public IEnumerable GetByParentId(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.ParentId == id); - return repository.GetByQuery(query); - } - } - - /// - /// Gets a list of objects by their parent entity - /// - /// Parent Entity to retrieve relations for - /// An enumerable list of objects - public IEnumerable GetByParent(IUmbracoEntity parent) - { - return GetByParentId(parent.Id); - } - - /// - /// Gets a list of objects by their parent entity - /// - /// Parent Entity to retrieve relations for - /// Alias of the type of relation to retrieve - /// An enumerable list of objects - public IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias) - { - return GetByParent(parent).Where(relation => relation.RelationType.Alias == relationTypeAlias); - } - - /// - /// Gets a list of objects by their child Id - /// - /// Id of the child to retrieve relations for - /// An enumerable list of objects - public IEnumerable GetByChildId(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.ChildId == id); - return repository.GetByQuery(query); - } - } - - /// - /// Gets a list of objects by their child Entity - /// - /// Child Entity to retrieve relations for - /// An enumerable list of objects - public IEnumerable GetByChild(IUmbracoEntity child) - { - return GetByChildId(child.Id); - } - - /// - /// Gets a list of objects by their child Entity - /// - /// Child Entity to retrieve relations for - /// Alias of the type of relation to retrieve - /// An enumerable list of objects - public IEnumerable GetByChild(IUmbracoEntity child, string relationTypeAlias) - { - return GetByChild(child).Where(relation => relation.RelationType.Alias == relationTypeAlias); - } - - /// - /// Gets a list of objects by their child or parent Id. - /// Using this method will get you all relations regards of it being a child or parent relation. - /// - /// Id of the child or parent to retrieve relations for - /// An enumerable list of objects - public IEnumerable GetByParentOrChildId(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.ParentId == id || x.ChildId == id); - return repository.GetByQuery(query); - } - } - - public IEnumerable GetByParentOrChildId(int id, string relationTypeAlias) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var typeRepository = RepositoryFactory.CreateRelationTypeRepository(uow); - var rtQuery = new Query().Where(x => x.Alias == relationTypeAlias); - var relationType = typeRepository.GetByQuery(rtQuery).FirstOrDefault(); - if (relationType == null) return Enumerable.Empty(); - - var relationRepo = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => (x.ParentId == id || x.ChildId == id) && x.RelationTypeId == relationType.Id); - return relationRepo.GetByQuery(query); - } - } - - /// - /// Gets a list of objects by the Name of the - /// - /// Name of the to retrieve Relations for - /// An enumerable list of objects - public IEnumerable GetByRelationTypeName(string relationTypeName) - { - List relationTypeIds = null; - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationTypeRepository(uow); - var query = new Query().Where(x => x.Name == relationTypeName); - var relationTypes = repository.GetByQuery(query); - if (relationTypes.Any()) - { - relationTypeIds = relationTypes.Select(x => x.Id).ToList(); - } - } - - if (relationTypeIds == null) - return Enumerable.Empty(); - - return GetRelationsByListOfTypeIds(relationTypeIds); - } - - /// - /// Gets a list of objects by the Alias of the - /// - /// Alias of the to retrieve Relations for - /// An enumerable list of objects - public IEnumerable GetByRelationTypeAlias(string relationTypeAlias) - { - List relationTypeIds = null; - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationTypeRepository(uow); - var query = new Query().Where(x => x.Alias == relationTypeAlias); - var relationTypes = repository.GetByQuery(query); - if (relationTypes.Any()) - { - relationTypeIds = relationTypes.Select(x => x.Id).ToList(); - } - } - - if (relationTypeIds == null) - return Enumerable.Empty(); - - return GetRelationsByListOfTypeIds(relationTypeIds); - } - - /// - /// Gets a list of objects by the Id of the - /// - /// Id of the to retrieve Relations for - /// An enumerable list of objects - public IEnumerable GetByRelationTypeId(int relationTypeId) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.RelationTypeId == relationTypeId); - return repository.GetByQuery(query); - } - } - - /// - /// Gets the Child object from a Relation as an - /// - /// Relation to retrieve child object from - /// Optional bool to load the complete object graph when set to False - /// An - public IUmbracoEntity GetChildEntityFromRelation(IRelation relation, bool loadBaseType = false) - { - var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType); - return _entityService.Get(relation.ChildId, objectType, loadBaseType); - } - - /// - /// Gets the Parent object from a Relation as an - /// - /// Relation to retrieve parent object from - /// Optional bool to load the complete object graph when set to False - /// An - public IUmbracoEntity GetParentEntityFromRelation(IRelation relation, bool loadBaseType = false) - { - var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType); - return _entityService.Get(relation.ParentId, objectType, loadBaseType); - } - - /// - /// Gets the Parent and Child objects from a Relation as a "/> with . - /// - /// Relation to retrieve parent and child object from - /// Optional bool to load the complete object graph when set to False - /// Returns a Tuple with Parent (item1) and Child (item2) - public Tuple GetEntitiesFromRelation(IRelation relation, bool loadBaseType = false) - { - var childObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType); - var parentObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType); - - var child = _entityService.Get(relation.ChildId, childObjectType, loadBaseType); - var parent = _entityService.Get(relation.ParentId, parentObjectType, loadBaseType); - - return new Tuple(parent, child); - } - - /// - /// Gets the Child objects from a list of Relations as a list of objects. - /// - /// List of relations to retrieve child objects from - /// Optional bool to load the complete object graph when set to False - /// An enumerable list of - public IEnumerable GetChildEntitiesFromRelations(IEnumerable relations, bool loadBaseType = false) - { - foreach (var relation in relations) - { - var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType); - yield return _entityService.Get(relation.ChildId, objectType, loadBaseType); - } - } - - /// - /// Gets the Parent objects from a list of Relations as a list of objects. - /// - /// List of relations to retrieve parent objects from - /// Optional bool to load the complete object graph when set to False - /// An enumerable list of - public IEnumerable GetParentEntitiesFromRelations(IEnumerable relations, - bool loadBaseType = false) - { - foreach (var relation in relations) - { - var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType); - yield return _entityService.Get(relation.ParentId, objectType, loadBaseType); - } - } - - /// - /// Gets the Parent and Child objects from a list of Relations as a list of objects. - /// - /// List of relations to retrieve parent and child objects from - /// Optional bool to load the complete object graph when set to False - /// An enumerable list of with - public IEnumerable> GetEntitiesFromRelations( - IEnumerable relations, - bool loadBaseType = false) - { - foreach (var relation in relations) - { - var childObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType); - var parentObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType); - - var child = _entityService.Get(relation.ChildId, childObjectType, loadBaseType); - var parent = _entityService.Get(relation.ParentId, parentObjectType, loadBaseType); - - yield return new Tuple(parent, child); - } - } - - /// - /// Relates two objects by their entity Ids. - /// - /// Id of the parent - /// Id of the child - /// The type of relation to create - /// The created - public IRelation Relate(int parentId, int childId, IRelationType relationType) - { - // Ensure that the RelationType has an indentity before using it to relate two entities - if (relationType.HasIdentity == false) - Save(relationType); - - var relation = new Relation(parentId, childId, relationType); - - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(relation); - if (uow.Events.DispatchCancelable(SavingRelation, this, saveEventArgs)) - { - uow.Commit(); - return relation; - } - - var repository = RepositoryFactory.CreateRelationRepository(uow); - repository.AddOrUpdate(relation); - uow.Commit(); - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(SavedRelation, this, saveEventArgs); - return relation; - } - } - - /// - /// Relates two objects that are based on the interface. - /// - /// Parent entity - /// Child entity - /// The type of relation to create - /// The created - public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType) - { - return Relate(parent.Id, child.Id, relationType); - } - - /// - /// Relates two objects by their entity Ids. - /// - /// Id of the parent - /// Id of the child - /// Alias of the type of relation to create - /// The created - public IRelation Relate(int parentId, int childId, string relationTypeAlias) - { - var relationType = GetRelationTypeByAlias(relationTypeAlias); - if (relationType == null || string.IsNullOrEmpty(relationType.Alias)) - throw new ArgumentNullException(string.Format("No RelationType with Alias '{0}' exists.", relationTypeAlias)); - - return Relate(parentId, childId, relationType); - } - - /// - /// Relates two objects that are based on the interface. - /// - /// Parent entity - /// Child entity - /// Alias of the type of relation to create - /// The created - public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias) - { - var relationType = GetRelationTypeByAlias(relationTypeAlias); - if (relationType == null || string.IsNullOrEmpty(relationType.Alias)) - throw new ArgumentNullException(string.Format("No RelationType with Alias '{0}' exists.", relationTypeAlias)); - - return Relate(parent.Id, child.Id, relationType); - } - - /// - /// Checks whether any relations exists for the passed in . - /// - /// to check for relations - /// Returns True if any relations exists for the given , otherwise False - public bool HasRelations(IRelationType relationType) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.RelationTypeId == relationType.Id); - return repository.GetByQuery(query).Any(); - } - } - - /// - /// Checks whether any relations exists for the passed in Id. - /// - /// Id of an object to check relations for - /// Returns True if any relations exists with the given Id, otherwise False - public bool IsRelated(int id) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.ParentId == id || x.ChildId == id); - return repository.GetByQuery(query).Any(); - } - } - - /// - /// Checks whether two items are related - /// - /// Id of the Parent relation - /// Id of the Child relation - /// Returns True if any relations exists with the given Ids, otherwise False - public bool AreRelated(int parentId, int childId) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.ParentId == parentId && x.ChildId == childId); - return repository.GetByQuery(query).Any(); - } - } - - /// - /// Checks whether two items are related with a given relation type alias - /// - /// Id of the Parent relation - /// Id of the Child relation - /// Alias of the relation type - /// Returns True if any relations exists with the given Ids and relation type, otherwise False - public bool AreRelated(int parentId, int childId, string relationTypeAlias) - { - var relType = GetRelationTypeByAlias(relationTypeAlias); - if (relType == null) - return false; - - return AreRelated(parentId, childId, relType); - } - - - /// - /// Checks whether two items are related with a given relation type - /// - /// Id of the Parent relation - /// Id of the Child relation - /// Type of relation - /// Returns True if any relations exists with the given Ids and relation type, otherwise False - public bool AreRelated(int parentId, int childId, IRelationType relationType) - { - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.ParentId == parentId && x.ChildId == childId && x.RelationTypeId == relationType.Id); - return repository.GetByQuery(query).Any(); - } - } - - /// - /// Checks whether two items are related - /// - /// Parent entity - /// Child entity - /// Returns True if any relations exist between the entities, otherwise False - public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child) - { - return AreRelated(parent.Id, child.Id); - } - - /// - /// Checks whether two items are related - /// - /// Parent entity - /// Child entity - /// Alias of the type of relation to create - /// Returns True if any relations exist between the entities, otherwise False - public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias) - { - return AreRelated(parent.Id, child.Id, relationTypeAlias); - } - - - /// - /// Saves a - /// - /// Relation to save - public void Save(IRelation relation) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(relation); - if (uow.Events.DispatchCancelable(SavingRelation, this, saveEventArgs)) - { - uow.Commit(); - return; - } - var repository = RepositoryFactory.CreateRelationRepository(uow); - repository.AddOrUpdate(relation); - uow.Commit(); - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(SavedRelation, this, saveEventArgs); - } - } - - /// - /// Saves a - /// - /// RelationType to Save - public void Save(IRelationType relationType) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var saveEventArgs = new SaveEventArgs(relationType); - if (uow.Events.DispatchCancelable(SavingRelationType, this, saveEventArgs)) - { - uow.Commit(); - return; - } - var repository = RepositoryFactory.CreateRelationTypeRepository(uow); - repository.AddOrUpdate(relationType); - uow.Commit(); - saveEventArgs.CanCancel = false; - uow.Events.Dispatch(SavedRelationType, this, saveEventArgs); - } - } - - /// - /// Deletes a - /// - /// Relation to Delete - public void Delete(IRelation relation) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var deleteEventArgs = new DeleteEventArgs(relation); - if (uow.Events.DispatchCancelable(DeletingRelation, this, deleteEventArgs)) - { - uow.Commit(); - return; - } - var repository = RepositoryFactory.CreateRelationRepository(uow); - repository.Delete(relation); - uow.Commit(); - deleteEventArgs.CanCancel = false; - uow.Events.Dispatch(DeletedRelation, this, deleteEventArgs); - } - } - - /// - /// Deletes a - /// - /// RelationType to Delete - public void Delete(IRelationType relationType) - { - using (var uow = UowProvider.GetUnitOfWork()) - { - var deleteEventArgs = new DeleteEventArgs(relationType); - if (uow.Events.DispatchCancelable(DeletingRelationType, this, deleteEventArgs)) - { - uow.Commit(); - return; - } - var repository = RepositoryFactory.CreateRelationTypeRepository(uow); - repository.Delete(relationType); - uow.Commit(); - deleteEventArgs.CanCancel = false; - uow.Events.Dispatch(DeletedRelationType, this, deleteEventArgs); - } - } - - /// - /// Deletes all objects based on the passed in - /// - /// to Delete Relations for - public void DeleteRelationsOfType(IRelationType relationType) - { - var relations = new List(); - using (var uow = UowProvider.GetUnitOfWork()) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - var query = new Query().Where(x => x.RelationTypeId == relationType.Id); - relations.AddRange(repository.GetByQuery(query).ToList()); - - foreach (var relation in relations) - { - repository.Delete(relation); - } - uow.Commit(); - uow.Events.Dispatch(DeletedRelation, this, new DeleteEventArgs(relations, false)); - } - } - - #region Private Methods - private IEnumerable GetRelationsByListOfTypeIds(IEnumerable relationTypeIds) - { - var relations = new List(); - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateRelationRepository(uow); - foreach (var relationTypeId in relationTypeIds) - { - int id = relationTypeId; - var query = new Query().Where(x => x.RelationTypeId == id); - relations.AddRange(repository.GetByQuery(query).ToList()); - } - } - return relations; - } - #endregion - - #region Events Handlers - /// - /// Occurs before Deleting a Relation - /// - public static event TypedEventHandler> DeletingRelation; - - /// - /// Occurs after a Relation is Deleted - /// - public static event TypedEventHandler> DeletedRelation; - - /// - /// Occurs before Saving a Relation - /// - public static event TypedEventHandler> SavingRelation; - - /// - /// Occurs after a Relation is Saved - /// - public static event TypedEventHandler> SavedRelation; - - /// - /// Occurs before Deleting a RelationType - /// - public static event TypedEventHandler> DeletingRelationType; - - /// - /// Occurs after a RelationType is Deleted - /// - public static event TypedEventHandler> DeletedRelationType; - - /// - /// Occurs before Saving a RelationType - /// - public static event TypedEventHandler> SavingRelationType; - - /// - /// Occurs after a RelationType is Saved - /// - public static event TypedEventHandler> SavedRelationType; - #endregion - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Services +{ + public class RelationService : ScopeRepositoryService, IRelationService + { + private readonly IEntityService _entityService; + + public RelationService(IDatabaseUnitOfWorkProvider uowProvider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IEntityService entityService) + : base(uowProvider, repositoryFactory, logger, eventMessagesFactory) + { + if (entityService == null) throw new ArgumentNullException("entityService"); + _entityService = entityService; + } + + /// + /// Gets a by its Id + /// + /// Id of the + /// A object + public IRelation GetById(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + return repository.Get(id); + } + } + + /// + /// Gets a by its Id + /// + /// Id of the + /// A object + public IRelationType GetRelationTypeById(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationTypeRepository(uow); + return repository.Get(id); + } + } + + /// + /// Gets a by its Id + /// + /// Id of the + /// A object + public IRelationType GetRelationTypeById(Guid id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationTypeRepository(uow); + return repository.Get(id); + } + } + + /// + /// Gets a by its Alias + /// + /// Alias of the + /// A object + public IRelationType GetRelationTypeByAlias(string alias) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationTypeRepository(uow); + var query = new Query().Where(x => x.Alias == alias); + return repository.GetByQuery(query).FirstOrDefault(); + } + } + + /// + /// Gets all objects + /// + /// Optional array of integer ids to return relations for + /// An enumerable list of objects + public IEnumerable GetAllRelations(params int[] ids) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + return repository.GetAll(ids); + } + } + + /// + /// Gets all objects by their + /// + /// to retrieve Relations for + /// An enumerable list of objects + public IEnumerable GetAllRelationsByRelationType(RelationType relationType) + { + return GetAllRelationsByRelationType(relationType.Id); + } + + /// + /// Gets all objects by their 's Id + /// + /// Id of the to retrieve Relations for + /// An enumerable list of objects + public IEnumerable GetAllRelationsByRelationType(int relationTypeId) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.RelationTypeId == relationTypeId); + return repository.GetByQuery(query); + } + } + + /// + /// Gets all objects + /// + /// Optional array of integer ids to return relationtypes for + /// An enumerable list of objects + public IEnumerable GetAllRelationTypes(params int[] ids) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationTypeRepository(uow); + return repository.GetAll(ids); + } + } + + /// + /// Gets a list of objects by their parent Id + /// + /// Id of the parent to retrieve relations for + /// An enumerable list of objects + public IEnumerable GetByParentId(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.ParentId == id); + return repository.GetByQuery(query); + } + } + + /// + /// Gets a list of objects by their parent entity + /// + /// Parent Entity to retrieve relations for + /// An enumerable list of objects + public IEnumerable GetByParent(IUmbracoEntity parent) + { + return GetByParentId(parent.Id); + } + + /// + /// Gets a list of objects by their parent entity + /// + /// Parent Entity to retrieve relations for + /// Alias of the type of relation to retrieve + /// An enumerable list of objects + public IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias) + { + return GetByParent(parent).Where(relation => relation.RelationType.Alias == relationTypeAlias); + } + + /// + /// Gets a list of objects by their child Id + /// + /// Id of the child to retrieve relations for + /// An enumerable list of objects + public IEnumerable GetByChildId(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.ChildId == id); + return repository.GetByQuery(query); + } + } + + /// + /// Gets a list of objects by their child Entity + /// + /// Child Entity to retrieve relations for + /// An enumerable list of objects + public IEnumerable GetByChild(IUmbracoEntity child) + { + return GetByChildId(child.Id); + } + + /// + /// Gets a list of objects by their child Entity + /// + /// Child Entity to retrieve relations for + /// Alias of the type of relation to retrieve + /// An enumerable list of objects + public IEnumerable GetByChild(IUmbracoEntity child, string relationTypeAlias) + { + return GetByChild(child).Where(relation => relation.RelationType.Alias == relationTypeAlias); + } + + /// + /// Gets a list of objects by their child or parent Id. + /// Using this method will get you all relations regards of it being a child or parent relation. + /// + /// Id of the child or parent to retrieve relations for + /// An enumerable list of objects + public IEnumerable GetByParentOrChildId(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.ParentId == id || x.ChildId == id); + return repository.GetByQuery(query); + } + } + + public IEnumerable GetByParentOrChildId(int id, string relationTypeAlias) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var typeRepository = RepositoryFactory.CreateRelationTypeRepository(uow); + var rtQuery = new Query().Where(x => x.Alias == relationTypeAlias); + var relationType = typeRepository.GetByQuery(rtQuery).FirstOrDefault(); + if (relationType == null) return Enumerable.Empty(); + + var relationRepo = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => (x.ParentId == id || x.ChildId == id) && x.RelationTypeId == relationType.Id); + return relationRepo.GetByQuery(query); + } + } + + /// + /// Gets a list of objects by the Name of the + /// + /// Name of the to retrieve Relations for + /// An enumerable list of objects + public IEnumerable GetByRelationTypeName(string relationTypeName) + { + List relationTypeIds = null; + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationTypeRepository(uow); + var query = new Query().Where(x => x.Name == relationTypeName); + var relationTypes = repository.GetByQuery(query); + if (relationTypes.Any()) + { + relationTypeIds = relationTypes.Select(x => x.Id).ToList(); + } + } + + if (relationTypeIds == null) + return Enumerable.Empty(); + + return GetRelationsByListOfTypeIds(relationTypeIds); + } + + /// + /// Gets a list of objects by the Alias of the + /// + /// Alias of the to retrieve Relations for + /// An enumerable list of objects + public IEnumerable GetByRelationTypeAlias(string relationTypeAlias) + { + List relationTypeIds = null; + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationTypeRepository(uow); + var query = new Query().Where(x => x.Alias == relationTypeAlias); + var relationTypes = repository.GetByQuery(query); + if (relationTypes.Any()) + { + relationTypeIds = relationTypes.Select(x => x.Id).ToList(); + } + } + + if (relationTypeIds == null) + return Enumerable.Empty(); + + return GetRelationsByListOfTypeIds(relationTypeIds); + } + + /// + /// Gets a list of objects by the Id of the + /// + /// Id of the to retrieve Relations for + /// An enumerable list of objects + public IEnumerable GetByRelationTypeId(int relationTypeId) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.RelationTypeId == relationTypeId); + return repository.GetByQuery(query); + } + } + + /// + /// Gets the Child object from a Relation as an + /// + /// Relation to retrieve child object from + /// An + public IUmbracoEntity GetChildEntityFromRelation(IRelation relation) + { + var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType); + return _entityService.Get(relation.ChildId, objectType); + } + + /// + /// Gets the Parent object from a Relation as an + /// + /// Relation to retrieve parent object from + /// An + public IUmbracoEntity GetParentEntityFromRelation(IRelation relation) + { + var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType); + return _entityService.Get(relation.ParentId, objectType); + } + + /// + /// Gets the Parent and Child objects from a Relation as a "/> with . + /// + /// Relation to retrieve parent and child object from + /// Returns a Tuple with Parent (item1) and Child (item2) + public Tuple GetEntitiesFromRelation(IRelation relation) + { + var childObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType); + var parentObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType); + + var child = _entityService.Get(relation.ChildId, childObjectType); + var parent = _entityService.Get(relation.ParentId, parentObjectType); + + return new Tuple(parent, child); + } + + /// + /// Gets the Child objects from a list of Relations as a list of objects. + /// + /// List of relations to retrieve child objects from + /// An enumerable list of + public IEnumerable GetChildEntitiesFromRelations(IEnumerable relations) + { + foreach (var relation in relations) + { + var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType); + yield return _entityService.Get(relation.ChildId, objectType); + } + } + + /// + /// Gets the Parent objects from a list of Relations as a list of objects. + /// + /// List of relations to retrieve parent objects from + /// An enumerable list of + public IEnumerable GetParentEntitiesFromRelations(IEnumerable relations) + { + foreach (var relation in relations) + { + var objectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType); + yield return _entityService.Get(relation.ParentId, objectType); + } + } + + /// + /// Gets the Parent and Child objects from a list of Relations as a list of objects. + /// + /// List of relations to retrieve parent and child objects from + /// An enumerable list of with + public IEnumerable> GetEntitiesFromRelations( + IEnumerable relations) + { + foreach (var relation in relations) + { + var childObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ChildObjectType); + var parentObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(relation.RelationType.ParentObjectType); + + var child = _entityService.Get(relation.ChildId, childObjectType); + var parent = _entityService.Get(relation.ParentId, parentObjectType); + + yield return new Tuple(parent, child); + } + } + + /// + /// Relates two objects by their entity Ids. + /// + /// Id of the parent + /// Id of the child + /// The type of relation to create + /// The created + public IRelation Relate(int parentId, int childId, IRelationType relationType) + { + // Ensure that the RelationType has an indentity before using it to relate two entities + if (relationType.HasIdentity == false) + Save(relationType); + + var relation = new Relation(parentId, childId, relationType); + + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(relation); + if (uow.Events.DispatchCancelable(SavingRelation, this, saveEventArgs)) + { + uow.Commit(); + return relation; + } + + var repository = RepositoryFactory.CreateRelationRepository(uow); + repository.AddOrUpdate(relation); + uow.Commit(); + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(SavedRelation, this, saveEventArgs); + return relation; + } + } + + /// + /// Relates two objects that are based on the interface. + /// + /// Parent entity + /// Child entity + /// The type of relation to create + /// The created + public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType) + { + return Relate(parent.Id, child.Id, relationType); + } + + /// + /// Relates two objects by their entity Ids. + /// + /// Id of the parent + /// Id of the child + /// Alias of the type of relation to create + /// The created + public IRelation Relate(int parentId, int childId, string relationTypeAlias) + { + var relationType = GetRelationTypeByAlias(relationTypeAlias); + if (relationType == null || string.IsNullOrEmpty(relationType.Alias)) + throw new ArgumentNullException(string.Format("No RelationType with Alias '{0}' exists.", relationTypeAlias)); + + return Relate(parentId, childId, relationType); + } + + /// + /// Relates two objects that are based on the interface. + /// + /// Parent entity + /// Child entity + /// Alias of the type of relation to create + /// The created + public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias) + { + var relationType = GetRelationTypeByAlias(relationTypeAlias); + if (relationType == null || string.IsNullOrEmpty(relationType.Alias)) + throw new ArgumentNullException(string.Format("No RelationType with Alias '{0}' exists.", relationTypeAlias)); + + return Relate(parent.Id, child.Id, relationType); + } + + /// + /// Checks whether any relations exists for the passed in . + /// + /// to check for relations + /// Returns True if any relations exists for the given , otherwise False + public bool HasRelations(IRelationType relationType) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.RelationTypeId == relationType.Id); + return repository.GetByQuery(query).Any(); + } + } + + /// + /// Checks whether any relations exists for the passed in Id. + /// + /// Id of an object to check relations for + /// Returns True if any relations exists with the given Id, otherwise False + public bool IsRelated(int id) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.ParentId == id || x.ChildId == id); + return repository.GetByQuery(query).Any(); + } + } + + /// + /// Checks whether two items are related + /// + /// Id of the Parent relation + /// Id of the Child relation + /// Returns True if any relations exists with the given Ids, otherwise False + public bool AreRelated(int parentId, int childId) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.ParentId == parentId && x.ChildId == childId); + return repository.GetByQuery(query).Any(); + } + } + + /// + /// Checks whether two items are related with a given relation type alias + /// + /// Id of the Parent relation + /// Id of the Child relation + /// Alias of the relation type + /// Returns True if any relations exists with the given Ids and relation type, otherwise False + public bool AreRelated(int parentId, int childId, string relationTypeAlias) + { + var relType = GetRelationTypeByAlias(relationTypeAlias); + if (relType == null) + return false; + + return AreRelated(parentId, childId, relType); + } + + + /// + /// Checks whether two items are related with a given relation type + /// + /// Id of the Parent relation + /// Id of the Child relation + /// Type of relation + /// Returns True if any relations exists with the given Ids and relation type, otherwise False + public bool AreRelated(int parentId, int childId, IRelationType relationType) + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.ParentId == parentId && x.ChildId == childId && x.RelationTypeId == relationType.Id); + return repository.GetByQuery(query).Any(); + } + } + + /// + /// Checks whether two items are related + /// + /// Parent entity + /// Child entity + /// Returns True if any relations exist between the entities, otherwise False + public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child) + { + return AreRelated(parent.Id, child.Id); + } + + /// + /// Checks whether two items are related + /// + /// Parent entity + /// Child entity + /// Alias of the type of relation to create + /// Returns True if any relations exist between the entities, otherwise False + public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias) + { + return AreRelated(parent.Id, child.Id, relationTypeAlias); + } + + + /// + /// Saves a + /// + /// Relation to save + public void Save(IRelation relation) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(relation); + if (uow.Events.DispatchCancelable(SavingRelation, this, saveEventArgs)) + { + uow.Commit(); + return; + } + var repository = RepositoryFactory.CreateRelationRepository(uow); + repository.AddOrUpdate(relation); + uow.Commit(); + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(SavedRelation, this, saveEventArgs); + } + } + + /// + /// Saves a + /// + /// RelationType to Save + public void Save(IRelationType relationType) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var saveEventArgs = new SaveEventArgs(relationType); + if (uow.Events.DispatchCancelable(SavingRelationType, this, saveEventArgs)) + { + uow.Commit(); + return; + } + var repository = RepositoryFactory.CreateRelationTypeRepository(uow); + repository.AddOrUpdate(relationType); + uow.Commit(); + saveEventArgs.CanCancel = false; + uow.Events.Dispatch(SavedRelationType, this, saveEventArgs); + } + } + + /// + /// Deletes a + /// + /// Relation to Delete + public void Delete(IRelation relation) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var deleteEventArgs = new DeleteEventArgs(relation); + if (uow.Events.DispatchCancelable(DeletingRelation, this, deleteEventArgs)) + { + uow.Commit(); + return; + } + var repository = RepositoryFactory.CreateRelationRepository(uow); + repository.Delete(relation); + uow.Commit(); + deleteEventArgs.CanCancel = false; + uow.Events.Dispatch(DeletedRelation, this, deleteEventArgs); + } + } + + /// + /// Deletes a + /// + /// RelationType to Delete + public void Delete(IRelationType relationType) + { + using (var uow = UowProvider.GetUnitOfWork()) + { + var deleteEventArgs = new DeleteEventArgs(relationType); + if (uow.Events.DispatchCancelable(DeletingRelationType, this, deleteEventArgs)) + { + uow.Commit(); + return; + } + var repository = RepositoryFactory.CreateRelationTypeRepository(uow); + repository.Delete(relationType); + uow.Commit(); + deleteEventArgs.CanCancel = false; + uow.Events.Dispatch(DeletedRelationType, this, deleteEventArgs); + } + } + + /// + /// Deletes all objects based on the passed in + /// + /// to Delete Relations for + public void DeleteRelationsOfType(IRelationType relationType) + { + var relations = new List(); + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + var query = new Query().Where(x => x.RelationTypeId == relationType.Id); + relations.AddRange(repository.GetByQuery(query).ToList()); + + foreach (var relation in relations) + { + repository.Delete(relation); + } + uow.Commit(); + uow.Events.Dispatch(DeletedRelation, this, new DeleteEventArgs(relations, false)); + } + } + + #region Private Methods + private IEnumerable GetRelationsByListOfTypeIds(IEnumerable relationTypeIds) + { + var relations = new List(); + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateRelationRepository(uow); + foreach (var relationTypeId in relationTypeIds) + { + int id = relationTypeId; + var query = new Query().Where(x => x.RelationTypeId == id); + relations.AddRange(repository.GetByQuery(query).ToList()); + } + } + return relations; + } + #endregion + + #region Events Handlers + /// + /// Occurs before Deleting a Relation + /// + public static event TypedEventHandler> DeletingRelation; + + /// + /// Occurs after a Relation is Deleted + /// + public static event TypedEventHandler> DeletedRelation; + + /// + /// Occurs before Saving a Relation + /// + public static event TypedEventHandler> SavingRelation; + + /// + /// Occurs after a Relation is Saved + /// + public static event TypedEventHandler> SavedRelation; + + /// + /// Occurs before Deleting a RelationType + /// + public static event TypedEventHandler> DeletingRelationType; + + /// + /// Occurs after a RelationType is Deleted + /// + public static event TypedEventHandler> DeletedRelationType; + + /// + /// Occurs before Saving a RelationType + /// + public static event TypedEventHandler> SavingRelationType; + + /// + /// Occurs after a RelationType is Saved + /// + public static event TypedEventHandler> SavedRelationType; + #endregion + } +} diff --git a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js index 636437e387..2f31d36b62 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js @@ -4,7 +4,7 @@ * @description A helper object used for dealing with media items **/ function mediaHelper(umbRequestHelper) { - + //container of fileresolvers var _mediaFileResolvers = {}; @@ -13,11 +13,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#getImagePropertyValue * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Returns the file path associated with the media property if there is one - * + * * @param {object} options Options object * @param {object} options.mediaModel The media object to retrieve the image path from * @param {object} options.imageOnly Optional, if true then will only return a path if the media item is an image @@ -75,16 +75,16 @@ function mediaHelper(umbRequestHelper) { return ""; }, - + /** * @ngdoc function * @name umbraco.services.mediaHelper#getImagePropertyValue * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Returns the actual image path associated with the image property if there is one - * + * * @param {object} options Options object * @param {object} options.imageModel The media object to retrieve the image path from */ @@ -104,11 +104,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#getThumbnail * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * formats the display model used to display the content to the model used to save the content - * + * * @param {object} options Options object * @param {object} options.imageModel The media object to retrieve the image path from */ @@ -133,54 +133,48 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#resolveFileFromEntity * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Gets the media file url for a media entity returned with the entityResource - * + * * @param {object} mediaEntity A media Entity returned from the entityResource * @param {boolean} thumbnail Whether to return the thumbnail url or normal url */ - resolveFileFromEntity : function(mediaEntity, thumbnail) { - - if (!angular.isObject(mediaEntity.metaData)) { + resolveFileFromEntity: function (mediaEntity, thumbnail) { + + if (!angular.isObject(mediaEntity.metaData) || !mediaEntity.metaData.MediaPath) { throw "Cannot resolve the file url from the mediaEntity, it does not contain the required metaData"; } - var values = _.values(mediaEntity.metaData); - for (var i = 0; i < values.length; i++) { - var val = values[i]; - if (angular.isObject(val) && val.PropertyEditorAlias) { - for (var resolver in _mediaFileResolvers) { - if (val.PropertyEditorAlias === resolver) { - //we need to format a property variable that coincides with how the property would be structured - // if it came from the mediaResource just to keep things slightly easier for the file resolvers. - var property = { value: val.Value }; - - return _mediaFileResolvers[resolver](property, mediaEntity, thumbnail); - } - } + if (thumbnail) { + if (this.detectIfImageByExtension(mediaEntity.metaData.MediaPath)) { + return this.getThumbnailFromPath(mediaEntity.metaData.MediaPath); + } + else { + return null; } } - - return ""; + else { + return mediaEntity.metaData.MediaPath; + } }, /** * @ngdoc function * @name umbraco.services.mediaHelper#resolveFile * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Gets the media file url for a media object returned with the mediaResource - * + * * @param {object} mediaEntity A media Entity returned from the entityResource * @param {boolean} thumbnail Whether to return the thumbnail url or normal url */ /*jshint loopfunc: true */ resolveFile : function(mediaItem, thumbnail){ - + function iterateProps(props){ var res = null; for(var resolver in _mediaFileResolvers) { @@ -191,7 +185,7 @@ function mediaHelper(umbRequestHelper) { } } - return res; + return res; } //we either have properties raw on the object, or spread out on tabs @@ -208,7 +202,7 @@ function mediaHelper(umbRequestHelper) { } } } - return result; + return result; }, /*jshint loopfunc: true */ @@ -246,11 +240,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#scaleToMaxSize * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Finds the corrct max width and max height, given maximum dimensions and keeping aspect ratios - * + * * @param {number} maxSize Maximum width & height * @param {number} width Current width * @param {number} height Current height @@ -289,11 +283,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#getThumbnailFromPath * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Returns the path to the thumbnail version of a given media library image path - * + * * @param {string} imagePath Image path, ex: /media/1234/my-image.jpg */ getThumbnailFromPath: function (imagePath) { @@ -319,11 +313,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#detectIfImageByExtension * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Returns true/false, indicating if the given path has an allowed image extension - * + * * @param {string} imagePath Image path, ex: /media/1234/my-image.jpg */ detectIfImageByExtension: function (imagePath) { @@ -331,7 +325,7 @@ function mediaHelper(umbRequestHelper) { if (!imagePath) { return false; } - + var lowered = imagePath.toLowerCase(); var ext = lowered.substr(lowered.lastIndexOf(".") + 1); return ("," + Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes + ",").indexOf("," + ext + ",") !== -1; @@ -388,6 +382,6 @@ function mediaHelper(umbRequestHelper) { var ext = lowered.substr(lowered.lastIndexOf(".") + 1); return ext; } - + }; }angular.module('umbraco.services').factory('mediaHelper', mediaHelper); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js index f337dbce18..da7ca307ca 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js @@ -1,134 +1,140 @@ -//used for the media picker dialog -angular.module("umbraco") - .controller("Umbraco.Dialogs.MediaPickerController", - function($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService) { - - var dialogOptions = $scope.dialogOptions; - - $scope.onlyImages = dialogOptions.onlyImages; - $scope.showDetails = dialogOptions.showDetails; - $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; - $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; - $scope.cropSize = dialogOptions.cropSize; - - //preload selected item - $scope.target = undefined; - if (dialogOptions.currentTarget) { - $scope.target = dialogOptions.currentTarget; - } - - $scope.acceptedMediatypes = []; - mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) - .then(function(types) { - $scope.acceptedMediatypes = types; - }); - - $scope.upload = function(v) { - angular.element(".umb-file-dropzone-directive .file-select").click(); - }; - - $scope.dragLeave = function(el, event) { - $scope.activeDrag = false; - }; - - $scope.dragEnter = function(el, event) { - $scope.activeDrag = true; - }; - - $scope.submitFolder = function(e) { - if (e.keyCode === 13) { - e.preventDefault(); - - mediaResource - .addFolder($scope.newFolderName, $scope.currentFolder.id) - .then(function(data) { - $scope.showFolderInput = false; - $scope.newFolderName = ""; - - //we've added a new folder so lets clear the tree cache for that specific item - treeService.clearCache({ - cacheKey: "__media", //this is the main media tree cache key - childrenOf: data.parentId //clear the children of the parent - }); - - $scope.gotoFolder(data); - }); - } - }; - - $scope.gotoFolder = function(folder) { - if (!folder) { - folder = { id: -1, name: "Media", icon: "icon-folder" }; - } - - if (folder.id > 0) { - entityResource.getAncestors(folder.id, "media") - .then(function(anc) { - // anc.splice(0,1); - $scope.path = _.filter(anc, - function(f) { - return f.path.indexOf($scope.startNodeId) !== -1; - }); - }); - - mediaTypeHelper.getAllowedImagetypes(folder.id) - .then(function(types) { - $scope.acceptedMediatypes = types; - }); - } else { - $scope.path = []; - } - - //mediaResource.rootMedia() - mediaResource.getChildren(folder.id) - .then(function(data) { - $scope.searchTerm = ""; - $scope.images = data.items ? data.items : []; - }); - - $scope.currentFolder = folder; - }; - - - $scope.clickHandler = function(image, ev, select) { - ev.preventDefault(); - - if (image.isFolder && !select) { - $scope.gotoFolder(image); - } else { - eventsService.emit("dialogs.mediaPicker.select", image); - - //we have 3 options add to collection (if multi) show details, or submit it right back to the callback - if ($scope.multiPicker) { - $scope.select(image); - image.cssclass = ($scope.dialogData.selection.indexOf(image) > -1) ? "selected" : ""; - } else if ($scope.showDetails) { - $scope.target = image; - $scope.target.url = mediaHelper.resolveFile(image); - } else { - $scope.submit(image); - } - } - }; - - $scope.exitDetails = function() { - if (!$scope.currentFolder) { - $scope.gotoFolder(); - } - - $scope.target = undefined; - }; - - $scope.onUploadComplete = function() { - $scope.gotoFolder($scope.currentFolder); - }; - - $scope.onFilesQueue = function() { - $scope.activeDrag = false; - }; - - //default root item - if (!$scope.target) { - $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); - } - }); \ No newline at end of file +//used for the media picker dialog +angular.module("umbraco") + .controller("Umbraco.Dialogs.MediaPickerController", + function($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService) { + + var dialogOptions = $scope.dialogOptions; + + $scope.onlyImages = dialogOptions.onlyImages; + $scope.showDetails = dialogOptions.showDetails; + $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; + $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; + $scope.cropSize = dialogOptions.cropSize; + + //preload selected item + $scope.target = undefined; + if (dialogOptions.currentTarget) { + $scope.target = dialogOptions.currentTarget; + } + + $scope.acceptedMediatypes = []; + mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) + .then(function(types) { + $scope.acceptedMediatypes = types; + }); + + $scope.upload = function(v) { + angular.element(".umb-file-dropzone-directive .file-select").click(); + }; + + $scope.dragLeave = function(el, event) { + $scope.activeDrag = false; + }; + + $scope.dragEnter = function(el, event) { + $scope.activeDrag = true; + }; + + $scope.submitFolder = function(e) { + if (e.keyCode === 13) { + e.preventDefault(); + + mediaResource + .addFolder($scope.newFolderName, $scope.currentFolder.id) + .then(function(data) { + $scope.showFolderInput = false; + $scope.newFolderName = ""; + + //we've added a new folder so lets clear the tree cache for that specific item + treeService.clearCache({ + cacheKey: "__media", //this is the main media tree cache key + childrenOf: data.parentId //clear the children of the parent + }); + + $scope.gotoFolder(data); + }); + } + }; + + $scope.gotoFolder = function(folder) { + if (!folder) { + folder = { id: -1, name: "Media", icon: "icon-folder" }; + } + + if (folder.id > 0) { + entityResource.getAncestors(folder.id, "media") + .then(function(anc) { + // anc.splice(0,1); + $scope.path = _.filter(anc, + function(f) { + return f.path.indexOf($scope.startNodeId) !== -1; + }); + }); + + mediaTypeHelper.getAllowedImagetypes(folder.id) + .then(function(types) { + $scope.acceptedMediatypes = types; + }); + } else { + $scope.path = []; + } + + //mediaResource.rootMedia() + entityResource.getChildren(folder.id, "Media") + .then(function(data) { + for (i=0;i -1) ? "selected" : ""; + } else if ($scope.showDetails) { + $scope.target = image; + $scope.target.url = mediaHelper.resolveFile(image); + } else { + $scope.submit(image); + } + } + }; + + $scope.exitDetails = function() { + if (!$scope.currentFolder) { + $scope.gotoFolder(); + } + + $scope.target = undefined; + }; + + $scope.onUploadComplete = function() { + $scope.gotoFolder($scope.currentFolder); + }; + + $scope.onFilesQueue = function() { + $scope.activeDrag = false; + }; + + //default root item + if (!$scope.target) { + $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); + } + }); 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 e7d14c9871..400d3a7bb5 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 @@ -90,7 +90,7 @@ angular.module("umbraco") var id = $scope.target.udi ? $scope.target.udi : $scope.target.id var altText = $scope.target.altText; if (id) { - mediaResource.getById(id) + entityResource.getById(id, "Media") .then(function (node) { $scope.target = node; if (ensureWithinStartNode(node)) { @@ -388,10 +388,17 @@ angular.module("umbraco") function getChildren(id) { $scope.loading = true; - return mediaResource.getChildren(id, { dataTypeId: $scope.model.dataTypeId }) + return entityResource.getChildren(id, "Media") .then(function(data) { + + for (i=0;i - /// Summary description for Access. - /// - [Obsolete("Use Umbraco.Core.Service.IPublicAccessService instead")] - public class Access - { - - [Obsolete("Do not access this property directly, it is not thread safe, use GetXmlDocumentCopy instead")] - public static XmlDocument AccessXml - { - get { return GetXmlDocumentCopy(); } - } - - //NOTE: This is here purely for backwards compat - [Obsolete("This should never be used, the data is stored in the database now")] - public static XmlDocument GetXmlDocumentCopy() - { - var allAccessEntries = ApplicationContext.Current.Services.PublicAccessService.GetAll().ToArray(); - - var xml = XDocument.Parse(""); - foreach (var entry in allAccessEntries) - { - var pageXml = new XElement("page", - new XAttribute("id", entry.ProtectedNodeId), - new XAttribute("loginPage", entry.LoginNodeId), - new XAttribute("noRightsPage", entry.NoAccessNodeId)); - - foreach (var rule in entry.Rules) - { - if (rule.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType) - { - //if there is a member id claim then it is 'simple' (this is how legacy worked) - pageXml.Add(new XAttribute("simple", "True")); - pageXml.Add(new XAttribute("memberId", rule.RuleValue)); - } - else if (rule.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType) - { - pageXml.Add(new XElement("group", new XAttribute("id", rule.RuleValue))); - } - } - - xml.Root.Add(pageXml); - } - - return xml.ToXmlDocument(); - } - - #region Manipulation methods - - public static void AddMembershipRoleToDocument(int documentId, string role) - { - //event - var doc = new Document(documentId); - var e = new AddMemberShipRoleToDocumentEventArgs(); - new Access().FireBeforeAddMemberShipRoleToDocument(doc, role, e); - - if (e.Cancel) return; - - - var entry = ApplicationContext.Current.Services.PublicAccessService.AddRule( - doc.ContentEntity, - Constants.Conventions.PublicAccess.MemberRoleRuleType, - role); - - if (entry.Success == false && entry.Result.Entity == null) - { - throw new Exception("Document is not protected!"); - } - - Save(); - - new Access().FireAfterAddMemberShipRoleToDocument(doc, role, e); - } - - - [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] - public static void AddMemberGroupToDocument(int DocumentId, int MemberGroupId) - { - var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); - - if (content == null) - throw new Exception("No content found with document id " + DocumentId); - - if (ApplicationContext.Current.Services.PublicAccessService.AddRule( - content, - Constants.Conventions.PublicAccess.MemberGroupIdRuleType, - MemberGroupId.ToString(CultureInfo.InvariantCulture))) - { - Save(); - } - - } - - [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] - public static void AddMemberToDocument(int DocumentId, int MemberId) - { - var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); - - if (content == null) - throw new Exception("No content found with document id " + DocumentId); - - if (ApplicationContext.Current.Services.PublicAccessService.AddRule( - content, - Constants.Conventions.PublicAccess.MemberIdRuleType, - MemberId.ToString(CultureInfo.InvariantCulture))) - { - Save(); - } - - } - - public static void AddMembershipUserToDocument(int documentId, string membershipUserName) - { - //event - var doc = new Document(documentId); - var e = new AddMembershipUserToDocumentEventArgs(); - new Access().FireBeforeAddMembershipUserToDocument(doc, membershipUserName, e); - - if (e.Cancel) return; - - var entry = ApplicationContext.Current.Services.PublicAccessService.AddRule( - doc.ContentEntity, - Constants.Conventions.PublicAccess.MemberUsernameRuleType, - membershipUserName); - - if (entry.Success == false && entry.Result.Entity == null) - { - throw new Exception("Document is not protected!"); - } - - if (entry) - { - Save(); - new Access().FireAfterAddMembershipUserToDocument(doc, membershipUserName, e); - } - - } - - [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] - public static void RemoveMemberGroupFromDocument(int DocumentId, int MemberGroupId) - { - var doc = new Document(DocumentId); - - var entry = ApplicationContext.Current.Services.PublicAccessService.AddRule( - doc.ContentEntity, - Constants.Conventions.PublicAccess.MemberGroupIdRuleType, - MemberGroupId.ToString(CultureInfo.InvariantCulture)); - - if (entry.Success == false && entry.Result.Entity == null) - { - throw new Exception("Document is not protected!"); - } - - if (entry) - { - Save(); - } - } - - public static void RemoveMembershipRoleFromDocument(int documentId, string role) - { - var doc = new Document(documentId); - var e = new RemoveMemberShipRoleFromDocumentEventArgs(); - new Access().FireBeforeRemoveMemberShipRoleFromDocument(doc, role, e); - - if (e.Cancel) return; - - if (ApplicationContext.Current.Services.PublicAccessService.RemoveRule( - doc.ContentEntity, - Constants.Conventions.PublicAccess.MemberRoleRuleType, - role)) - { - Save(); - new Access().FireAfterRemoveMemberShipRoleFromDocument(doc, role, e); - }; - - } - - public static bool RenameMemberShipRole(string oldRolename, string newRolename) - { - var hasChange = ApplicationContext.Current.Services.PublicAccessService.RenameMemberGroupRoleRules(oldRolename, newRolename); - - if (hasChange) - Save(); - - return hasChange; - } - - public static void ProtectPage(bool Simple, int DocumentId, int LoginDocumentId, int ErrorDocumentId) - { - var doc = new Document(DocumentId); - var e = new AddProtectionEventArgs(); - new Access().FireBeforeAddProtection(doc, e); - - if (e.Cancel) return; - - var loginContent = ApplicationContext.Current.Services.ContentService.GetById(LoginDocumentId); - if (loginContent == null) throw new NullReferenceException("No content item found with id " + LoginDocumentId); - var noAccessContent = ApplicationContext.Current.Services.ContentService.GetById(ErrorDocumentId); - if (noAccessContent == null) throw new NullReferenceException("No content item found with id " + ErrorDocumentId); - - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(doc.ContentEntity.Id.ToString()); - if (entry != null) - { - if (Simple) - { - // if using simple mode, make sure that all existing groups are removed - entry.ClearRules(); - } - - //ensure the correct ids are applied - entry.LoginNodeId = loginContent.Id; - entry.NoAccessNodeId = noAccessContent.Id; - } - else - { - entry = new PublicAccessEntry(doc.ContentEntity, - ApplicationContext.Current.Services.ContentService.GetById(LoginDocumentId), - ApplicationContext.Current.Services.ContentService.GetById(ErrorDocumentId), - new List()); - } - - if (ApplicationContext.Current.Services.PublicAccessService.Save(entry)) - { - Save(); - new Access().FireAfterAddProtection(new Document(DocumentId), e); - } - - } - - public static void RemoveProtection(int DocumentId) - { - //event - var doc = new Document(DocumentId); - var e = new RemoveProtectionEventArgs(); - new Access().FireBeforeRemoveProtection(doc, e); - - if (e.Cancel) return; - - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(doc.ContentEntity); - if (entry != null) - { - ApplicationContext.Current.Services.PublicAccessService.Delete(entry); - } - - Save(); - - new Access().FireAfterRemoveProtection(doc, e); - } - #endregion - - #region Reading methods - - [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] - public static bool IsProtectedByGroup(int DocumentId, int GroupId) - { - var d = new Document(DocumentId); - - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(d.ContentEntity); - if (entry == null) return false; - - return entry.Rules - .Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberGroupIdRuleType - && x.RuleValue == GroupId.ToString(CultureInfo.InvariantCulture)); - - } - - public static bool IsProtectedByMembershipRole(int documentId, string role) - { - var content = ApplicationContext.Current.Services.ContentService.GetById(documentId); - - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); - if (entry == null) return false; - - return entry.Rules - .Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType - && x.RuleValue == role); - - } - - public static string[] GetAccessingMembershipRoles(int documentId, string path) - { - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(path.EnsureEndsWith("," + documentId)); - if (entry == null) return new string[] { }; - - var memberGroupRoleRules = entry.Rules.Where(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType); - return memberGroupRoleRules.Select(x => x.RuleValue).ToArray(); - - } - - [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] - public static member.MemberGroup[] GetAccessingGroups(int DocumentId) - { - var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); - if (content == null) return null; - - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); - if (entry == null) return null; - - var memberGroupIdRules = entry.Rules.Where(x => x.RuleType == Constants.Conventions.PublicAccess.MemberGroupIdRuleType); - - return memberGroupIdRules.Select(x => new member.MemberGroup(int.Parse(x.RuleValue))).ToArray(); - - } - - [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] - public static member.Member GetAccessingMember(int DocumentId) - { - var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); - if (content == null) return null; - - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); - if (entry == null) return null; - - //legacy would throw an exception here if it was not 'simple' and simple means based on a member id in this case - if (entry.Rules.All(x => x.RuleType != Constants.Conventions.PublicAccess.MemberIdRuleType)) - { - throw new Exception("Document isn't protected using Simple mechanism. Use GetAccessingMemberGroups instead"); - } - - var memberIdRule = entry.Rules.First(x => x.RuleType == Constants.Conventions.PublicAccess.MemberIdRuleType); - return new member.Member(int.Parse(memberIdRule.RuleValue)); - - } - - public static MembershipUser GetAccessingMembershipUser(int documentId) - { - var content = ApplicationContext.Current.Services.ContentService.GetById(documentId); - if (content == null) return null; - - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); - if (entry == null) return null; - - //legacy would throw an exception here if it was not 'simple' and simple means based on a username - if (entry.Rules.All(x => x.RuleType != Constants.Conventions.PublicAccess.MemberUsernameRuleType)) - { - throw new Exception("Document isn't protected using Simple mechanism. Use GetAccessingMemberGroups instead"); - } - - var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); - var usernameRule = entry.Rules.First(x => x.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType); - return provider.GetUser(usernameRule.RuleValue, false); - - } - - - [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] - public static bool HasAccess(int DocumentId, member.Member Member) - { - var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); - if (content == null) return true; - - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); - if (entry == null) return true; - - var memberGroupIds = Member.Groups.Values.Cast().Select(x => x.Id.ToString(CultureInfo.InvariantCulture)).ToArray(); - return entry.Rules.Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberGroupIdRuleType - && memberGroupIds.Contains(x.RuleValue)); - - } - - [Obsolete("This method has been replaced because of a spelling mistake. Use the HasAccess method instead.", false)] - public static bool HasAccces(int documentId, object memberId) - { - // Call the correctly named version of this method - return HasAccess(documentId, memberId); - } - - public static bool HasAccess(int documentId, object memberId) - { - return ApplicationContext.Current.Services.PublicAccessService.HasAccess( - documentId, - memberId, - ApplicationContext.Current.Services.ContentService, - MembershipProviderExtensions.GetMembersMembershipProvider(), - //TODO: This should really be targeting a specific provider by name!! - Roles.Provider); - } - - public static bool HasAccess(int documentId, string path, MembershipUser member) - { - return ApplicationContext.Current.Services.PublicAccessService.HasAccess( - path, - member, - //TODO: This should really be targeting a specific provider by name!! - Roles.Provider); - } - - public static ProtectionType GetProtectionType(int DocumentId) - { - var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); - if (content == null) return ProtectionType.NotProtected; - - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); - if (entry == null) return ProtectionType.NotProtected; - - //legacy states that if it is protected by a member id then it is 'simple' - return entry.Rules.Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberIdRuleType) - ? ProtectionType.Simple - : ProtectionType.Advanced; - - } - - public static bool IsProtected(int DocumentId, string Path) - { - return ApplicationContext.Current.Services.PublicAccessService.IsProtected(Path.EnsureEndsWith("," + DocumentId)); - } - - //return the protection status of this exact document - not based on inheritance - public static bool IsProtected(int DocumentId) - { - return ApplicationContext.Current.Services.PublicAccessService.IsProtected(DocumentId.ToString()); - } - - public static int GetErrorPage(string Path) - { - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(Path); - if (entry == null) return -1; - var entity = ApplicationContext.Current.Services.EntityService.Get(entry.NoAccessNodeId, UmbracoObjectTypes.Document, false); - return entity.Id; - - } - - public static int GetLoginPage(string Path) - { - var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(Path); - if (entry == null) return -1; - var entity = ApplicationContext.Current.Services.EntityService.Get(entry.LoginNodeId, UmbracoObjectTypes.Document, false); - return entity.Id; - - } - #endregion - - - //NOTE: This is purely here for backwards compat for events - private static void Save() - { - var e = new SaveEventArgs(); - - new Access().FireBeforeSave(e); - - if (e.Cancel) return; - - new Access().FireAfterSave(e); - } - - - //Event delegates - public delegate void SaveEventHandler(Access sender, SaveEventArgs e); - - public delegate void AddProtectionEventHandler(Document sender, AddProtectionEventArgs e); - public delegate void RemoveProtectionEventHandler(Document sender, RemoveProtectionEventArgs e); - - public delegate void AddMemberShipRoleToDocumentEventHandler(Document sender, string role, AddMemberShipRoleToDocumentEventArgs e); - public delegate void RemoveMemberShipRoleFromDocumentEventHandler(Document sender, string role, RemoveMemberShipRoleFromDocumentEventArgs e); - - public delegate void RemoveMemberShipUserFromDocumentEventHandler(Document sender, string MembershipUserName, RemoveMemberShipUserFromDocumentEventArgs e); - public delegate void AddMembershipUserToDocumentEventHandler(Document sender, string MembershipUserName, AddMembershipUserToDocumentEventArgs e); - - //Events - - public static event SaveEventHandler BeforeSave; - protected virtual void FireBeforeSave(SaveEventArgs e) - { - if (BeforeSave != null) - BeforeSave(this, e); - } - - public static event SaveEventHandler AfterSave; - protected virtual void FireAfterSave(SaveEventArgs e) - { - if (AfterSave != null) - AfterSave(this, e); - } - - public static event AddProtectionEventHandler BeforeAddProtection; - protected virtual void FireBeforeAddProtection(Document doc, AddProtectionEventArgs e) - { - if (BeforeAddProtection != null) - BeforeAddProtection(doc, e); - } - - public static event AddProtectionEventHandler AfterAddProtection; - protected virtual void FireAfterAddProtection(Document doc, AddProtectionEventArgs e) - { - if (AfterAddProtection != null) - AfterAddProtection(doc, e); - } - - public static event RemoveProtectionEventHandler BeforeRemoveProtection; - protected virtual void FireBeforeRemoveProtection(Document doc, RemoveProtectionEventArgs e) - { - if (BeforeRemoveProtection != null) - BeforeRemoveProtection(doc, e); - } - - public static event RemoveProtectionEventHandler AfterRemoveProtection; - protected virtual void FireAfterRemoveProtection(Document doc, RemoveProtectionEventArgs e) - { - if (AfterRemoveProtection != null) - AfterRemoveProtection(doc, e); - } - - public static event AddMemberShipRoleToDocumentEventHandler BeforeAddMemberShipRoleToDocument; - protected virtual void FireBeforeAddMemberShipRoleToDocument(Document doc, string role, AddMemberShipRoleToDocumentEventArgs e) - { - if (BeforeAddMemberShipRoleToDocument != null) - BeforeAddMemberShipRoleToDocument(doc, role, e); - } - - public static event AddMemberShipRoleToDocumentEventHandler AfterAddMemberShipRoleToDocument; - protected virtual void FireAfterAddMemberShipRoleToDocument(Document doc, string role, AddMemberShipRoleToDocumentEventArgs e) - { - if (AfterAddMemberShipRoleToDocument != null) - AfterAddMemberShipRoleToDocument(doc, role, e); - } - - public static event RemoveMemberShipRoleFromDocumentEventHandler BeforeRemoveMemberShipRoleToDocument; - protected virtual void FireBeforeRemoveMemberShipRoleFromDocument(Document doc, string role, RemoveMemberShipRoleFromDocumentEventArgs e) - { - if (BeforeRemoveMemberShipRoleToDocument != null) - BeforeRemoveMemberShipRoleToDocument(doc, role, e); - } - - public static event RemoveMemberShipRoleFromDocumentEventHandler AfterRemoveMemberShipRoleToDocument; - protected virtual void FireAfterRemoveMemberShipRoleFromDocument(Document doc, string role, RemoveMemberShipRoleFromDocumentEventArgs e) - { - if (AfterRemoveMemberShipRoleToDocument != null) - AfterRemoveMemberShipRoleToDocument(doc, role, e); - } - - public static event RemoveMemberShipUserFromDocumentEventHandler BeforeRemoveMembershipUserFromDocument; - protected virtual void FireBeforeRemoveMembershipUserFromDocument(Document doc, string username, RemoveMemberShipUserFromDocumentEventArgs e) - { - if (BeforeRemoveMembershipUserFromDocument != null) - BeforeRemoveMembershipUserFromDocument(doc, username, e); - } - - public static event RemoveMemberShipUserFromDocumentEventHandler AfterRemoveMembershipUserFromDocument; - protected virtual void FireAfterRemoveMembershipUserFromDocument(Document doc, string username, RemoveMemberShipUserFromDocumentEventArgs e) - { - if (AfterRemoveMembershipUserFromDocument != null) - AfterRemoveMembershipUserFromDocument(doc, username, e); - } - - public static event AddMembershipUserToDocumentEventHandler BeforeAddMembershipUserToDocument; - protected virtual void FireBeforeAddMembershipUserToDocument(Document doc, string username, AddMembershipUserToDocumentEventArgs e) - { - if (BeforeAddMembershipUserToDocument != null) - BeforeAddMembershipUserToDocument(doc, username, e); - } - - public static event AddMembershipUserToDocumentEventHandler AfterAddMembershipUserToDocument; - protected virtual void FireAfterAddMembershipUserToDocument(Document doc, string username, AddMembershipUserToDocumentEventArgs e) - { - if (AfterAddMembershipUserToDocument != null) - AfterAddMembershipUserToDocument(doc, username, e); - } - } - - public enum ProtectionType - { - NotProtected, - Simple, - Advanced - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml; +using System.Xml.Linq; +using System.Web.Security; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Security; +using Umbraco.Core.Services; + +namespace umbraco.cms.businesslogic.web +{ + /// + /// Summary description for Access. + /// + [Obsolete("Use Umbraco.Core.Service.IPublicAccessService instead")] + public class Access + { + + [Obsolete("Do not access this property directly, it is not thread safe, use GetXmlDocumentCopy instead")] + public static XmlDocument AccessXml + { + get { return GetXmlDocumentCopy(); } + } + + //NOTE: This is here purely for backwards compat + [Obsolete("This should never be used, the data is stored in the database now")] + public static XmlDocument GetXmlDocumentCopy() + { + var allAccessEntries = ApplicationContext.Current.Services.PublicAccessService.GetAll().ToArray(); + + var xml = XDocument.Parse(""); + foreach (var entry in allAccessEntries) + { + var pageXml = new XElement("page", + new XAttribute("id", entry.ProtectedNodeId), + new XAttribute("loginPage", entry.LoginNodeId), + new XAttribute("noRightsPage", entry.NoAccessNodeId)); + + foreach (var rule in entry.Rules) + { + if (rule.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType) + { + //if there is a member id claim then it is 'simple' (this is how legacy worked) + pageXml.Add(new XAttribute("simple", "True")); + pageXml.Add(new XAttribute("memberId", rule.RuleValue)); + } + else if (rule.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType) + { + pageXml.Add(new XElement("group", new XAttribute("id", rule.RuleValue))); + } + } + + xml.Root.Add(pageXml); + } + + return xml.ToXmlDocument(); + } + + #region Manipulation methods + + public static void AddMembershipRoleToDocument(int documentId, string role) + { + //event + var doc = new Document(documentId); + var e = new AddMemberShipRoleToDocumentEventArgs(); + new Access().FireBeforeAddMemberShipRoleToDocument(doc, role, e); + + if (e.Cancel) return; + + + var entry = ApplicationContext.Current.Services.PublicAccessService.AddRule( + doc.ContentEntity, + Constants.Conventions.PublicAccess.MemberRoleRuleType, + role); + + if (entry.Success == false && entry.Result.Entity == null) + { + throw new Exception("Document is not protected!"); + } + + Save(); + + new Access().FireAfterAddMemberShipRoleToDocument(doc, role, e); + } + + + [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] + public static void AddMemberGroupToDocument(int DocumentId, int MemberGroupId) + { + var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); + + if (content == null) + throw new Exception("No content found with document id " + DocumentId); + + if (ApplicationContext.Current.Services.PublicAccessService.AddRule( + content, + Constants.Conventions.PublicAccess.MemberGroupIdRuleType, + MemberGroupId.ToString(CultureInfo.InvariantCulture))) + { + Save(); + } + + } + + [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] + public static void AddMemberToDocument(int DocumentId, int MemberId) + { + var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); + + if (content == null) + throw new Exception("No content found with document id " + DocumentId); + + if (ApplicationContext.Current.Services.PublicAccessService.AddRule( + content, + Constants.Conventions.PublicAccess.MemberIdRuleType, + MemberId.ToString(CultureInfo.InvariantCulture))) + { + Save(); + } + + } + + public static void AddMembershipUserToDocument(int documentId, string membershipUserName) + { + //event + var doc = new Document(documentId); + var e = new AddMembershipUserToDocumentEventArgs(); + new Access().FireBeforeAddMembershipUserToDocument(doc, membershipUserName, e); + + if (e.Cancel) return; + + var entry = ApplicationContext.Current.Services.PublicAccessService.AddRule( + doc.ContentEntity, + Constants.Conventions.PublicAccess.MemberUsernameRuleType, + membershipUserName); + + if (entry.Success == false && entry.Result.Entity == null) + { + throw new Exception("Document is not protected!"); + } + + if (entry) + { + Save(); + new Access().FireAfterAddMembershipUserToDocument(doc, membershipUserName, e); + } + + } + + [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] + public static void RemoveMemberGroupFromDocument(int DocumentId, int MemberGroupId) + { + var doc = new Document(DocumentId); + + var entry = ApplicationContext.Current.Services.PublicAccessService.AddRule( + doc.ContentEntity, + Constants.Conventions.PublicAccess.MemberGroupIdRuleType, + MemberGroupId.ToString(CultureInfo.InvariantCulture)); + + if (entry.Success == false && entry.Result.Entity == null) + { + throw new Exception("Document is not protected!"); + } + + if (entry) + { + Save(); + } + } + + public static void RemoveMembershipRoleFromDocument(int documentId, string role) + { + var doc = new Document(documentId); + var e = new RemoveMemberShipRoleFromDocumentEventArgs(); + new Access().FireBeforeRemoveMemberShipRoleFromDocument(doc, role, e); + + if (e.Cancel) return; + + if (ApplicationContext.Current.Services.PublicAccessService.RemoveRule( + doc.ContentEntity, + Constants.Conventions.PublicAccess.MemberRoleRuleType, + role)) + { + Save(); + new Access().FireAfterRemoveMemberShipRoleFromDocument(doc, role, e); + }; + + } + + public static bool RenameMemberShipRole(string oldRolename, string newRolename) + { + var hasChange = ApplicationContext.Current.Services.PublicAccessService.RenameMemberGroupRoleRules(oldRolename, newRolename); + + if (hasChange) + Save(); + + return hasChange; + } + + public static void ProtectPage(bool Simple, int DocumentId, int LoginDocumentId, int ErrorDocumentId) + { + var doc = new Document(DocumentId); + var e = new AddProtectionEventArgs(); + new Access().FireBeforeAddProtection(doc, e); + + if (e.Cancel) return; + + var loginContent = ApplicationContext.Current.Services.ContentService.GetById(LoginDocumentId); + if (loginContent == null) throw new NullReferenceException("No content item found with id " + LoginDocumentId); + var noAccessContent = ApplicationContext.Current.Services.ContentService.GetById(ErrorDocumentId); + if (noAccessContent == null) throw new NullReferenceException("No content item found with id " + ErrorDocumentId); + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(doc.ContentEntity.Id.ToString()); + if (entry != null) + { + if (Simple) + { + // if using simple mode, make sure that all existing groups are removed + entry.ClearRules(); + } + + //ensure the correct ids are applied + entry.LoginNodeId = loginContent.Id; + entry.NoAccessNodeId = noAccessContent.Id; + } + else + { + entry = new PublicAccessEntry(doc.ContentEntity, + ApplicationContext.Current.Services.ContentService.GetById(LoginDocumentId), + ApplicationContext.Current.Services.ContentService.GetById(ErrorDocumentId), + new List()); + } + + if (ApplicationContext.Current.Services.PublicAccessService.Save(entry)) + { + Save(); + new Access().FireAfterAddProtection(new Document(DocumentId), e); + } + + } + + public static void RemoveProtection(int DocumentId) + { + //event + var doc = new Document(DocumentId); + var e = new RemoveProtectionEventArgs(); + new Access().FireBeforeRemoveProtection(doc, e); + + if (e.Cancel) return; + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(doc.ContentEntity); + if (entry != null) + { + ApplicationContext.Current.Services.PublicAccessService.Delete(entry); + } + + Save(); + + new Access().FireAfterRemoveProtection(doc, e); + } + #endregion + + #region Reading methods + + [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] + public static bool IsProtectedByGroup(int DocumentId, int GroupId) + { + var d = new Document(DocumentId); + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(d.ContentEntity); + if (entry == null) return false; + + return entry.Rules + .Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberGroupIdRuleType + && x.RuleValue == GroupId.ToString(CultureInfo.InvariantCulture)); + + } + + public static bool IsProtectedByMembershipRole(int documentId, string role) + { + var content = ApplicationContext.Current.Services.ContentService.GetById(documentId); + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); + if (entry == null) return false; + + return entry.Rules + .Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType + && x.RuleValue == role); + + } + + public static string[] GetAccessingMembershipRoles(int documentId, string path) + { + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(path.EnsureEndsWith("," + documentId)); + if (entry == null) return new string[] { }; + + var memberGroupRoleRules = entry.Rules.Where(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType); + return memberGroupRoleRules.Select(x => x.RuleValue).ToArray(); + + } + + [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] + public static member.MemberGroup[] GetAccessingGroups(int DocumentId) + { + var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); + if (content == null) return null; + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); + if (entry == null) return null; + + var memberGroupIdRules = entry.Rules.Where(x => x.RuleType == Constants.Conventions.PublicAccess.MemberGroupIdRuleType); + + return memberGroupIdRules.Select(x => new member.MemberGroup(int.Parse(x.RuleValue))).ToArray(); + + } + + [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] + public static member.Member GetAccessingMember(int DocumentId) + { + var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); + if (content == null) return null; + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); + if (entry == null) return null; + + //legacy would throw an exception here if it was not 'simple' and simple means based on a member id in this case + if (entry.Rules.All(x => x.RuleType != Constants.Conventions.PublicAccess.MemberIdRuleType)) + { + throw new Exception("Document isn't protected using Simple mechanism. Use GetAccessingMemberGroups instead"); + } + + var memberIdRule = entry.Rules.First(x => x.RuleType == Constants.Conventions.PublicAccess.MemberIdRuleType); + return new member.Member(int.Parse(memberIdRule.RuleValue)); + + } + + public static MembershipUser GetAccessingMembershipUser(int documentId) + { + var content = ApplicationContext.Current.Services.ContentService.GetById(documentId); + if (content == null) return null; + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); + if (entry == null) return null; + + //legacy would throw an exception here if it was not 'simple' and simple means based on a username + if (entry.Rules.All(x => x.RuleType != Constants.Conventions.PublicAccess.MemberUsernameRuleType)) + { + throw new Exception("Document isn't protected using Simple mechanism. Use GetAccessingMemberGroups instead"); + } + + var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); + var usernameRule = entry.Rules.First(x => x.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType); + return provider.GetUser(usernameRule.RuleValue, false); + + } + + + [Obsolete("This method is no longer supported. Use the ASP.NET MemberShip methods instead", true)] + public static bool HasAccess(int DocumentId, member.Member Member) + { + var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); + if (content == null) return true; + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); + if (entry == null) return true; + + var memberGroupIds = Member.Groups.Values.Cast().Select(x => x.Id.ToString(CultureInfo.InvariantCulture)).ToArray(); + return entry.Rules.Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberGroupIdRuleType + && memberGroupIds.Contains(x.RuleValue)); + + } + + [Obsolete("This method has been replaced because of a spelling mistake. Use the HasAccess method instead.", false)] + public static bool HasAccces(int documentId, object memberId) + { + // Call the correctly named version of this method + return HasAccess(documentId, memberId); + } + + public static bool HasAccess(int documentId, object memberId) + { + return ApplicationContext.Current.Services.PublicAccessService.HasAccess( + documentId, + memberId, + ApplicationContext.Current.Services.ContentService, + MembershipProviderExtensions.GetMembersMembershipProvider(), + //TODO: This should really be targeting a specific provider by name!! + Roles.Provider); + } + + public static bool HasAccess(int documentId, string path, MembershipUser member) + { + return ApplicationContext.Current.Services.PublicAccessService.HasAccess( + path, + member, + //TODO: This should really be targeting a specific provider by name!! + Roles.Provider); + } + + public static ProtectionType GetProtectionType(int DocumentId) + { + var content = ApplicationContext.Current.Services.ContentService.GetById(DocumentId); + if (content == null) return ProtectionType.NotProtected; + + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(content); + if (entry == null) return ProtectionType.NotProtected; + + //legacy states that if it is protected by a member id then it is 'simple' + return entry.Rules.Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberIdRuleType) + ? ProtectionType.Simple + : ProtectionType.Advanced; + + } + + public static bool IsProtected(int DocumentId, string Path) + { + return ApplicationContext.Current.Services.PublicAccessService.IsProtected(Path.EnsureEndsWith("," + DocumentId)); + } + + //return the protection status of this exact document - not based on inheritance + public static bool IsProtected(int DocumentId) + { + return ApplicationContext.Current.Services.PublicAccessService.IsProtected(DocumentId.ToString()); + } + + public static int GetErrorPage(string Path) + { + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(Path); + if (entry == null) return -1; + var entity = ApplicationContext.Current.Services.EntityService.Get(entry.NoAccessNodeId, UmbracoObjectTypes.Document); + return entity.Id; + + } + + public static int GetLoginPage(string Path) + { + var entry = ApplicationContext.Current.Services.PublicAccessService.GetEntryForContent(Path); + if (entry == null) return -1; + var entity = ApplicationContext.Current.Services.EntityService.Get(entry.LoginNodeId, UmbracoObjectTypes.Document); + return entity.Id; + + } + #endregion + + + //NOTE: This is purely here for backwards compat for events + private static void Save() + { + var e = new SaveEventArgs(); + + new Access().FireBeforeSave(e); + + if (e.Cancel) return; + + new Access().FireAfterSave(e); + } + + + //Event delegates + public delegate void SaveEventHandler(Access sender, SaveEventArgs e); + + public delegate void AddProtectionEventHandler(Document sender, AddProtectionEventArgs e); + public delegate void RemoveProtectionEventHandler(Document sender, RemoveProtectionEventArgs e); + + public delegate void AddMemberShipRoleToDocumentEventHandler(Document sender, string role, AddMemberShipRoleToDocumentEventArgs e); + public delegate void RemoveMemberShipRoleFromDocumentEventHandler(Document sender, string role, RemoveMemberShipRoleFromDocumentEventArgs e); + + public delegate void RemoveMemberShipUserFromDocumentEventHandler(Document sender, string MembershipUserName, RemoveMemberShipUserFromDocumentEventArgs e); + public delegate void AddMembershipUserToDocumentEventHandler(Document sender, string MembershipUserName, AddMembershipUserToDocumentEventArgs e); + + //Events + + public static event SaveEventHandler BeforeSave; + protected virtual void FireBeforeSave(SaveEventArgs e) + { + if (BeforeSave != null) + BeforeSave(this, e); + } + + public static event SaveEventHandler AfterSave; + protected virtual void FireAfterSave(SaveEventArgs e) + { + if (AfterSave != null) + AfterSave(this, e); + } + + public static event AddProtectionEventHandler BeforeAddProtection; + protected virtual void FireBeforeAddProtection(Document doc, AddProtectionEventArgs e) + { + if (BeforeAddProtection != null) + BeforeAddProtection(doc, e); + } + + public static event AddProtectionEventHandler AfterAddProtection; + protected virtual void FireAfterAddProtection(Document doc, AddProtectionEventArgs e) + { + if (AfterAddProtection != null) + AfterAddProtection(doc, e); + } + + public static event RemoveProtectionEventHandler BeforeRemoveProtection; + protected virtual void FireBeforeRemoveProtection(Document doc, RemoveProtectionEventArgs e) + { + if (BeforeRemoveProtection != null) + BeforeRemoveProtection(doc, e); + } + + public static event RemoveProtectionEventHandler AfterRemoveProtection; + protected virtual void FireAfterRemoveProtection(Document doc, RemoveProtectionEventArgs e) + { + if (AfterRemoveProtection != null) + AfterRemoveProtection(doc, e); + } + + public static event AddMemberShipRoleToDocumentEventHandler BeforeAddMemberShipRoleToDocument; + protected virtual void FireBeforeAddMemberShipRoleToDocument(Document doc, string role, AddMemberShipRoleToDocumentEventArgs e) + { + if (BeforeAddMemberShipRoleToDocument != null) + BeforeAddMemberShipRoleToDocument(doc, role, e); + } + + public static event AddMemberShipRoleToDocumentEventHandler AfterAddMemberShipRoleToDocument; + protected virtual void FireAfterAddMemberShipRoleToDocument(Document doc, string role, AddMemberShipRoleToDocumentEventArgs e) + { + if (AfterAddMemberShipRoleToDocument != null) + AfterAddMemberShipRoleToDocument(doc, role, e); + } + + public static event RemoveMemberShipRoleFromDocumentEventHandler BeforeRemoveMemberShipRoleToDocument; + protected virtual void FireBeforeRemoveMemberShipRoleFromDocument(Document doc, string role, RemoveMemberShipRoleFromDocumentEventArgs e) + { + if (BeforeRemoveMemberShipRoleToDocument != null) + BeforeRemoveMemberShipRoleToDocument(doc, role, e); + } + + public static event RemoveMemberShipRoleFromDocumentEventHandler AfterRemoveMemberShipRoleToDocument; + protected virtual void FireAfterRemoveMemberShipRoleFromDocument(Document doc, string role, RemoveMemberShipRoleFromDocumentEventArgs e) + { + if (AfterRemoveMemberShipRoleToDocument != null) + AfterRemoveMemberShipRoleToDocument(doc, role, e); + } + + public static event RemoveMemberShipUserFromDocumentEventHandler BeforeRemoveMembershipUserFromDocument; + protected virtual void FireBeforeRemoveMembershipUserFromDocument(Document doc, string username, RemoveMemberShipUserFromDocumentEventArgs e) + { + if (BeforeRemoveMembershipUserFromDocument != null) + BeforeRemoveMembershipUserFromDocument(doc, username, e); + } + + public static event RemoveMemberShipUserFromDocumentEventHandler AfterRemoveMembershipUserFromDocument; + protected virtual void FireAfterRemoveMembershipUserFromDocument(Document doc, string username, RemoveMemberShipUserFromDocumentEventArgs e) + { + if (AfterRemoveMembershipUserFromDocument != null) + AfterRemoveMembershipUserFromDocument(doc, username, e); + } + + public static event AddMembershipUserToDocumentEventHandler BeforeAddMembershipUserToDocument; + protected virtual void FireBeforeAddMembershipUserToDocument(Document doc, string username, AddMembershipUserToDocumentEventArgs e) + { + if (BeforeAddMembershipUserToDocument != null) + BeforeAddMembershipUserToDocument(doc, username, e); + } + + public static event AddMembershipUserToDocumentEventHandler AfterAddMembershipUserToDocument; + protected virtual void FireAfterAddMembershipUserToDocument(Document doc, string username, AddMembershipUserToDocumentEventArgs e) + { + if (AfterAddMembershipUserToDocument != null) + AfterAddMembershipUserToDocument(doc, username, e); + } + } + + public enum ProtectionType + { + NotProtected, + Simple, + Advanced + } +} From 3e74bbfb453188f75e6ace3b28558bb31229ce49 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 25 Jun 2019 14:35:28 +1000 Subject: [PATCH 025/776] Creates a custom IAntiForgeryAdditionalDataProvider that provides and validates custom token data in the request which allows having a custom AF token per form created with BeginUmbracoForm --- ...oAntiForgeryAdditionalDataProviderTests.cs | 157 ++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 11 +- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 35 +--- ...mbracoAntiForgeryAdditionalDataProvider.cs | 91 ++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + src/Umbraco.Web/UmbracoHelper.cs | 44 ++++- src/Umbraco.Web/WebBootManager.cs | 8 +- 8 files changed, 309 insertions(+), 39 deletions(-) create mode 100644 src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs create mode 100644 src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs diff --git a/src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs b/src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs new file mode 100644 index 0000000000..c81c108e0d --- /dev/null +++ b/src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs @@ -0,0 +1,157 @@ +using System.Collections.Specialized; +using System.Web; +using System.Web.Helpers; +using Moq; +using Newtonsoft.Json; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web.Mvc; +using Umbraco.Web.Security; + +namespace Umbraco.Tests.Security +{ + [TestFixture] + public class UmbracoAntiForgeryAdditionalDataProviderTests + { + [Test] + public void Test_Wrapped_Non_BeginUmbracoForm() + { + var wrapped = Mock.Of(x => x.GetAdditionalData(It.IsAny()) == "custom"); + var provider = new UmbracoAntiForgeryAdditionalDataProvider(wrapped); + + var httpContextFactory = new FakeHttpContextFactory("/hello/world"); + var data = provider.GetAdditionalData(httpContextFactory.HttpContext); + + Assert.IsTrue(data.DetectIsJson()); + var json = JsonConvert.DeserializeObject(data); + Assert.AreEqual(null, json.Ufprt); + Assert.IsTrue(json.Stamp != default); + Assert.AreEqual("custom", json.WrappedValue); + } + + [Test] + public void Null_Wrapped_Non_BeginUmbracoForm() + { + var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); + + var httpContextFactory = new FakeHttpContextFactory("/hello/world"); + var data = provider.GetAdditionalData(httpContextFactory.HttpContext); + + Assert.IsTrue(data.DetectIsJson()); + var json = JsonConvert.DeserializeObject(data); + Assert.AreEqual(null, json.Ufprt); + Assert.IsTrue(json.Stamp != default); + Assert.AreEqual("default", json.WrappedValue); + } + + [Test] + public void Validate_Non_Json() + { + var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); + + var httpContextFactory = new FakeHttpContextFactory("/hello/world"); + var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "hello"); + + Assert.IsFalse(isValid); + } + + [Test] + public void Validate_Invalid_Json() + { + var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); + + var httpContextFactory = new FakeHttpContextFactory("/hello/world"); + var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '0'}"); + Assert.IsFalse(isValid); + + isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': ''}"); + Assert.IsFalse(isValid); + + isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'hello': 'world'}"); + Assert.IsFalse(isValid); + + } + + [Test] + public void Validate_No_Request_Ufprt() + { + var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); + + var httpContextFactory = new FakeHttpContextFactory("/hello/world"); + //there is a ufprt in the additional data, but not in the request + var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': 'ASBVDFDFDFDF'}"); + Assert.IsFalse(isValid); + } + + [Test] + public void Validate_No_AdditionalData_Ufprt() + { + var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); + + var httpContextFactory = new FakeHttpContextFactory("/hello/world"); + var requestMock = Mock.Get(httpContextFactory.HttpContext.Request); + requestMock.SetupGet(x => x["ufprt"]).Returns("ABCDEFG"); + + //there is a ufprt in the additional data, but not in the request + var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': ''}"); + Assert.IsFalse(isValid); + } + + [Test] + public void Validate_No_AdditionalData_Or_Request_Ufprt() + { + var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); + + var httpContextFactory = new FakeHttpContextFactory("/hello/world"); + + //there is a ufprt in the additional data, but not in the request + var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': ''}"); + Assert.IsTrue(isValid); + } + + [Test] + public void Validate_Request_And_AdditionalData_Ufprt() + { + var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); + + var routeParams1 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; + var routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; + + var httpContextFactory = new FakeHttpContextFactory("/hello/world"); + var requestMock = Mock.Get(httpContextFactory.HttpContext.Request); + requestMock.SetupGet(x => x["ufprt"]).Returns(routeParams1.EncryptWithMachineKey()); + + var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); + Assert.IsTrue(isValid); + + routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Invalid")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; + isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); + Assert.IsFalse(isValid); + } + + [Test] + public void Validate_Wrapped_Request_And_AdditionalData_Ufprt() + { + var wrapped = Mock.Of(x => x.ValidateAdditionalData(It.IsAny(), "custom") == true); + var provider = new UmbracoAntiForgeryAdditionalDataProvider(wrapped); + + var routeParams1 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; + var routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; + + var httpContextFactory = new FakeHttpContextFactory("/hello/world"); + var requestMock = Mock.Get(httpContextFactory.HttpContext.Request); + requestMock.SetupGet(x => x["ufprt"]).Returns(routeParams1.EncryptWithMachineKey()); + + var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); + Assert.IsFalse(isValid); + + isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'custom', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); + Assert.IsTrue(isValid); + + routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Invalid")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; + isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); + Assert.IsFalse(isValid); + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 72939c12d9..7e6fdf4823 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -197,6 +197,7 @@ + diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 90bc97ca19..a64c00a03c 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -306,14 +306,21 @@ namespace Umbraco.Web return; this._disposed = true; + //For UmbracoForm's we want to add our routing string to the httpcontext items in the case where anti-forgery tokens are used. + //In which case our custom UmbracoAntiForgeryAdditionalDataProvider will kick in and validate the values in the request against + //the values that will be appended to the token. This essentially means that when anti-forgery tokens are used with UmbracoForm's forms, + //that each token is unique to the controller/action/area instead of the default ASP.Net implementation which is that the token is unique + //per user. + _viewContext.HttpContext.Items["ufprt"] = _encryptedString; + //Detect if the call is targeting UmbRegisterController/UmbProfileController/UmbLoginStatusController/UmbLoginController and if it is we automatically output a AntiForgeryToken() // We have a controllerName and area so we can match if (_controllerName == "UmbRegister" || _controllerName == "UmbProfile" || _controllerName == "UmbLoginStatus" || _controllerName == "UmbLogin") - { - _viewContext.Writer.Write(AntiForgery.GetHtml().ToString()); + { + _viewContext.Writer.Write(AntiForgery.GetHtml().ToString()); } //write out the hidden surface form routes diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index bacdd2ffa9..0a5cb06247 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Mvc public class RenderRouteHandler : IRouteHandler { // Define reserved dictionary keys for controller, action and area specified in route additional values data - private static class ReservedAdditionalKeys + internal static class ReservedAdditionalKeys { internal const string Controller = "c"; internal const string Action = "a"; @@ -142,36 +142,7 @@ namespace Umbraco.Web.Mvc return null; } - - string decryptedString; - try - { - decryptedString = encodedVal.DecryptWithMachineKey(); - } - catch (FormatException) - { - LogHelper.Warn("A value was detected in the ufprt parameter but Umbraco could not decrypt the string"); - return null; - } - - var parsedQueryString = HttpUtility.ParseQueryString(decryptedString); - var decodedParts = new Dictionary(); - - foreach (var key in parsedQueryString.AllKeys) - { - decodedParts[key] = parsedQueryString[key]; - } - - //validate all required keys exist - - //the controller - if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Controller)) - return null; - //the action - if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Action)) - return null; - //the area - if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Area)) + if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(encodedVal, out var decodedParts)) return null; foreach (var item in decodedParts.Where(x => new[] { @@ -192,6 +163,8 @@ namespace Umbraco.Web.Mvc }; } + + /// /// Handles a posted form to an Umbraco Url and ensures the correct controller is routed to and that /// the right DataTokens are set. diff --git a/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs b/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs new file mode 100644 index 0000000000..660f7e58fc --- /dev/null +++ b/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs @@ -0,0 +1,91 @@ +using System; +using Umbraco.Web.Mvc; +using Umbraco.Core; +using System.Web.Helpers; +using System.Web; +using Newtonsoft.Json; + +namespace Umbraco.Web.Security +{ + /// + /// A custom to create a unique antiforgery token/validator per form created with BeginUmbracoForm + /// + public class UmbracoAntiForgeryAdditionalDataProvider : IAntiForgeryAdditionalDataProvider + { + private readonly IAntiForgeryAdditionalDataProvider _defaultProvider; + + /// + /// Constructor, allows wrapping a default provider + /// + /// + public UmbracoAntiForgeryAdditionalDataProvider(IAntiForgeryAdditionalDataProvider defaultProvider) + { + _defaultProvider = defaultProvider; + } + + public string GetAdditionalData(HttpContextBase context) + { + return JsonConvert.SerializeObject(new AdditionalData + { + Stamp = DateTime.UtcNow.Ticks, + //this value will be here if this is a BeginUmbracoForms form + Ufprt = context.Items["ufprt"]?.ToString(), + //if there was a wrapped provider, add it's value to the json, else just a static value + WrappedValue = _defaultProvider?.GetAdditionalData(context) ?? "default" + }); + } + + public bool ValidateAdditionalData(HttpContextBase context, string additionalData) + { + if (!additionalData.DetectIsJson()) + return false; //must be json + + AdditionalData json; + try + { + json = JsonConvert.DeserializeObject(additionalData); + } + catch + { + return false; //couldn't parse + } + + if (json.Stamp == default) return false; + + //if there was a wrapped provider, validate it, else validate the static value + var validateWrapped = _defaultProvider?.ValidateAdditionalData(context, json.WrappedValue) ?? json.WrappedValue == "default"; + if (!validateWrapped) + return false; + + var ufprtRequest = context.Request["ufprt"]?.ToString(); + + //if the custom BeginUmbracoForms route value is not there, then it's nothing more to validate + if (ufprtRequest.IsNullOrWhiteSpace() && json.Ufprt.IsNullOrWhiteSpace()) + return true; + + //if one or the other is null then something is wrong + if (!ufprtRequest.IsNullOrWhiteSpace() && json.Ufprt.IsNullOrWhiteSpace()) return false; + if (ufprtRequest.IsNullOrWhiteSpace() && !json.Ufprt.IsNullOrWhiteSpace()) return false; + + if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(json.Ufprt, out var additionalDataParts)) + return false; + + if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(ufprtRequest, out var requestParts)) + return false; + + //ensure they all match + return additionalDataParts.Count == requestParts.Count + && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Controller] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Controller] + && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Action] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Action] + && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Area]; + } + + internal class AdditionalData + { + public string Ufprt { get; set; } + public long Stamp { get; set; } + public string WrappedValue { get; set; } + } + + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 90624df5f2..16dc5dfec5 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -307,6 +307,7 @@ + diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index f920314075..c00d37f216 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -10,9 +10,11 @@ using System.Xml.XPath; using Umbraco.Core; using Umbraco.Core.Dictionary; using Umbraco.Core.Dynamics; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core.Xml; +using Umbraco.Web.Mvc; using Umbraco.Web.Routing; using Umbraco.Web.Security; @@ -1647,6 +1649,43 @@ namespace Umbraco.Web #endregion + internal static bool DecryptAndValidateEncryptedRouteString(string ufprt, out IDictionary parts) + { + string decryptedString; + try + { + decryptedString = ufprt.DecryptWithMachineKey(); + } + catch (FormatException) + { + LogHelper.Warn("A value was detected in the ufprt parameter but Umbraco could not decrypt the string"); + parts = null; + return false; + } + + var parsedQueryString = HttpUtility.ParseQueryString(decryptedString); + parts = new Dictionary(); + + foreach (var key in parsedQueryString.AllKeys) + { + parts[key] = parsedQueryString[key]; + } + + //validate all required keys exist + + //the controller + if (parts.All(x => x.Key != RenderRouteHandler.ReservedAdditionalKeys.Controller)) + return false; + //the action + if (parts.All(x => x.Key != RenderRouteHandler.ReservedAdditionalKeys.Action)) + return false; + //the area + if (parts.All(x => x.Key != RenderRouteHandler.ReservedAdditionalKeys.Area)) + return false; + + return true; + } + /// /// This is used in methods like BeginUmbracoForm and SurfaceAction to generate an encrypted string which gets submitted in a request for which /// Umbraco can decrypt during the routing process in order to delegate the request to a specific MVC Controller. @@ -1663,10 +1702,7 @@ namespace Umbraco.Web Mandate.ParameterNotNull(area, "area"); //need to create a params string as Base64 to put into our hidden field to use during the routes - var surfaceRouteParams = string.Format("c={0}&a={1}&ar={2}", - HttpUtility.UrlEncode(controllerName), - HttpUtility.UrlEncode(controllerAction), - area); + var surfaceRouteParams = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode(controllerName)}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode(controllerAction)}&{RenderRouteHandler.ReservedAdditionalKeys.Area}={area}"; //checking if the additional route values is already a dictionary and convert to querystring string additionalRouteValsAsQuery; diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 950676355e..ff6fcff164 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -47,10 +47,12 @@ using Umbraco.Web.Profiling; using Umbraco.Web.Search; using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; using ProfilingViewEngine = Umbraco.Core.Profiling.ProfilingViewEngine; - +using System.Web.Helpers; +using Umbraco.Web.Controllers; namespace Umbraco.Web { + /// /// A bootstrapper for the Umbraco application which initializes all objects including the Web portion of the application /// @@ -188,6 +190,8 @@ namespace Umbraco.Web base.Complete(afterComplete); + AntiForgeryConfig.AdditionalDataProvider = new UmbracoAntiForgeryAdditionalDataProvider(AntiForgeryConfig.AdditionalDataProvider); + //Now, startup all of our legacy startup handler ApplicationEventsResolver.Current.InstantiateLegacyStartupHandlers(); @@ -281,7 +285,7 @@ namespace Umbraco.Web // to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not // utilizing an old path appDomainHash); - + //set the file map and composite file default location to the %temp% location BaseCompositeFileProcessingProvider.CompositeFilePathDefaultFolder = XmlFileMapper.FileMapDefaultFolder From 3865f4fb50e559f3441574c3facd66191bbc08ca Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 08:15:06 +0200 Subject: [PATCH 026/776] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - bugfix - wrong constant used --- .../Persistence/Migrations/Initial/BaseDataCreation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs index e1716947e1..7d3b15e48b 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs @@ -131,7 +131,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = Constants.DataTypes.ApprovedColorGuid, Text = "Approved Color", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = -36, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-36", SortOrder = 2, UniqueId = Constants.DataTypes.DatePickerWithTimeGuid, Text = "Date Picker with time", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultContentListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-95", SortOrder = 2, UniqueId = Constants.DataTypes.ListViewContentGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); - _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMediaListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-96", SortOrder = 2, UniqueId = Constants.DataTypes.ListViewMembersGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); + _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMediaListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-96", SortOrder = 2, UniqueId = Constants.DataTypes.ListViewMediaGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.DefaultMembersListViewDataTypeId, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,-97", SortOrder = 2, UniqueId = Constants.DataTypes.ListViewMembersGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"), Text = Constants.Conventions.MediaTypes.Folder, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); _database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"), Text = Constants.Conventions.MediaTypes.Image, NodeObjectType = new Guid(Constants.ObjectTypes.MediaType), CreateDate = DateTime.Now }); From 383a8617cebefb77d7d9e74cfc98d152f6bea78b Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 08:19:50 +0200 Subject: [PATCH 027/776] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - Fixed test --- .../Services/EntityServiceTests.cs | 1308 ++++++++--------- 1 file changed, 651 insertions(+), 657 deletions(-) diff --git a/src/Umbraco.Tests/Services/EntityServiceTests.cs b/src/Umbraco.Tests/Services/EntityServiceTests.cs index 8867282155..889d7e5e76 100644 --- a/src/Umbraco.Tests/Services/EntityServiceTests.cs +++ b/src/Umbraco.Tests/Services/EntityServiceTests.cs @@ -1,659 +1,653 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Entities; - -namespace Umbraco.Tests.Services -{ - /// - /// Tests covering the EntityService - /// - [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerFixture)] - [TestFixture, RequiresSTA] - public class EntityServiceTests : BaseServiceTest - { - [SetUp] - public override void Initialize() - { - base.Initialize(); - } - - [TearDown] - public override void TearDown() - { - base.TearDown(); - } - - [Test] - public void EntityService_Can_Get_Paged_Content_Children() - { - - var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); - - var root = MockedContent.CreateSimpleContent(contentType); - ServiceContext.ContentService.Save(root); - for (int i = 0; i < 10; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); - ServiceContext.ContentService.Save(c1); - } - - var service = ServiceContext.EntityService; - - long total; - var entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Document, 0, 6, out total).ToArray(); - Assert.That(entities.Length, Is.EqualTo(6)); - Assert.That(total, Is.EqualTo(10)); - entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Document, 1, 6, out total).ToArray(); - Assert.That(entities.Length, Is.EqualTo(4)); - Assert.That(total, Is.EqualTo(10)); - } - - [Test] - public void EntityService_Can_Get_Paged_Content_Descendants() - { - var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); - - var root = MockedContent.CreateSimpleContent(contentType); - ServiceContext.ContentService.Save(root); - var count = 0; - for (int i = 0; i < 10; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); - ServiceContext.ContentService.Save(c1); - count++; - - for (int j = 0; j < 5; j++) - { - var c2 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), c1); - ServiceContext.ContentService.Save(c2); - count++; - } - } - - var service = ServiceContext.EntityService; - - long total; - var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 0, 31, out total).ToArray(); - Assert.That(entities.Length, Is.EqualTo(31)); - Assert.That(total, Is.EqualTo(60)); - entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 1, 31, out total).ToArray(); - Assert.That(entities.Length, Is.EqualTo(29)); - Assert.That(total, Is.EqualTo(60)); - } - - [Test] - public void EntityService_Can_Get_Paged_Content_Descendants_Including_Recycled() - { - var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); - - var root = MockedContent.CreateSimpleContent(contentType); - ServiceContext.ContentService.Save(root); - var toDelete = new List(); - for (int i = 0; i < 10; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); - ServiceContext.ContentService.Save(c1); - - if (i % 2 == 0) - { - toDelete.Add(c1); - } - - for (int j = 0; j < 5; j++) - { - var c2 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), c1); - ServiceContext.ContentService.Save(c2); - } - } - - foreach (var content in toDelete) - { - ServiceContext.ContentService.MoveToRecycleBin(content); - } - - var service = ServiceContext.EntityService; - - long total; - //search at root to see if it returns recycled - var entities = service.GetPagedDescendants(-1, UmbracoObjectTypes.Document, 0, 1000, out total) - .Select(x => x.Id) - .ToArray(); - - foreach (var c in toDelete) - { - Assert.True(entities.Contains(c.Id)); - } - } - - [Test] - public void EntityService_Can_Get_Paged_Content_Descendants_Without_Recycled() - { - var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); - - var root = MockedContent.CreateSimpleContent(contentType); - ServiceContext.ContentService.Save(root); - var toDelete = new List(); - for (int i = 0; i < 10; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); - ServiceContext.ContentService.Save(c1); - - if (i % 2 == 0) - { - toDelete.Add(c1); - } - - for (int j = 0; j < 5; j++) - { - var c2 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), c1); - ServiceContext.ContentService.Save(c2); - } - } - - foreach (var content in toDelete) - { - ServiceContext.ContentService.MoveToRecycleBin(content); - } - - var service = ServiceContext.EntityService; - - long total; - //search at root to see if it returns recycled - var entities = service.GetPagedDescendantsFromRoot(UmbracoObjectTypes.Document, 0, 1000, out total, includeTrashed:false) - .Select(x => x.Id) - .ToArray(); - - foreach (var c in toDelete) - { - Assert.IsFalse(entities.Contains(c.Id)); - } - } - - [Test] - public void EntityService_Can_Get_Paged_Content_Descendants_With_Search() - { - var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); - - var root = MockedContent.CreateSimpleContent(contentType); - ServiceContext.ContentService.Save(root); - - for (int i = 0; i < 10; i++) - { - var c1 = MockedContent.CreateSimpleContent(contentType, "ssss" + Guid.NewGuid(), root); - ServiceContext.ContentService.Save(c1); - - for (int j = 0; j < 5; j++) - { - var c2 = MockedContent.CreateSimpleContent(contentType, "tttt" + Guid.NewGuid(), c1); - ServiceContext.ContentService.Save(c2); - } - } - - var service = ServiceContext.EntityService; - - long total; - var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 0, 10, out total, filter: "ssss").ToArray(); - Assert.That(entities.Length, Is.EqualTo(10)); - Assert.That(total, Is.EqualTo(10)); - entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 0, 50, out total, filter: "tttt").ToArray(); - Assert.That(entities.Length, Is.EqualTo(50)); - Assert.That(total, Is.EqualTo(50)); - } - - [Test] - public void EntityService_Can_Get_Paged_Media_Children() - { - var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); - var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); - - var root = MockedMedia.CreateMediaFolder(folderType, -1); - ServiceContext.MediaService.Save(root); - for (int i = 0; i < 10; i++) - { - var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); - ServiceContext.MediaService.Save(c1); - } - - var service = ServiceContext.EntityService; - - long total; - var entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Media, 0, 6, out total).ToArray(); - Assert.That(entities.Length, Is.EqualTo(6)); - Assert.That(total, Is.EqualTo(10)); - entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Media, 1, 6, out total).ToArray(); - Assert.That(entities.Length, Is.EqualTo(4)); - Assert.That(total, Is.EqualTo(10)); - } - - [Test] - public void EntityService_Can_Get_Paged_Media_Descendants() - { - var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); - var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); - - var root = MockedMedia.CreateMediaFolder(folderType, -1); - ServiceContext.MediaService.Save(root); - var count = 0; - for (int i = 0; i < 10; i++) - { - var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); - ServiceContext.MediaService.Save(c1); - count++; - - for (int j = 0; j < 5; j++) - { - var c2 = MockedMedia.CreateMediaImage(imageMediaType, c1.Id); - ServiceContext.MediaService.Save(c2); - count++; - } - } - - var service = ServiceContext.EntityService; - - long total; - var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 0, 31, out total).ToArray(); - Assert.That(entities.Length, Is.EqualTo(31)); - Assert.That(total, Is.EqualTo(60)); - entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 1, 31, out total).ToArray(); - Assert.That(entities.Length, Is.EqualTo(29)); - Assert.That(total, Is.EqualTo(60)); - } - - [Test] - public void EntityService_Can_Get_Paged_Media_Descendants_Including_Recycled() - { - var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); - var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); - - var root = MockedMedia.CreateMediaFolder(folderType, -1); - ServiceContext.MediaService.Save(root); - var toDelete = new List(); - for (int i = 0; i < 10; i++) - { - var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); - ServiceContext.MediaService.Save(c1); - - if (i % 2 == 0) - { - toDelete.Add(c1); - } - - for (int j = 0; j < 5; j++) - { - var c2 = MockedMedia.CreateMediaImage(imageMediaType, c1.Id); - ServiceContext.MediaService.Save(c2); - } - } - - foreach (var content in toDelete) - { - ServiceContext.MediaService.MoveToRecycleBin(content); - } - - var service = ServiceContext.EntityService; - - long total; - //search at root to see if it returns recycled - var entities = service.GetPagedDescendants(-1, UmbracoObjectTypes.Media, 0, 1000, out total) - .Select(x => x.Id) - .ToArray(); - - foreach (var media in toDelete) - { - Assert.IsTrue(entities.Contains(media.Id)); - } - } - - [Test] - public void EntityService_Can_Get_Paged_Media_Descendants_Without_Recycled() - { - var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); - var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); - - var root = MockedMedia.CreateMediaFolder(folderType, -1); - ServiceContext.MediaService.Save(root); - var toDelete = new List(); - for (int i = 0; i < 10; i++) - { - var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); - ServiceContext.MediaService.Save(c1); - - if (i % 2 == 0) - { - toDelete.Add(c1); - } - - for (int j = 0; j < 5; j++) - { - var c2 = MockedMedia.CreateMediaImage(imageMediaType, c1.Id); - ServiceContext.MediaService.Save(c2); - } - } - - foreach (var content in toDelete) - { - ServiceContext.MediaService.MoveToRecycleBin(content); - } - - var service = ServiceContext.EntityService; - - long total; - //search at root to see if it returns recycled - var entities = service.GetPagedDescendantsFromRoot(UmbracoObjectTypes.Media, 0, 1000, out total, includeTrashed:false) - .Select(x => x.Id) - .ToArray(); - - foreach (var media in toDelete) - { - Assert.IsFalse(entities.Contains(media.Id)); - } - } - - [Test] - public void EntityService_Can_Get_Paged_Media_Descendants_With_Search() - { - var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); - var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); - - var root = MockedMedia.CreateMediaFolder(folderType, -1); - ServiceContext.MediaService.Save(root); - - for (int i = 0; i < 10; i++) - { - var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); - c1.Name = "ssss" + Guid.NewGuid(); - ServiceContext.MediaService.Save(c1); - - for (int j = 0; j < 5; j++) - { - var c2 = MockedMedia.CreateMediaImage(imageMediaType, c1.Id); - c2.Name = "tttt" + Guid.NewGuid(); - ServiceContext.MediaService.Save(c2); - } - } - - var service = ServiceContext.EntityService; - - long total; - var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 0, 10, out total, filter: "ssss").ToArray(); - Assert.That(entities.Length, Is.EqualTo(10)); - Assert.That(total, Is.EqualTo(10)); - entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 0, 50, out total, filter: "tttt").ToArray(); - Assert.That(entities.Length, Is.EqualTo(50)); - Assert.That(total, Is.EqualTo(50)); - } - - [Test] - public void EntityService_Can_Find_All_Content_By_UmbracoObjectTypes() - { - var service = ServiceContext.EntityService; - - var entities = service.GetAll(UmbracoObjectTypes.Document).ToArray(); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(4)); - Assert.That(entities.Any(x => x.Trashed), Is.True); - } - - [Test] - public void EntityService_Can_Find_All_Content_By_UmbracoObjectType_Id() - { - var service = ServiceContext.EntityService; - - var objectTypeId = new Guid(Constants.ObjectTypes.Document); - var entities = service.GetAll(objectTypeId).ToArray(); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(4)); - Assert.That(entities.Any(x => x.Trashed), Is.True); - } - - [Test] - public void EntityService_Can_Find_All_Content_By_Type() - { - var service = ServiceContext.EntityService; - - var entities = service.GetAll().ToArray(); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(4)); - Assert.That(entities.Any(x => x.Trashed), Is.True); - } - - [Test] - public void EntityService_Can_Get_Child_Content_By_ParentId_And_UmbracoObjectType() - { - var service = ServiceContext.EntityService; - - var entities = service.GetChildren(-1, UmbracoObjectTypes.Document).ToArray(); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(1)); - Assert.That(entities.Any(x => x.Trashed), Is.False); - } - - [Test] - public void EntityService_Can_Get_Children_By_ParentId() - { - var service = ServiceContext.EntityService; - - var entities = service.GetChildren(folderId); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(3)); - Assert.That(entities.Any(x => x.Trashed), Is.False); - } - - [Test] - public void EntityService_Can_Get_Descendants_By_ParentId() - { - var service = ServiceContext.EntityService; - - var entities = service.GetDescendents(folderId); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(4)); - Assert.That(entities.Any(x => x.Trashed), Is.False); - } - - [Test] - public void EntityService_Throws_When_Getting_All_With_Invalid_Type() - { - var service = ServiceContext.EntityService; - var objectTypeId = new Guid(Constants.ObjectTypes.ContentItem); - - Assert.Throws(() => service.GetAll()); - Assert.Throws(() => service.GetAll(UmbracoObjectTypes.ContentItem)); - Assert.Throws(() => service.GetAll(objectTypeId)); - } - - [Test] - public void EntityService_Can_Find_All_ContentTypes_By_UmbracoObjectTypes() - { - var service = ServiceContext.EntityService; - - var entities = service.GetAll(UmbracoObjectTypes.DocumentType).ToArray(); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(1)); - } - - [Test] - public void EntityService_Can_Find_All_ContentTypes_By_UmbracoObjectType_Id() - { - var service = ServiceContext.EntityService; - - var objectTypeId = new Guid(Constants.ObjectTypes.DocumentType); - var entities = service.GetAll(objectTypeId).ToArray(); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(1)); - } - - [Test] - public void EntityService_Can_Find_All_ContentTypes_By_Type() - { - var service = ServiceContext.EntityService; - - var entities = service.GetAll().ToArray(); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(1)); - } - - [Test] - public void EntityService_Can_Find_All_Media_By_UmbracoObjectTypes() - { - var service = ServiceContext.EntityService; - - var entities = service.GetAll(UmbracoObjectTypes.Media).ToArray(); - - Assert.That(entities.Any(), Is.True); - Assert.That(entities.Count(), Is.EqualTo(5)); - - Assert.That( - entities.Any( - x => - x.AdditionalData.Any(y => y.Value is UmbracoEntity.EntityProperty - && ((UmbracoEntity.EntityProperty)y.Value).PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias)), Is.True); - } - - [Test] - public void EntityService_Can_Get_ObjectType() - { - var service = ServiceContext.EntityService; - var mediaObjectType = service.GetObjectType(1031); - - Assert.NotNull(mediaObjectType); - Assert.AreEqual(mediaObjectType, UmbracoObjectTypes.MediaType); - } - - [Test] - public void EntityService_Can_Get_Key_For_Id_With_Unknown_Type() - { - var service = ServiceContext.EntityService; - var result = service.GetKeyForId(1060, UmbracoObjectTypes.Unknown); - - Assert.IsTrue(result.Success); - Assert.AreEqual(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), result.Result); +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Entities; + +namespace Umbraco.Tests.Services +{ + /// + /// Tests covering the EntityService + /// + [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerFixture)] + [TestFixture, RequiresSTA] + public class EntityServiceTests : BaseServiceTest + { + [SetUp] + public override void Initialize() + { + base.Initialize(); } - - [Test] - public void EntityService_Can_Get_Key_For_Id() - { - var service = ServiceContext.EntityService; - var result = service.GetKeyForId(1060, UmbracoObjectTypes.DocumentType); - - Assert.IsTrue(result.Success); - Assert.AreEqual(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), result.Result); - } - - [Test] - public void EntityService_Cannot_Get_Key_For_Id_With_Incorrect_Object_Type() - { - var service = ServiceContext.EntityService; - var result1 = service.GetKeyForId(1060, UmbracoObjectTypes.DocumentType); - var result2 = service.GetKeyForId(1060, UmbracoObjectTypes.MediaType); - - Assert.IsTrue(result1.Success); - Assert.IsFalse(result2.Success); - } - - [Test] - public void EntityService_Can_Get_Id_For_Key_With_Unknown_Type() - { - var service = ServiceContext.EntityService; - var result = service.GetIdForKey(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), UmbracoObjectTypes.Unknown); - - Assert.IsTrue(result.Success); - Assert.AreEqual(1060, result.Result); + + [TearDown] + public override void TearDown() + { + base.TearDown(); } - - [Test] - public void EntityService_Can_Get_Id_For_Key() - { - var service = ServiceContext.EntityService; - var result = service.GetIdForKey(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), UmbracoObjectTypes.DocumentType); - - Assert.IsTrue(result.Success); - Assert.AreEqual(1060, result.Result); - } - - [Test] - public void EntityService_Cannot_Get_Id_For_Key_With_Incorrect_Object_Type() - { - var service = ServiceContext.EntityService; - var result1 = service.GetIdForKey(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), UmbracoObjectTypes.DocumentType); - var result2 = service.GetIdForKey(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), UmbracoObjectTypes.MediaType); - - Assert.IsTrue(result1.Success); - Assert.IsFalse(result2.Success); - } - - [Test] - public void ReserveId() - { - var service = ServiceContext.EntityService; - var guid = Guid.NewGuid(); - - // can reserve - var reservedId = service.ReserveId(guid); - Assert.IsTrue(reservedId > 0); - - // can get it back - var id = service.GetIdForKey(guid, UmbracoObjectTypes.DocumentType); - Assert.IsTrue(id.Success); - Assert.AreEqual(reservedId, id.Result); - - // anything goes - id = service.GetIdForKey(guid, UmbracoObjectTypes.Media); - Assert.IsTrue(id.Success); - Assert.AreEqual(reservedId, id.Result); - - // a random guid won't work - Assert.IsFalse(service.GetIdForKey(Guid.NewGuid(), UmbracoObjectTypes.DocumentType).Success); - } - - private static bool _isSetup = false; - - private int folderId; - - public override void CreateTestData() - { - if (_isSetup == false) - { - _isSetup = true; - - base.CreateTestData(); - - //Create and Save folder-Media -> 1050 - var folderMediaType = ServiceContext.ContentTypeService.GetMediaType(1031); - var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); - ServiceContext.MediaService.Save(folder, 0); - folderId = folder.Id; - - //Create and Save image-Media -> 1051 - var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); - var image = MockedMedia.CreateMediaImage(imageMediaType, folder.Id); - ServiceContext.MediaService.Save(image, 0); - - //Create and Save file-Media -> 1052 - var fileMediaType = ServiceContext.ContentTypeService.GetMediaType(1033); - var file = MockedMedia.CreateMediaFile(fileMediaType, folder.Id); - ServiceContext.MediaService.Save(file, 0); - - var subfolder = MockedMedia.CreateMediaFolder(folderMediaType, folder.Id); - ServiceContext.MediaService.Save(subfolder, 0); - var subfolder2 = MockedMedia.CreateMediaFolder(folderMediaType, subfolder.Id); - ServiceContext.MediaService.Save(subfolder2, 0); - - } - - } - } -} \ No newline at end of file + + [Test] + public void EntityService_Can_Get_Paged_Content_Children() + { + + var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); + + var root = MockedContent.CreateSimpleContent(contentType); + ServiceContext.ContentService.Save(root); + for (int i = 0; i < 10; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); + ServiceContext.ContentService.Save(c1); + } + + var service = ServiceContext.EntityService; + + long total; + var entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Document, 0, 6, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(6)); + Assert.That(total, Is.EqualTo(10)); + entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Document, 1, 6, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(4)); + Assert.That(total, Is.EqualTo(10)); + } + + [Test] + public void EntityService_Can_Get_Paged_Content_Descendants() + { + var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); + + var root = MockedContent.CreateSimpleContent(contentType); + ServiceContext.ContentService.Save(root); + var count = 0; + for (int i = 0; i < 10; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); + ServiceContext.ContentService.Save(c1); + count++; + + for (int j = 0; j < 5; j++) + { + var c2 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), c1); + ServiceContext.ContentService.Save(c2); + count++; + } + } + + var service = ServiceContext.EntityService; + + long total; + var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 0, 31, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(31)); + Assert.That(total, Is.EqualTo(60)); + entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 1, 31, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(29)); + Assert.That(total, Is.EqualTo(60)); + } + + [Test] + public void EntityService_Can_Get_Paged_Content_Descendants_Including_Recycled() + { + var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); + + var root = MockedContent.CreateSimpleContent(contentType); + ServiceContext.ContentService.Save(root); + var toDelete = new List(); + for (int i = 0; i < 10; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); + ServiceContext.ContentService.Save(c1); + + if (i % 2 == 0) + { + toDelete.Add(c1); + } + + for (int j = 0; j < 5; j++) + { + var c2 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), c1); + ServiceContext.ContentService.Save(c2); + } + } + + foreach (var content in toDelete) + { + ServiceContext.ContentService.MoveToRecycleBin(content); + } + + var service = ServiceContext.EntityService; + + long total; + //search at root to see if it returns recycled + var entities = service.GetPagedDescendants(-1, UmbracoObjectTypes.Document, 0, 1000, out total) + .Select(x => x.Id) + .ToArray(); + + foreach (var c in toDelete) + { + Assert.True(entities.Contains(c.Id)); + } + } + + [Test] + public void EntityService_Can_Get_Paged_Content_Descendants_Without_Recycled() + { + var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); + + var root = MockedContent.CreateSimpleContent(contentType); + ServiceContext.ContentService.Save(root); + var toDelete = new List(); + for (int i = 0; i < 10; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); + ServiceContext.ContentService.Save(c1); + + if (i % 2 == 0) + { + toDelete.Add(c1); + } + + for (int j = 0; j < 5; j++) + { + var c2 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), c1); + ServiceContext.ContentService.Save(c2); + } + } + + foreach (var content in toDelete) + { + ServiceContext.ContentService.MoveToRecycleBin(content); + } + + var service = ServiceContext.EntityService; + + long total; + //search at root to see if it returns recycled + var entities = service.GetPagedDescendantsFromRoot(UmbracoObjectTypes.Document, 0, 1000, out total, includeTrashed:false) + .Select(x => x.Id) + .ToArray(); + + foreach (var c in toDelete) + { + Assert.IsFalse(entities.Contains(c.Id)); + } + } + + [Test] + public void EntityService_Can_Get_Paged_Content_Descendants_With_Search() + { + var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); + + var root = MockedContent.CreateSimpleContent(contentType); + ServiceContext.ContentService.Save(root); + + for (int i = 0; i < 10; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType, "ssss" + Guid.NewGuid(), root); + ServiceContext.ContentService.Save(c1); + + for (int j = 0; j < 5; j++) + { + var c2 = MockedContent.CreateSimpleContent(contentType, "tttt" + Guid.NewGuid(), c1); + ServiceContext.ContentService.Save(c2); + } + } + + var service = ServiceContext.EntityService; + + long total; + var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 0, 10, out total, filter: "ssss").ToArray(); + Assert.That(entities.Length, Is.EqualTo(10)); + Assert.That(total, Is.EqualTo(10)); + entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 0, 50, out total, filter: "tttt").ToArray(); + Assert.That(entities.Length, Is.EqualTo(50)); + Assert.That(total, Is.EqualTo(50)); + } + + [Test] + public void EntityService_Can_Get_Paged_Media_Children() + { + var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + + var root = MockedMedia.CreateMediaFolder(folderType, -1); + ServiceContext.MediaService.Save(root); + for (int i = 0; i < 10; i++) + { + var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); + ServiceContext.MediaService.Save(c1); + } + + var service = ServiceContext.EntityService; + + long total; + var entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Media, 0, 6, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(6)); + Assert.That(total, Is.EqualTo(10)); + entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Media, 1, 6, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(4)); + Assert.That(total, Is.EqualTo(10)); + } + + [Test] + public void EntityService_Can_Get_Paged_Media_Descendants() + { + var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + + var root = MockedMedia.CreateMediaFolder(folderType, -1); + ServiceContext.MediaService.Save(root); + var count = 0; + for (int i = 0; i < 10; i++) + { + var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); + ServiceContext.MediaService.Save(c1); + count++; + + for (int j = 0; j < 5; j++) + { + var c2 = MockedMedia.CreateMediaImage(imageMediaType, c1.Id); + ServiceContext.MediaService.Save(c2); + count++; + } + } + + var service = ServiceContext.EntityService; + + long total; + var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 0, 31, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(31)); + Assert.That(total, Is.EqualTo(60)); + entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 1, 31, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(29)); + Assert.That(total, Is.EqualTo(60)); + } + + [Test] + public void EntityService_Can_Get_Paged_Media_Descendants_Including_Recycled() + { + var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + + var root = MockedMedia.CreateMediaFolder(folderType, -1); + ServiceContext.MediaService.Save(root); + var toDelete = new List(); + for (int i = 0; i < 10; i++) + { + var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); + ServiceContext.MediaService.Save(c1); + + if (i % 2 == 0) + { + toDelete.Add(c1); + } + + for (int j = 0; j < 5; j++) + { + var c2 = MockedMedia.CreateMediaImage(imageMediaType, c1.Id); + ServiceContext.MediaService.Save(c2); + } + } + + foreach (var content in toDelete) + { + ServiceContext.MediaService.MoveToRecycleBin(content); + } + + var service = ServiceContext.EntityService; + + long total; + //search at root to see if it returns recycled + var entities = service.GetPagedDescendants(-1, UmbracoObjectTypes.Media, 0, 1000, out total) + .Select(x => x.Id) + .ToArray(); + + foreach (var media in toDelete) + { + Assert.IsTrue(entities.Contains(media.Id)); + } + } + + [Test] + public void EntityService_Can_Get_Paged_Media_Descendants_Without_Recycled() + { + var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + + var root = MockedMedia.CreateMediaFolder(folderType, -1); + ServiceContext.MediaService.Save(root); + var toDelete = new List(); + for (int i = 0; i < 10; i++) + { + var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); + ServiceContext.MediaService.Save(c1); + + if (i % 2 == 0) + { + toDelete.Add(c1); + } + + for (int j = 0; j < 5; j++) + { + var c2 = MockedMedia.CreateMediaImage(imageMediaType, c1.Id); + ServiceContext.MediaService.Save(c2); + } + } + + foreach (var content in toDelete) + { + ServiceContext.MediaService.MoveToRecycleBin(content); + } + + var service = ServiceContext.EntityService; + + long total; + //search at root to see if it returns recycled + var entities = service.GetPagedDescendantsFromRoot(UmbracoObjectTypes.Media, 0, 1000, out total, includeTrashed:false) + .Select(x => x.Id) + .ToArray(); + + foreach (var media in toDelete) + { + Assert.IsFalse(entities.Contains(media.Id)); + } + } + + [Test] + public void EntityService_Can_Get_Paged_Media_Descendants_With_Search() + { + var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + + var root = MockedMedia.CreateMediaFolder(folderType, -1); + ServiceContext.MediaService.Save(root); + + for (int i = 0; i < 10; i++) + { + var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); + c1.Name = "ssss" + Guid.NewGuid(); + ServiceContext.MediaService.Save(c1); + + for (int j = 0; j < 5; j++) + { + var c2 = MockedMedia.CreateMediaImage(imageMediaType, c1.Id); + c2.Name = "tttt" + Guid.NewGuid(); + ServiceContext.MediaService.Save(c2); + } + } + + var service = ServiceContext.EntityService; + + long total; + var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 0, 10, out total, filter: "ssss").ToArray(); + Assert.That(entities.Length, Is.EqualTo(10)); + Assert.That(total, Is.EqualTo(10)); + entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 0, 50, out total, filter: "tttt").ToArray(); + Assert.That(entities.Length, Is.EqualTo(50)); + Assert.That(total, Is.EqualTo(50)); + } + + [Test] + public void EntityService_Can_Find_All_Content_By_UmbracoObjectTypes() + { + var service = ServiceContext.EntityService; + + var entities = service.GetAll(UmbracoObjectTypes.Document).ToArray(); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(4)); + Assert.That(entities.Any(x => x.Trashed), Is.True); + } + + [Test] + public void EntityService_Can_Find_All_Content_By_UmbracoObjectType_Id() + { + var service = ServiceContext.EntityService; + + var objectTypeId = new Guid(Constants.ObjectTypes.Document); + var entities = service.GetAll(objectTypeId).ToArray(); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(4)); + Assert.That(entities.Any(x => x.Trashed), Is.True); + } + + [Test] + public void EntityService_Can_Find_All_Content_By_Type() + { + var service = ServiceContext.EntityService; + + var entities = service.GetAll().ToArray(); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(4)); + Assert.That(entities.Any(x => x.Trashed), Is.True); + } + + [Test] + public void EntityService_Can_Get_Child_Content_By_ParentId_And_UmbracoObjectType() + { + var service = ServiceContext.EntityService; + + var entities = service.GetChildren(-1, UmbracoObjectTypes.Document).ToArray(); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(1)); + Assert.That(entities.Any(x => x.Trashed), Is.False); + } + + [Test] + public void EntityService_Can_Get_Children_By_ParentId() + { + var service = ServiceContext.EntityService; + + var entities = service.GetChildren(folderId); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(3)); + Assert.That(entities.Any(x => x.Trashed), Is.False); + } + + [Test] + public void EntityService_Can_Get_Descendants_By_ParentId() + { + var service = ServiceContext.EntityService; + + var entities = service.GetDescendents(folderId); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(4)); + Assert.That(entities.Any(x => x.Trashed), Is.False); + } + + [Test] + public void EntityService_Throws_When_Getting_All_With_Invalid_Type() + { + var service = ServiceContext.EntityService; + var objectTypeId = new Guid(Constants.ObjectTypes.ContentItem); + + Assert.Throws(() => service.GetAll()); + Assert.Throws(() => service.GetAll(UmbracoObjectTypes.ContentItem)); + Assert.Throws(() => service.GetAll(objectTypeId)); + } + + [Test] + public void EntityService_Can_Find_All_ContentTypes_By_UmbracoObjectTypes() + { + var service = ServiceContext.EntityService; + + var entities = service.GetAll(UmbracoObjectTypes.DocumentType).ToArray(); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(1)); + } + + [Test] + public void EntityService_Can_Find_All_ContentTypes_By_UmbracoObjectType_Id() + { + var service = ServiceContext.EntityService; + + var objectTypeId = new Guid(Constants.ObjectTypes.DocumentType); + var entities = service.GetAll(objectTypeId).ToArray(); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(1)); + } + + [Test] + public void EntityService_Can_Find_All_ContentTypes_By_Type() + { + var service = ServiceContext.EntityService; + + var entities = service.GetAll().ToArray(); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(1)); + } + + [Test] + public void EntityService_Can_Find_All_Media_By_UmbracoObjectTypes() + { + var service = ServiceContext.EntityService; + + var entities = service.GetAll(UmbracoObjectTypes.Media).ToArray(); + + Assert.That(entities.Any(), Is.True); + Assert.That(entities.Count(), Is.EqualTo(5)); + } + + [Test] + public void EntityService_Can_Get_ObjectType() + { + var service = ServiceContext.EntityService; + var mediaObjectType = service.GetObjectType(1031); + + Assert.NotNull(mediaObjectType); + Assert.AreEqual(mediaObjectType, UmbracoObjectTypes.MediaType); + } + + [Test] + public void EntityService_Can_Get_Key_For_Id_With_Unknown_Type() + { + var service = ServiceContext.EntityService; + var result = service.GetKeyForId(1060, UmbracoObjectTypes.Unknown); + + Assert.IsTrue(result.Success); + Assert.AreEqual(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), result.Result); + } + + [Test] + public void EntityService_Can_Get_Key_For_Id() + { + var service = ServiceContext.EntityService; + var result = service.GetKeyForId(1060, UmbracoObjectTypes.DocumentType); + + Assert.IsTrue(result.Success); + Assert.AreEqual(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), result.Result); + } + + [Test] + public void EntityService_Cannot_Get_Key_For_Id_With_Incorrect_Object_Type() + { + var service = ServiceContext.EntityService; + var result1 = service.GetKeyForId(1060, UmbracoObjectTypes.DocumentType); + var result2 = service.GetKeyForId(1060, UmbracoObjectTypes.MediaType); + + Assert.IsTrue(result1.Success); + Assert.IsFalse(result2.Success); + } + + [Test] + public void EntityService_Can_Get_Id_For_Key_With_Unknown_Type() + { + var service = ServiceContext.EntityService; + var result = service.GetIdForKey(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), UmbracoObjectTypes.Unknown); + + Assert.IsTrue(result.Success); + Assert.AreEqual(1060, result.Result); + } + + [Test] + public void EntityService_Can_Get_Id_For_Key() + { + var service = ServiceContext.EntityService; + var result = service.GetIdForKey(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), UmbracoObjectTypes.DocumentType); + + Assert.IsTrue(result.Success); + Assert.AreEqual(1060, result.Result); + } + + [Test] + public void EntityService_Cannot_Get_Id_For_Key_With_Incorrect_Object_Type() + { + var service = ServiceContext.EntityService; + var result1 = service.GetIdForKey(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), UmbracoObjectTypes.DocumentType); + var result2 = service.GetIdForKey(Guid.Parse("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"), UmbracoObjectTypes.MediaType); + + Assert.IsTrue(result1.Success); + Assert.IsFalse(result2.Success); + } + + [Test] + public void ReserveId() + { + var service = ServiceContext.EntityService; + var guid = Guid.NewGuid(); + + // can reserve + var reservedId = service.ReserveId(guid); + Assert.IsTrue(reservedId > 0); + + // can get it back + var id = service.GetIdForKey(guid, UmbracoObjectTypes.DocumentType); + Assert.IsTrue(id.Success); + Assert.AreEqual(reservedId, id.Result); + + // anything goes + id = service.GetIdForKey(guid, UmbracoObjectTypes.Media); + Assert.IsTrue(id.Success); + Assert.AreEqual(reservedId, id.Result); + + // a random guid won't work + Assert.IsFalse(service.GetIdForKey(Guid.NewGuid(), UmbracoObjectTypes.DocumentType).Success); + } + + private static bool _isSetup = false; + + private int folderId; + + public override void CreateTestData() + { + if (_isSetup == false) + { + _isSetup = true; + + base.CreateTestData(); + + //Create and Save folder-Media -> 1050 + var folderMediaType = ServiceContext.ContentTypeService.GetMediaType(1031); + var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1); + ServiceContext.MediaService.Save(folder, 0); + folderId = folder.Id; + + //Create and Save image-Media -> 1051 + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + var image = MockedMedia.CreateMediaImage(imageMediaType, folder.Id); + ServiceContext.MediaService.Save(image, 0); + + //Create and Save file-Media -> 1052 + var fileMediaType = ServiceContext.ContentTypeService.GetMediaType(1033); + var file = MockedMedia.CreateMediaFile(fileMediaType, folder.Id); + ServiceContext.MediaService.Save(file, 0); + + var subfolder = MockedMedia.CreateMediaFolder(folderMediaType, folder.Id); + ServiceContext.MediaService.Save(subfolder, 0); + var subfolder2 = MockedMedia.CreateMediaFolder(folderMediaType, subfolder.Id); + ServiceContext.MediaService.Save(subfolder2, 0); + + } + + } + } +} From 0869bf27c741b13cab8876b7dc2ac71b75c42250 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 08:26:01 +0200 Subject: [PATCH 028/776] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - clean up --- .../FilterAllowedOutgoingMediaAttribute.cs | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index a246fa26ad..22ce7a5ce7 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -89,42 +89,8 @@ namespace Umbraco.Web.WebApi.Filters { FilterBasedOnStartNode(items, user); } - else - { - // FilterOutPossibleSensitiveData(items); - } - } - /// - /// Removes all properties of the items except the umbracoFile. - /// - /// - private void FilterOutPossibleSensitiveData(IList items) - { - foreach (dynamic item in items) - { - - if (item.Properties != null) - { - if (item.Properties is IList properties) - // Iterate reverse, because we removed from the same list, such that the ordering of indexes not - // changes doing the iteration - for (var i = properties.Count - 1; i >= 0; i--) - { - dynamic property = properties[i]; - if (property.Alias != null && property.Alias is string) - { - var alias = property.Alias as string; - if (string.Equals(alias, Constants.Conventions.Media.File) == false) - { - properties.RemoveAt(i); - } - } - } - } - } - } internal void FilterBasedOnStartNode(IList items, IUser user) { From afac4bb918a578d8b5513cd5cac720dde6e2089d Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 09:18:30 +0200 Subject: [PATCH 029/776] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - missing entityResource injection --- .../src/views/propertyeditors/grid/editors/rte.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js index 30ea1eaef6..601c0a36d2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js @@ -1,7 +1,7 @@ (function() { "use strict"; - function GridRichTextEditorController($scope, tinyMceService, macroService, editorState) { + function GridRichTextEditorController($scope, tinyMceService, macroService, editorState, entityResource) { var vm = this; From d558646d75a45b8cd52c71d21bdd92644e1e3f58 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 09:21:51 +0200 Subject: [PATCH 030/776] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - Fix for NullReferenceException --- src/Umbraco.Core/Services/ContentService.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index cf49e892dd..3e5acd0296 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -310,9 +310,13 @@ namespace Umbraco.Core.Services { if (string.Equals(contentProperty.PropertyType.PropertyEditorAlias, Constants.PropertyEditors.TinyMCEAlias)) { - var value = contentProperty.Value.ToString(); + var value = contentProperty.Value?.ToString(); + + if (string.IsNullOrEmpty(value)) + { + result.AddRange(GetAnchorValuesFromRTEContent(value)); + } - result.AddRange(GetAnchorValuesFromRTEContent(value)); } } From 4dd325cec943045847852fde3e28a74da37ae1f6 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 09:21:51 +0200 Subject: [PATCH 031/776] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - Fix for NullReferenceException --- src/Umbraco.Core/Services/ContentService.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index cf49e892dd..abde058252 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -310,9 +310,13 @@ namespace Umbraco.Core.Services { if (string.Equals(contentProperty.PropertyType.PropertyEditorAlias, Constants.PropertyEditors.TinyMCEAlias)) { - var value = contentProperty.Value.ToString(); + var value = contentProperty.Value?.ToString(); + + if (!string.IsNullOrEmpty(value)) + { + result.AddRange(GetAnchorValuesFromRTEContent(value)); + } - result.AddRange(GetAnchorValuesFromRTEContent(value)); } } From 36559b876123bf9521b6f5f93dd48bd6727659b5 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 09:22:56 +0200 Subject: [PATCH 032/776] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - Fix for NullReferenceException --- src/Umbraco.Core/Services/ContentService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 3e5acd0296..abde058252 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -312,7 +312,7 @@ namespace Umbraco.Core.Services { var value = contentProperty.Value?.ToString(); - if (string.IsNullOrEmpty(value)) + if (!string.IsNullOrEmpty(value)) { result.AddRange(GetAnchorValuesFromRTEContent(value)); } From 1994194656c419359fc827cfc030818612bf9a20 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 11:00:20 +0200 Subject: [PATCH 033/776] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - Review fixes --- src/Umbraco.Core/Models/DataTypeDefinition.cs | 41 - .../Models/DataTypeDefinitionExtensions.cs | 47 + .../Models/IDataTypeDefinition.cs | 5 - .../Factories/DataTypeDefinitionFactory.cs | 2 +- src/Umbraco.Core/Services/ContentService.cs | 12 +- src/Umbraco.Core/Services/DataTypeService.cs | 16 - .../Services/DateTypeServiceExtensions.cs | 22 + src/Umbraco.Core/Services/IContentService.cs | 4 +- src/Umbraco.Core/Services/IDataTypeService.cs | 1 - src/Umbraco.Core/Umbraco.Core.csproj | 2 + .../src/common/resources/content.resource.js | 1045 ++++++++--------- .../src/common/resources/entity.resource.js | 32 +- .../src/common/resources/media.resource.js | 766 ++++++------ .../src/common/services/search.service.js | 20 +- .../common/dialogs/linkpicker.controller.js | 3 +- .../common/dialogs/mediapicker.controller.js | 2 +- .../common/dialogs/treepicker.controller.js | 451 +++---- .../linkpicker/linkpicker.controller.js | 2 +- src/Umbraco.Web/Editors/ContentController.cs | 12 +- src/Umbraco.Web/Editors/EntityController.cs | 48 +- src/Umbraco.Web/Editors/MediaController.cs | 49 +- .../Models/ContentEditing/UrlAndAnchors.cs | 4 +- .../Mapping/ContentPropertyBasicConverter.cs | 16 +- .../ContentPropertyDisplayConverter.cs | 134 +-- .../Mapping/ContentPropertyDtoConverter.cs | 82 +- .../Mapping/ContentPropertyModelMapper.cs | 74 +- .../Models/Mapping/PreValueDisplayResolver.cs | 4 +- src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 27 +- src/Umbraco.Web/Trees/TreeControllerBase.cs | 1 + ...EnsureUserPermissionForContentAttribute.cs | 22 +- .../FilterAllowedOutgoingMediaAttribute.cs | 16 +- 31 files changed, 1417 insertions(+), 1545 deletions(-) create mode 100644 src/Umbraco.Core/Models/DataTypeDefinitionExtensions.cs create mode 100644 src/Umbraco.Core/Services/DateTypeServiceExtensions.cs diff --git a/src/Umbraco.Core/Models/DataTypeDefinition.cs b/src/Umbraco.Core/Models/DataTypeDefinition.cs index 5e1fd649ae..997dd6b16f 100644 --- a/src/Umbraco.Core/Models/DataTypeDefinition.cs +++ b/src/Umbraco.Core/Models/DataTypeDefinition.cs @@ -32,35 +32,6 @@ namespace Umbraco.Core.Models private string _propertyEditorAlias; private DataTypeDatabaseType _databaseType; - private static readonly ISet IdsOfBuildInDataTypes = new HashSet() - { - Constants.DataTypes.ContentPickerGuid, - Constants.DataTypes.MemberPickerGuid, - Constants.DataTypes.MediaPickerGuid, - Constants.DataTypes.MultipleMediaPickerGuid, - Constants.DataTypes.RelatedLinksGuid, - Constants.DataTypes.MemberGuid, - Constants.DataTypes.ImageCropperGuid, - Constants.DataTypes.TagsGuid, - Constants.DataTypes.ListViewContentGuid, - Constants.DataTypes.ListViewMediaGuid, - Constants.DataTypes.ListViewMembersGuid, - Constants.DataTypes.DatePickerWithTimeGuid, - Constants.DataTypes.ApprovedColorGuid, - Constants.DataTypes.DropdownMultipleGuid, - Constants.DataTypes.RadioboxGuid, - Constants.DataTypes.DatePickerGuid, - Constants.DataTypes.DropdownGuid, - Constants.DataTypes.CheckboxListGuid, - Constants.DataTypes.CheckboxGuid, - Constants.DataTypes.NumericGuid, - Constants.DataTypes.RichtextEditorGuid, - Constants.DataTypes.TextstringGuid, - Constants.DataTypes.TextareaGuid, - Constants.DataTypes.UploadGuid, - Constants.DataTypes.LabelGuid, - }; - [Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the alternative contructor that specifies an alias")] [EditorBrowsable(EditorBrowsableState.Never)] public DataTypeDefinition(int parentId, Guid controlId) @@ -94,15 +65,6 @@ namespace Umbraco.Core.Models _additionalData = new Dictionary(); } - public DataTypeDefinition(string propertyEditorAlias, Guid uniqueId) - { - _parentId = -1; - _propertyEditorAlias = propertyEditorAlias; - - _additionalData = new Dictionary(); - IsBuildInDataType = IdsOfBuildInDataTypes.Contains(uniqueId); - } - private static readonly Lazy Ps = new Lazy(); private class PropertySelectors @@ -253,8 +215,5 @@ namespace Umbraco.Core.Models { get { return _additionalData; } } - - public bool IsBuildInDataType { get;} } - } diff --git a/src/Umbraco.Core/Models/DataTypeDefinitionExtensions.cs b/src/Umbraco.Core/Models/DataTypeDefinitionExtensions.cs new file mode 100644 index 0000000000..45c4420820 --- /dev/null +++ b/src/Umbraco.Core/Models/DataTypeDefinitionExtensions.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Models +{ + public static class DataTypeDefinitionExtensions + { + private static readonly ISet IdsOfBuildInDataTypes = new HashSet() + { + Constants.DataTypes.ContentPickerGuid, + Constants.DataTypes.MemberPickerGuid, + Constants.DataTypes.MediaPickerGuid, + Constants.DataTypes.MultipleMediaPickerGuid, + Constants.DataTypes.RelatedLinksGuid, + Constants.DataTypes.MemberGuid, + Constants.DataTypes.ImageCropperGuid, + Constants.DataTypes.TagsGuid, + Constants.DataTypes.ListViewContentGuid, + Constants.DataTypes.ListViewMediaGuid, + Constants.DataTypes.ListViewMembersGuid, + Constants.DataTypes.DatePickerWithTimeGuid, + Constants.DataTypes.ApprovedColorGuid, + Constants.DataTypes.DropdownMultipleGuid, + Constants.DataTypes.RadioboxGuid, + Constants.DataTypes.DatePickerGuid, + Constants.DataTypes.DropdownGuid, + Constants.DataTypes.CheckboxListGuid, + Constants.DataTypes.CheckboxGuid, + Constants.DataTypes.NumericGuid, + Constants.DataTypes.RichtextEditorGuid, + Constants.DataTypes.TextstringGuid, + Constants.DataTypes.TextareaGuid, + Constants.DataTypes.UploadGuid, + Constants.DataTypes.LabelGuid, + }; + + /// + /// Returns true if this date type is build-in/default. + /// + /// The data type definition. + /// + public static bool IsBuildInDataType(this IDataTypeDefinition dataTypeDefinition) + { + return IdsOfBuildInDataTypes.Contains(dataTypeDefinition.Key); + } + } +} diff --git a/src/Umbraco.Core/Models/IDataTypeDefinition.cs b/src/Umbraco.Core/Models/IDataTypeDefinition.cs index f57a1bdca7..0162fe7738 100644 --- a/src/Umbraco.Core/Models/IDataTypeDefinition.cs +++ b/src/Umbraco.Core/Models/IDataTypeDefinition.cs @@ -20,10 +20,5 @@ namespace Umbraco.Core.Models /// Gets or Sets the DatabaseType for which the DataType's value is saved as /// DataTypeDatabaseType DatabaseType { get; set; } - - /// - /// Gets information about whether this data type is build in or not. - /// - bool IsBuildInDataType { get;} } } diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs index 14c162d8ea..71248a1ce8 100644 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Persistence.Factories public IDataTypeDefinition BuildEntity(DataTypeDto dto) { - var dataTypeDefinition = new DataTypeDefinition(dto.PropertyEditorAlias,dto.NodeDto.UniqueId); + var dataTypeDefinition = new DataTypeDefinition(dto.PropertyEditorAlias); try diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index abde058252..2994bcc4f6 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -35,6 +35,7 @@ namespace Umbraco.Core.Services //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 static readonly Regex AnchorRegex = new Regex("", RegexOptions.Compiled); public ContentService( IDatabaseUnitOfWorkProvider provider, @@ -300,7 +301,7 @@ namespace Umbraco.Core.Services return content; } - public IList GetAnchorValuesFromRTEs(int id) + public IEnumerable GetAnchorValuesFromRTEs(int id) { var result = new List(); @@ -308,7 +309,7 @@ namespace Umbraco.Core.Services foreach (var contentProperty in content.Properties) { - if (string.Equals(contentProperty.PropertyType.PropertyEditorAlias, Constants.PropertyEditors.TinyMCEAlias)) + if (contentProperty.PropertyType.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.TinyMCEAlias)) { var value = contentProperty.Value?.ToString(); @@ -324,11 +325,12 @@ namespace Umbraco.Core.Services return result; } - public IList GetAnchorValuesFromRTEContent(string rteContent) + + public IEnumerable GetAnchorValuesFromRTEContent(string rteContent) { var result = new List(); - var regex = new Regex(""); - var matches = regex.Matches(rteContent); + + var matches = AnchorRegex.Matches(rteContent); foreach (Match match in matches) { diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index b02fb644d8..cc9e99d39f 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -349,22 +349,6 @@ namespace Umbraco.Core.Services new OperationStatus(MoveOperationStatusType.Success, evtMsgs)); } - public bool IsDataTypeIgnoringUserStartNodes(Guid key) - { - var dataType = GetDataTypeDefinitionById(key); - - if (dataType != null) - { - var preValues = GetPreValuesCollectionByDataTypeId(dataType.Id); - if (preValues.PreValuesAsDictionary.TryGetValue(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, out var preValue)) - { - return string.Equals(preValue.Value, "1", StringComparison.InvariantCulture); - } - } - - return false; - } - /// /// Saves an /// diff --git a/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs new file mode 100644 index 0000000000..63006653ab --- /dev/null +++ b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs @@ -0,0 +1,22 @@ +using System; + +namespace Umbraco.Core.Services +{ + public static class DateTypeServiceExtensions + { + public static bool IsDataTypeIgnoringUserStartNodes(this IDataTypeService dataTypeService, Guid key) + { + var dataType = dataTypeService.GetDataTypeDefinitionById(key); + + if (dataType != null) + { + var preValues = dataTypeService.GetPreValuesCollectionByDataTypeId(dataType.Id); + if (preValues.PreValuesAsDictionary.TryGetValue( + Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, out var preValue)) + return preValue.Value.InvariantEquals("1"); + } + + return false; + } + } +} diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 8c063890fd..021b483ec2 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -714,7 +714,7 @@ namespace Umbraco.Core.Services /// IContent CreateContentWithIdentity(string name, int parentId, string contentTypeAlias, int userId = 0); - IList GetAnchorValuesFromRTEs(int id); - IList GetAnchorValuesFromRTEContent(string rteContent); + IEnumerable GetAnchorValuesFromRTEs(int id); + IEnumerable GetAnchorValuesFromRTEContent(string rteContent); } } diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index de8ee5f49c..7410268ae4 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -163,6 +163,5 @@ namespace Umbraco.Core.Services Attempt> Move(IDataTypeDefinition toMove, int parentId); - bool IsDataTypeIgnoringUserStartNodes(Guid dataTypeId); } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index f29c1bea46..49f5e8b5d2 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -377,6 +377,7 @@ + @@ -767,6 +768,7 @@ + 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 d85cc74d7d..fe47c753fa 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 @@ -1,34 +1,34 @@ /** - * @ngdoc service - * @name umbraco.resources.contentResource - * @description Handles all transactions of content data - * from the angular application to the Umbraco database, using the Content WebApi controller - * - * all methods returns a resource promise async, so all operations won't complete untill .then() is completed. - * - * @requires $q - * @requires $http - * @requires umbDataFormatter - * @requires umbRequestHelper - * - * ##usage - * To use, simply inject the contentResource into any controller or service that needs it, and make - * sure the umbraco.resources module is accesible - which it should be by default. - * - *
-  *    contentResource.getById(1234)
-  *          .then(function(data) {
-  *              $scope.content = data;
-  *          });
-  * 
- **/ + * @ngdoc service + * @name umbraco.resources.contentResource + * @description Handles all transactions of content data + * from the angular application to the Umbraco database, using the Content WebApi controller + * + * all methods returns a resource promise async, so all operations won't complete untill .then() is completed. + * + * @requires $q + * @requires $http + * @requires umbDataFormatter + * @requires umbRequestHelper + * + * ##usage + * To use, simply inject the contentResource into any controller or service that needs it, and make + * sure the umbraco.resources module is accesible - which it should be by default. + * + *
+ *    contentResource.getById(1234)
+ *          .then(function(data) {
+ *              $scope.content = data;
+ *          });
+ * 
+ **/ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { /** internal method process the saving of data and post processing the result */ - function saveContentItem(content, action, files, restApiUrl) { + function saveContentItem(content, action, files, restApiUrl) { return umbRequestHelper.postSaveContent({ - restApiUrl: restApiUrl, + restApiUrl: restApiUrl, content: content, action: action, files: files, @@ -41,55 +41,55 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { return { - savePermissions: function (saveModel) { - if (!saveModel) { - throw "saveModel cannot be null"; - } - if (!saveModel.contentId) { - throw "saveModel.contentId cannot be null"; - } - if (!saveModel.permissions) { - throw "saveModel.permissions cannot be null"; - } + savePermissions: function (saveModel) { + if (!saveModel) { + throw "saveModel cannot be null"; + } + if (!saveModel.contentId) { + throw "saveModel.contentId cannot be null"; + } + if (!saveModel.permissions) { + throw "saveModel.permissions cannot be null"; + } - return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSaveUserGroupPermissions"), - saveModel), - 'Failed to save permissions'); - }, + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSaveUserGroupPermissions"), + saveModel), + 'Failed to save permissions'); + }, getRecycleBin: function () { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for content recycle bin'); + $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. - * - */ + * @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"; @@ -102,37 +102,37 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort content'); + $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. - * - */ + * @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"; @@ -145,38 +145,38 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - 'Failed to move content'); + $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. - * - */ + * @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"; @@ -189,169 +189,152 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"), - args), - 'Failed to copy content'); + $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. - * - */ + * @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); + $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. - * - */ + * @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'); + $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. - * - */ + * @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); + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "DeleteById", + [{ id: id }])), + 'Failed to delete item ' + id); }, deleteBlueprint: function (id) { - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "DeleteBlueprint", - [{ id: id }])), - 'Failed to delete blueprint ' + id); + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "DeleteBlueprint", + [{ id: id }])), + 'Failed to delete blueprint ' + 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 - * @param {Object} options optional options object - * @param {Guid} options.dataTypeId set to determine whether to allow a user to choose nodes that they normally don't have access to - * @returns {Promise} resourcePromise object containing the content item. - * - */ - getById: function (id, options) { - var defaults = { - dataTypeId: null - }; - 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; - - var args = [{ id: id }]; - if(options.dataTypeId){ - args.push({ dataTypeId: options.dataTypeId }); - } + * @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", - args)), - 'Failed to retrieve data for content id ' + id); + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetById", + [{ id: id }])), + 'Failed to retrieve data for content id ' + id); }, getBlueprintById: function (id) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetBlueprintById", - [{ id: id }])), - 'Failed to retrieve data for content id ' + id); + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetBlueprintById", + [{ id: id }])), + 'Failed to retrieve data for content id ' + id); }, getNotifySettingsById: function (id) { @@ -378,26 +361,26 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @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. - * - */ + * @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 = ""; @@ -406,125 +389,125 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }); return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for content with multiple ids'); + $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. - * - */ + * @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); + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty content item type ' + alias); }, getBlueprintScaffold: function (parentId, blueprintId) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetEmpty", - [{ blueprintId: blueprintId }, { parentId: parentId}])), - 'Failed to retrieve blueprint for id ' + blueprintId); + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetEmpty", + [{ blueprintId: blueprintId }, { parentId: parentId}])), + 'Failed to retrieve blueprint for id ' + blueprintId); }, /** - * @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. - * - */ + * @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); + $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. - * - */ + * @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 = { @@ -584,44 +567,44 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @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. - * - */ + * @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); + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "HasPermission", + [{ permissionToCheck: permission }, { nodeId: id }])), + 'Failed to check permission for item ' + id); }, getDetailedPermissions: function (contentId) { - return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "GetDetailedPermissions", { contentId: contentId })), - 'Failed to retrieve permissions for content item ' + contentId); + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "GetDetailedPermissions", { contentId: contentId })), + 'Failed to retrieve permissions for content item ' + contentId); }, getPermissions: function (nodeIds) { @@ -635,136 +618,136 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @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. - * - */ + * @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) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"); - return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"); + return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); }, saveBlueprint: function (content, isNew, files) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSaveBlueprint"); - return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSaveBlueprint"); + return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint); }, /** - * @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. - * - */ + * @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) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"); - return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint); + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"); + return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint); }, /** - * @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. - * - */ + * @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) { - var endpoint = umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostSave"); - return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint); + var endpoint = umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostSave"); + return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint); }, /** - * @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. - * - */ + * @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) { @@ -772,12 +755,12 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "contentApiBaseUrl", - "PostPublishById", - [{ id: id }])), - 'Failed to publish content with id ' + id); + $http.post( + umbRequestHelper.getApiUrl( + "contentApiBaseUrl", + "PostPublishById", + [{ id: id }])), + 'Failed to publish content with id ' + id); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index db5e9dd404..416fd0a286 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -172,14 +172,14 @@ function entityResource($q, $http, umbRequestHelper) { umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetUrlAndAnchors", - [{ id: id }])), + { id: id })), 'Failed to retrieve url and anchors data for id ' + id); }, getAnchors: function (rteContent) { - if (rteContent == null || rteContent.length === 0) { + if (!rteContent || rteContent.length === 0) { return []; } @@ -188,7 +188,7 @@ function entityResource($q, $http, umbRequestHelper) { umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetAnchors", - [{ rteContent: rteContent }])), + { rteContent: rteContent })), 'Failed to anchors data for rte content ' + rteContent); }, @@ -543,19 +543,16 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity array. * */ - search: function (query, type, options, canceler) { + search: function (query, type, searchFrom, canceler, dataTypeId) { - var defaults = { - searchFrom: null, - dataTypeId: null - }; - if (options === undefined) { - options = {}; + var args = [{ query: query }, { type: type }]; + if (searchFrom) { + args.push({ searchFrom: searchFrom }); + } + + if (dataTypeId) { + args.push({ dataTypeId: dataTypeId }); } - //overwrite the defaults if there are any specified - angular.extend(defaults, options); - //now copy back to the options we will use - options = defaults; var httpConfig = {}; if (canceler) { @@ -567,12 +564,7 @@ function entityResource($q, $http, umbRequestHelper) { umbRequestHelper.getApiUrl( "entityApiBaseUrl", "Search", - { - query: query, - type: type, - searchFrom: options.searchFrom, - dataTypeId: options.dataTypeId - }), + args), httpConfig), 'Failed to retrieve entity data for query ' + query); }, 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 6600aec0c7..ab5b88f841 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 @@ -1,16 +1,16 @@ /** - * @ngdoc service - * @name umbraco.resources.mediaResource - * @description Loads in data for media - **/ + * @ngdoc service + * @name umbraco.resources.mediaResource + * @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"), + "mediaApiBaseUrl", + "PostSave"), content: content, action: action, files: files, @@ -24,35 +24,35 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { getRecycleBin: function () { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for media recycle bin'); + $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. - * - */ + * @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"; @@ -65,37 +65,37 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort media'); + $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. - * - */ + * @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"; @@ -108,121 +108,121 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id - }), - { - error: function(data){ - var errorMsg = 'Failed to move media'; - if (data.id !== undefined && data.parentId !== undefined) { - if (data.id === data.parentId) { - errorMsg = 'Media can\'t be moved into itself'; - } - } - else if (data.notifications !== undefined) { - if (data.notifications.length > 0) { - if (data.notifications[0].header.length > 0) { - errorMsg = data.notifications[0].header; - } - if (data.notifications[0].message.length > 0) { - errorMsg = errorMsg + ": " + data.notifications[0].message; - } + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }), + { + error: function(data){ + var errorMsg = 'Failed to move media'; + if (data.id !== undefined && data.parentId !== undefined) { + if (data.id === data.parentId) { + errorMsg = 'Media can\'t be moved into itself'; + } + } + else if (data.notifications !== undefined) { + if (data.notifications.length > 0) { + if (data.notifications[0].header.length > 0) { + errorMsg = data.notifications[0].header; + } + if (data.notifications[0].message.length > 0) { + errorMsg = errorMsg + ": " + data.notifications[0].message; } } + } - return { - errorMsg: errorMsg - }; - } - }); + return { + errorMsg: errorMsg + }; + } + }); }, /** - * @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. - * - */ + * @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); + $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. - * - */ + * @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); + $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. - * - */ + * @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 = ""; @@ -231,96 +231,96 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { }); return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for media ids ' + ids); + $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. - * - */ + * @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); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty media item type ' + alias); }, rootMedia: function () { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRootMedia")), - 'Failed to retrieve data for root media'); + $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. - * - */ + * @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 = { @@ -329,8 +329,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { filter: '', orderDirection: "Ascending", orderBy: "SortOrder", - orderBySystemField: true, - dataTypeId: null + orderBySystemField: true }; if (options === undefined) { options = {}; @@ -362,111 +361,110 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { } 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 }, - { dataTypeId: options.dataTypeId } - ])), - 'Failed to retrieve children for media item ' + parentId); + $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#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. - * - */ + * @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. - * - */ + * @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'); + $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, - * - * NOTE: This will return a max of 500 folders, if more is required it needs to be paged - * - * ##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. - * - */ + * @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, + * + * NOTE: This will return a max of 500 folders, if more is required it needs to be paged + * + * ##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; @@ -474,67 +472,67 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { //NOTE: This will return a max of 500 folders, if more is required it needs to be paged return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildFolders", - { - id: parentId - })), - 'Failed to retrieve child folders for media item ' + parentId); + $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. - * - */ + * @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'); + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#search - * @methodOf umbraco.resources.mediaResource - * - * @description - * Paginated search for media items starting on the supplied nodeId - * - * ##usage - *
-          * mediaResource.search("my search", 1, 100, -1)
-          *    .then(function(searchResult) {
-          *        alert('it's here!');
-          *    });
-          * 
- * - * @param {string} query The search query - * @param {int} pageNumber The page number - * @param {int} pageSize The number of media items on a page - * @param {int} searchFrom NodeId to search from (-1 for root) - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#search + * @methodOf umbraco.resources.mediaResource + * + * @description + * Paginated search for media items starting on the supplied nodeId + * + * ##usage + *
+         * mediaResource.search("my search", 1, 100, -1)
+         *    .then(function(searchResult) {
+         *        alert('it's here!');
+         *    });
+         * 
+ * + * @param {string} query The search query + * @param {int} pageNumber The page number + * @param {int} pageSize The number of media items on a page + * @param {int} searchFrom NodeId to search from (-1 for root) + * @returns {Promise} resourcePromise object. + * + */ search: function (query, pageNumber, pageSize, searchFrom) { var args = [ @@ -545,12 +543,12 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { ]; return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "Search", - args)), - 'Failed to retrieve media items for search: ' + query); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "Search", + args)), + 'Failed to retrieve media items for search: ' + query); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js index 24b946cdc8..e4f527f2d3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js @@ -42,11 +42,7 @@ angular.module('umbraco.services') throw "args.term is required"; } - var options = { - searchFrom: args.searchFrom - } - - return entityResource.search(args.term, "Member", options).then(function (data) { + return entityResource.search(args.term, "Member", args.searchFrom).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureMemberResult(item); }); @@ -71,12 +67,7 @@ angular.module('umbraco.services') throw "args.term is required"; } - var options = { - searchFrom: args.searchFrom, - dataTypeId: args.dataTypeId - } - - return entityResource.search(args.term, "Document", options, args.canceler).then(function (data) { + return entityResource.search(args.term, "Document", args.searchFrom, args.canceler, args.dataTypeId).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureContentResult(item); }); @@ -101,12 +92,7 @@ angular.module('umbraco.services') throw "args.term is required"; } - var options = { - searchFrom: args.searchFrom, - dataTypeId: args.dataTypeId - } - - return entityResource.search(args.term, "Media", options).then(function (data) { + return entityResource.search(args.term, "Media", args.searchFrom, args.canceler, args.dataTypeId).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureMediaResult(item); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js index b1fa038c44..e08178ec93 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js @@ -8,7 +8,6 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", searchText = value + "..."; }); - debugger; $scope.dialogTreeEventHandler = $({}); $scope.target = {}; $scope.searchInfo = { @@ -134,7 +133,7 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", $scope.target.id = media.id; $scope.target.isMedia = true; $scope.target.name = media.name; - $scope.target.url = mediaHelper.resolveFile(media); + $scope.target.url = media.image; } }); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js index da7ca307ca..a0de457b87 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js @@ -83,7 +83,7 @@ angular.module("umbraco") entityResource.getChildren(folder.id, "Media") .then(function(data) { for (i=0;i= 0; + var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0; - if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { - value.filtered = true; + if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) { + value.filtered = true; - if (dialogOptions.filterCssClass) { - if (!value.cssClasses) { - value.cssClasses = []; - } - value.cssClasses.push(dialogOptions.filterCssClass); - } - } - }); - } - } + if (dialogOptions.filterCssClass) { + if (!value.cssClasses) { + value.cssClasses = []; + } + value.cssClasses.push(dialogOptions.filterCssClass); + } + } + }); + } + } - $scope.multiSubmit = function (result) { - entityResource.getByIds(result, entityType).then(function (ents) { - $scope.submit(ents); - }); - }; + $scope.multiSubmit = function (result) { + entityResource.getByIds(result, entityType).then(function (ents) { + $scope.submit(ents); + }); + }; - /** method to select a search result */ - $scope.selectResult = function (evt, result) { + /** method to select a search result */ + $scope.selectResult = function (evt, result) { if (result.filtered) { return; } - result.selected = result.selected === true ? false : true; + result.selected = result.selected === true ? false : true; - //since result = an entity, we'll pass it in so we don't have to go back to the server - select(result.name, result.id, result); + //since result = an entity, we'll pass it in so we don't have to go back to the server + select(result.name, result.id, result); - //add/remove to our custom tracked list of selected search results + //add/remove to our custom tracked list of selected search results if (result.selected) { $scope.searchInfo.selectedSearchResults.push(result); } @@ -306,23 +306,23 @@ angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController", }); } - //ensure the tree node in the tree is checked/unchecked if it already exists there - if (tree) { - var found = treeService.getDescendantNode(tree.root, result.id); + //ensure the tree node in the tree is checked/unchecked if it already exists there + if (tree) { + var found = treeService.getDescendantNode(tree.root, result.id); if (found) { found.selected = result.selected; } - } + } - }; + }; - $scope.hideSearch = function () { + $scope.hideSearch = function () { //Traverse the entire displayed tree and update each node to sync with the selected search results - if (tree) { + if (tree) { - //we need to ensure that any currently displayed nodes that get selected - // from the search get updated to have a check box! + //we need to ensure that any currently displayed nodes that get selected + // from the search get updated to have a check box! function checkChildren(children) { _.each(children, function (child) { //check if the id is in the selection, if so ensure it's flagged as selected @@ -385,7 +385,7 @@ angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController", }); } checkChildren(tree.root.children); - } + } $scope.searchInfo.showSearch = false; @@ -394,37 +394,38 @@ angular.module("umbraco").controller("Umbraco.Dialogs.TreePickerController", $scope.searchInfo.results = []; } - $scope.onSearchResults = function(results) {; + $scope.onSearchResults = function(results) { + //filter all items - this will mark an item as filtered - performFiltering(results); + performFiltering(results); - //now actually remove all filtered items so they are not even displayed - results = _.filter(results, function(item) { - return !item.filtered; - }); + //now actually remove all filtered items so they are not even displayed + results = _.filter(results, function(item) { + return !item.filtered; + }); - $scope.searchInfo.results = results; + $scope.searchInfo.results = results; //sync with the curr selected results - _.each($scope.searchInfo.results, function (result) { - var exists = _.find($scope.dialogData.selection, function (selectedId) { - return result.id == selectedId; - }); - if (exists) { - result.selected = true; - } - }); + _.each($scope.searchInfo.results, function (result) { + var exists = _.find($scope.dialogData.selection, function (selectedId) { + return result.id == selectedId; + }); + if (exists) { + result.selected = true; + } + }); - $scope.searchInfo.showSearch = true; - }; + $scope.searchInfo.showSearch = true; + }; - $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - $scope.$on('$destroy', function () { - $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); - $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); - }); - }); + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + }); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js index 787fe4186e..2b6b77ed41 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js @@ -128,7 +128,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", $scope.model.target.udi = media.udi; $scope.model.target.isMedia = true; $scope.model.target.name = media.name; - $scope.model.target.url = mediaHelper.resolveFile(media); + $scope.model.target.url = media.image; $scope.mediaPickerOverlay.show = false; $scope.mediaPickerOverlay = null; diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index c909c1eb84..1ac8318f9c 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -262,11 +262,10 @@ namespace Umbraco.Web.Editors /// Gets the content json for the content id ///
/// - /// If set used to lookup whether the user and group start node permissions should be ignored. /// [OutgoingEditorModelEvent] [EnsureUserPermissionForContent("id")] - public ContentItemDisplay GetById(int id, [FromUri]Guid? dataTypeId = null) + public ContentItemDisplay GetById(int id) { var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); if (foundContent == null) @@ -1117,7 +1116,6 @@ namespace Umbraco.Web.Editors /// The content to lookup, if the contentItem is not specified /// /// Specifies the already resolved content item to check against - /// If set to true, user and group start node permissions will be ignored. /// internal static bool CheckPermissions( IDictionary storage, @@ -1127,8 +1125,7 @@ namespace Umbraco.Web.Editors IEntityService entityService, int nodeId, char[] permissionsToCheck = null, - IContent contentItem = null, - bool ignoreUserStartNodes = false) + IContent contentItem = null) { if (storage == null) throw new ArgumentNullException("storage"); if (user == null) throw new ArgumentNullException("user"); @@ -1149,11 +1146,6 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } - if(ignoreUserStartNodes) - { - return true; - } - var hasPathAccess = (nodeId == Constants.System.Root) ? user.HasContentRootAccess(entityService) : (nodeId == Constants.System.RecycleBinContent) diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index d6e997b57e..6c9cc7c36b 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -20,6 +20,7 @@ using System.Text.RegularExpressions; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using System.Web.Http.Controllers; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Services; using Umbraco.Core.Xml; using Umbraco.Web.Search; using Umbraco.Web.Trees; @@ -74,22 +75,6 @@ namespace Umbraco.Web.Editors return returnObj; } - /// - /// Searches for results based on the entity type - /// - /// - /// - /// - /// A starting point for the search, generally a node id, but for members this is a member type alias - /// - /// - [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] - [HttpGet] - public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null) - { - return Search(query, type, null, searchFrom); - } - /// /// Searches for results based on the entity type /// @@ -101,7 +86,7 @@ namespace Umbraco.Web.Editors /// If set used to look up whether user and group start node permissions will be ignored. /// [HttpGet] - public IEnumerable Search(string query, UmbracoEntityTypes type, Guid? dataTypeId, string searchFrom = null) + public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null, Guid? dataTypeId = null) { //TODO: Should we restrict search results based on what app the user has access to? // - Theoretically you shouldn't be able to see member data if you don't have access to members right? @@ -301,7 +286,7 @@ namespace Umbraco.Web.Editors } [HttpPost] - public IList GetAnchors(string rteContent) + public IEnumerable GetAnchors(string rteContent) { var anchorValues = Services.ContentService.GetAnchorValuesFromRTEContent(rteContent); @@ -565,29 +550,16 @@ namespace Umbraco.Web.Editors } } - [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] - public PagedResult GetPagedDescendants( - int id, - UmbracoEntityTypes type, - int pageNumber, - int pageSize, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - string filter = "") - { - return GetPagedDescendants(id, type, pageNumber, pageSize, - null, orderBy, orderDirection, filter); - } public PagedResult GetPagedDescendants( int id, UmbracoEntityTypes type, int pageNumber, int pageSize, - Guid? dataTypeId, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, - string filter = "") + string filter = "", + Guid? dataTypeId = null) { if (pageNumber <= 0) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -654,13 +626,7 @@ namespace Umbraco.Web.Editors private bool IsDataTypeIgnoringUserStartNodes(Guid? dataTypeId) => dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); - [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] - public IEnumerable GetAncestors(int id, UmbracoEntityTypes type) - { - return GetResultForAncestors(id, type, null); - } - - public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, Guid? dataTypeId) + public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, Guid? dataTypeId = null) { return GetResultForAncestors(id, type, dataTypeId); } @@ -681,7 +647,7 @@ namespace Umbraco.Web.Editors private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false) { long total; - return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, ignoreUserStartNodes, searchFrom); + return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, searchFrom, ignoreUserStartNodes); } diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 788ae3a0c7..54aaacf2f2 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -264,28 +264,23 @@ namespace Umbraco.Web.Editors string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, - string filter = "", - Guid? dataTypeId = null) + string filter = "") { //if a request is made for the root node data but the user's start node is not the default, then // we need to return their start nodes if (id == Constants.System.Root && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false) { - var ignoreUserStartNodes = dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); - if (ignoreUserStartNodes == false) + if (pageNumber > 0) + return new PagedResult>(0, 0, 0); + var nodes = Services.MediaService.GetByIds(UserStartNodes).ToArray(); + if (nodes.Length == 0) + return new PagedResult>(0, 0, 0); + if (pageSize < nodes.Length) pageSize = nodes.Length; // bah + var pr = new PagedResult>(nodes.Length, pageNumber, pageSize) { - if (pageNumber > 0) - return new PagedResult>(0, 0, 0); - var nodes = Services.MediaService.GetByIds(UserStartNodes).ToArray(); - if (nodes.Length == 0) - return new PagedResult>(0, 0, 0); - if (pageSize < nodes.Length) pageSize = nodes.Length; // bah - var pr = new PagedResult>(nodes.Length, pageNumber, pageSize) - { - Items = nodes.Select(Mapper.Map>) - }; - return pr; - } + Items = nodes.Select(Mapper.Map>) + }; + return pr; } // else proceed as usual @@ -317,7 +312,7 @@ namespace Umbraco.Web.Editors } /// - /// This method is obsolete, use the overload with dataTypeId instead + /// This method is obsolete, use the overload with ignoreUserStartNodes instead /// Returns the child media objects - using the entity GUID id /// /// @@ -328,7 +323,7 @@ namespace Umbraco.Web.Editors /// /// /// - [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(Guid id, int pageNumber = 0, @@ -338,7 +333,7 @@ namespace Umbraco.Web.Editors bool orderBySystemField = true, string filter = "") { - return GetChildren(id, null, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + return GetChildren(id, false, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); } /// @@ -351,11 +346,10 @@ namespace Umbraco.Web.Editors /// /// /// - /// If set used to lookup whether user and group start node permissions should be ignored. /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(Guid id, - Guid? dataTypeId, + bool ignoreUserStartNodes, int pageNumber = 0, int pageSize = 0, string orderBy = "SortOrder", @@ -366,13 +360,13 @@ namespace Umbraco.Web.Editors var entity = Services.EntityService.GetByKey(id); if (entity != null) { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, dataTypeId); + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); } throw new HttpResponseException(HttpStatusCode.NotFound); } /// - /// This method is obsolete, use the overload with dataTypeId instead + /// This method is obsolete, use the overload with ignoreUserStartNodes instead /// Returns the child media objects - using the entity UDI id /// /// @@ -383,7 +377,7 @@ namespace Umbraco.Web.Editors /// /// /// - [Obsolete("This method is obsolete, use the overload with dataTypeId instead", false)] + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(Udi id, int pageNumber = 0, @@ -393,7 +387,7 @@ namespace Umbraco.Web.Editors bool orderBySystemField = true, string filter = "") { - return GetChildren(id, null, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + return GetChildren(id, false, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); } /// @@ -406,11 +400,10 @@ namespace Umbraco.Web.Editors /// /// /// - /// If set used to lookup whether the user and group start node permissions will be ignored. /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(Udi id, - Guid? dataTypeId, + bool ignoreUserStartNodes, int pageNumber = 0, int pageSize = 0, string orderBy = "SortOrder", @@ -424,7 +417,7 @@ namespace Umbraco.Web.Editors var entity = Services.EntityService.GetByKey(guidUdi.Guid); if (entity != null) { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, dataTypeId); + return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); } } diff --git a/src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs b/src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs index a8cf2e26ee..86642a8d65 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs @@ -6,7 +6,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataContract(Name = "urlAndAnchors", Namespace = "")] public class UrlAndAnchors { - public UrlAndAnchors(string url, IList anchorValues) + public UrlAndAnchors(string url, IEnumerable anchorValues) { Url = url; AnchorValues = anchorValues; @@ -16,6 +16,6 @@ namespace Umbraco.Web.Models.ContentEditing public string Url { get; } [DataMember(Name = "anchorValues")] - public IList AnchorValues { get; } + public IEnumerable AnchorValues { get; } } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs index 02cf570f7d..dfff72a6f1 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs @@ -18,13 +18,15 @@ namespace Umbraco.Web.Models.Mapping internal class ContentPropertyBasicConverter : ITypeConverter where T : ContentPropertyBasic, new() { - protected IDataTypeService DataTypeService { get; private set; } + protected IDataTypeService DataTypeService { get; } + protected IEntityService EntityService { get; } private static readonly List ComplexPropertyTypeAliases = new List {"Umbraco.NestedContent"}; - public ContentPropertyBasicConverter(IDataTypeService dataTypeService) + public ContentPropertyBasicConverter(IDataTypeService dataTypeService, IEntityService entityService) { DataTypeService = dataTypeService; + EntityService = entityService; } /// @@ -48,12 +50,18 @@ namespace Umbraco.Web.Models.Mapping editor = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias); } - var dataTypeDefinition = DataTypeService.GetDataTypeDefinitionById(property.PropertyType.DataTypeDefinitionId); + var dataTypeDefinitionKey = EntityService.GetKeyForId(property.PropertyType.DataTypeDefinitionId, UmbracoObjectTypes.DataType); + + if (dataTypeDefinitionKey.Success == false) + { + throw new InvalidOperationException("Can't get the unique key from the id: " + property.PropertyType.DataTypeDefinitionId); + } + var result = new T { Id = property.Id, - DataTypeId = dataTypeDefinition.Key, + DataTypeId = dataTypeDefinitionKey.Result, Alias = property.Alias, PropertyEditor = editor, Editor = editor.Alias diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs index 86c79d9c59..9c9cad560c 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs @@ -1,67 +1,67 @@ -using System; -using AutoMapper; -using Umbraco.Core.Models; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Creates a ContentPropertyDisplay from a Property - /// - internal class ContentPropertyDisplayConverter : ContentPropertyBasicConverter - { - private readonly ILocalizedTextService _textService; - - public ContentPropertyDisplayConverter(IDataTypeService dataTypeService, ILocalizedTextService textService) - : base(dataTypeService) - { - _textService = textService; - } - public override ContentPropertyDisplay Convert(ResolutionContext context) - { - var display = base.Convert(context); - - var originalProperty = context.SourceValue as Property; - if (originalProperty == null) - throw new InvalidOperationException("Source value is not a property."); - - var dataTypeService = DataTypeService; - var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(originalProperty.PropertyType.DataTypeDefinitionId); - - //configure the editor for display with the pre-values - var valEditor = display.PropertyEditor.ValueEditor; - valEditor.ConfigureForDisplay(preVals); - - //set the display properties after mapping - display.Alias = originalProperty.Alias; - display.Description = originalProperty.PropertyType.Description; - display.Label = originalProperty.PropertyType.Name; - display.HideLabel = valEditor.HideLabel; - - //add the validation information - display.Validation.Mandatory = originalProperty.PropertyType.Mandatory; - display.Validation.Pattern = originalProperty.PropertyType.ValidationRegExp; - - if (display.PropertyEditor == null) - { - //display.Config = PreValueCollection.AsDictionary(preVals); - //if there is no property editor it means that it is a legacy data type - // we cannot support editing with that so we'll just render the readonly value view. - display.View = "views/propertyeditors/readonlyvalue/readonlyvalue.html"; - } - else - { - //let the property editor format the pre-values - display.Config = display.PropertyEditor.PreValueEditor.ConvertDbToEditor(display.PropertyEditor.DefaultPreValues, preVals); - display.View = valEditor.View; - } - - //Translate - display.Label = _textService.UmbracoDictionaryTranslate(display.Label); - display.Description = _textService.UmbracoDictionaryTranslate(display.Description); - - return display; - } - } -} +using System; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Creates a ContentPropertyDisplay from a Property + /// + internal class ContentPropertyDisplayConverter : ContentPropertyBasicConverter + { + private readonly ILocalizedTextService _textService; + + public ContentPropertyDisplayConverter(IDataTypeService dataTypeService, ILocalizedTextService textService, IEntityService entityService) + : base(dataTypeService, entityService) + { + _textService = textService; + } + public override ContentPropertyDisplay Convert(ResolutionContext context) + { + var display = base.Convert(context); + + var originalProperty = context.SourceValue as Property; + if (originalProperty == null) + throw new InvalidOperationException("Source value is not a property."); + + var dataTypeService = DataTypeService; + var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(originalProperty.PropertyType.DataTypeDefinitionId); + + //configure the editor for display with the pre-values + var valEditor = display.PropertyEditor.ValueEditor; + valEditor.ConfigureForDisplay(preVals); + + //set the display properties after mapping + display.Alias = originalProperty.Alias; + display.Description = originalProperty.PropertyType.Description; + display.Label = originalProperty.PropertyType.Name; + display.HideLabel = valEditor.HideLabel; + + //add the validation information + display.Validation.Mandatory = originalProperty.PropertyType.Mandatory; + display.Validation.Pattern = originalProperty.PropertyType.ValidationRegExp; + + if (display.PropertyEditor == null) + { + //display.Config = PreValueCollection.AsDictionary(preVals); + //if there is no property editor it means that it is a legacy data type + // we cannot support editing with that so we'll just render the readonly value view. + display.View = "views/propertyeditors/readonlyvalue/readonlyvalue.html"; + } + else + { + //let the property editor format the pre-values + display.Config = display.PropertyEditor.PreValueEditor.ConvertDbToEditor(display.PropertyEditor.DefaultPreValues, preVals); + display.View = valEditor.View; + } + + //Translate + display.Label = _textService.UmbracoDictionaryTranslate(display.Label); + display.Description = _textService.UmbracoDictionaryTranslate(display.Description); + + return display; + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs index 61fd4fbe00..03b2fc7bcd 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs @@ -1,41 +1,41 @@ -using System; -using AutoMapper; -using Umbraco.Core.Models; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Creates a ContentPropertyDto from a Property - /// - internal class ContentPropertyDtoConverter : ContentPropertyBasicConverter - { - public ContentPropertyDtoConverter(IDataTypeService dataTypeService) - : base(dataTypeService) - { - } - - public override ContentPropertyDto Convert(ResolutionContext context) - { - var propertyDto = base.Convert(context); - - var originalProperty = context.SourceValue as Property; - if (originalProperty == null) - throw new InvalidOperationException("Source value is not a property."); - - var dataTypeService = DataTypeService; - - propertyDto.IsRequired = originalProperty.PropertyType.Mandatory; - propertyDto.ValidationRegExp = originalProperty.PropertyType.ValidationRegExp; - propertyDto.Description = originalProperty.PropertyType.Description; - propertyDto.Label = originalProperty.PropertyType.Name; - - //TODO: We should be able to look both of these up at the same time! - propertyDto.DataType = dataTypeService.GetDataTypeDefinitionById(originalProperty.PropertyType.DataTypeDefinitionId); - propertyDto.PreValues = dataTypeService.GetPreValuesCollectionByDataTypeId(originalProperty.PropertyType.DataTypeDefinitionId); - - return propertyDto; - } - } -} +using System; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Creates a ContentPropertyDto from a Property + /// + internal class ContentPropertyDtoConverter : ContentPropertyBasicConverter + { + public ContentPropertyDtoConverter(IDataTypeService dataTypeService, IEntityService entityService) + : base(dataTypeService, entityService) + { + } + + public override ContentPropertyDto Convert(ResolutionContext context) + { + var propertyDto = base.Convert(context); + + var originalProperty = context.SourceValue as Property; + if (originalProperty == null) + throw new InvalidOperationException("Source value is not a property."); + + var dataTypeService = DataTypeService; + + propertyDto.IsRequired = originalProperty.PropertyType.Mandatory; + propertyDto.ValidationRegExp = originalProperty.PropertyType.ValidationRegExp; + propertyDto.Description = originalProperty.PropertyType.Description; + propertyDto.Label = originalProperty.PropertyType.Name; + + //TODO: We should be able to look both of these up at the same time! + propertyDto.DataType = dataTypeService.GetDataTypeDefinitionById(originalProperty.PropertyType.DataTypeDefinitionId); + propertyDto.PreValues = dataTypeService.GetPreValuesCollectionByDataTypeId(originalProperty.PropertyType.DataTypeDefinitionId); + + return propertyDto; + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs index f401832020..fd9d3d19f8 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs @@ -1,37 +1,37 @@ -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Mapping; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// A mapper which declares how to map content properties. These mappings are shared among media (and probably members) which is - /// why they are in their own mapper - /// - internal class ContentPropertyModelMapper : MapperConfiguration - { - public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) - { - //FROM Property TO ContentPropertyBasic - config.CreateMap>() - .ForMember(tab => tab.Label, expression => expression.MapFrom(@group => @group.Name)) - .ForMember(tab => tab.IsActive, expression => expression.UseValue(true)) - .ForMember(tab => tab.Properties, expression => expression.Ignore()) - .ForMember(tab => tab.Alias, expression => expression.Ignore()); - - //FROM Property TO ContentPropertyBasic - config.CreateMap() - .ConvertUsing(new ContentPropertyBasicConverter(applicationContext.Services.DataTypeService)); - - //FROM Property TO ContentPropertyDto - config.CreateMap() - .ConvertUsing(new ContentPropertyDtoConverter(applicationContext.Services.DataTypeService)); - - //FROM Property TO ContentPropertyDisplay - config.CreateMap() - .ConvertUsing(new ContentPropertyDisplayConverter(applicationContext.Services.DataTypeService, applicationContext.Services.TextService)); - } - } -} +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Mapping; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// A mapper which declares how to map content properties. These mappings are shared among media (and probably members) which is + /// why they are in their own mapper + /// + internal class ContentPropertyModelMapper : MapperConfiguration + { + public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) + { + //FROM Property TO ContentPropertyBasic + config.CreateMap>() + .ForMember(tab => tab.Label, expression => expression.MapFrom(@group => @group.Name)) + .ForMember(tab => tab.IsActive, expression => expression.UseValue(true)) + .ForMember(tab => tab.Properties, expression => expression.Ignore()) + .ForMember(tab => tab.Alias, expression => expression.Ignore()); + + //FROM Property TO ContentPropertyBasic + config.CreateMap() + .ConvertUsing(new ContentPropertyBasicConverter(applicationContext.Services.DataTypeService, applicationContext.Services.EntityService)); + + //FROM Property TO ContentPropertyDto + config.CreateMap() + .ConvertUsing(new ContentPropertyDtoConverter(applicationContext.Services.DataTypeService, applicationContext.Services.EntityService)); + + //FROM Property TO ContentPropertyDisplay + config.CreateMap() + .ConvertUsing(new ContentPropertyDisplayConverter(applicationContext.Services.DataTypeService, applicationContext.Services.TextService, applicationContext.Services.EntityService)); + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs b/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs index af6f99a15d..76e841d65a 100644 --- a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs @@ -67,8 +67,8 @@ namespace Umbraco.Web.Models.Mapping // If we have a prop editor, then format the pre-values based on it and create it's fields if (propEd != null) { - result = propEd.PreValueEditor.Fields.Select(Mapper.Map).AsEnumerable(); - if (source.IsBuildInDataType) + result = propEd.PreValueEditor.Fields.Select(Mapper.Map); + if (source.IsBuildInDataType()) { result = RemovePreValuesNotSupportedOnBuildInTypes(result); } diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index 8845e9c323..9cfa29985b 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -15,31 +15,6 @@ namespace Umbraco.Web.Search { internal class UmbracoTreeSearcher { - /// - /// This method is obsolete, use the overload with ignoreUserStartNodes instead - /// Searches for results based on the entity type - /// - /// - /// - /// - /// - /// - /// A starting point for the search, generally a node id, but for members this is a member type alias - /// - /// - /// - /// - [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] - public IEnumerable ExamineSearch( - UmbracoHelper umbracoHelper, - string query, - UmbracoEntityTypes entityType, - int pageSize, - long pageIndex, out long totalFound, string searchFrom = null) - { - return ExamineSearch(umbracoHelper, query, entityType, pageSize, pageIndex, out totalFound, false, searchFrom); - } - /// /// Searches for results based on the entity type /// @@ -59,7 +34,7 @@ namespace Umbraco.Web.Search string query, UmbracoEntityTypes entityType, int pageSize, - long pageIndex, out long totalFound, bool ignoreUserStartNodes, string searchFrom = null) + long pageIndex, out long totalFound, string searchFrom = null, bool ignoreUserStartNodes = false) { var sb = new StringBuilder(); diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index f2f83d1133..995cb031d0 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -10,6 +10,7 @@ using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Services; using Umbraco.Web.Search; namespace Umbraco.Web.Trees diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs index dfdea268c4..13bceb318f 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -73,24 +73,6 @@ namespace Umbraco.Web.WebApi.Filters throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized); } - var ignoreUserStartNodes = false; - - if (actionContext.ActionArguments.ContainsKey("dataTypeId")) - { - if (actionContext.ActionArguments.TryGetValue("dataTypeId", out var dataTypeIdValue)) - { - var dataTypeIdString = dataTypeIdValue?.ToString(); - if (string.IsNullOrEmpty(dataTypeIdString) == false && - Guid.TryParse(dataTypeIdString, out var dataTypeId)) - { - ignoreUserStartNodes = - ApplicationContext.Current.Services.DataTypeService - .IsDataTypeIgnoringUserStartNodes(dataTypeId); - } - } - - } - int nodeId; if (_nodeId.HasValue == false) { @@ -144,9 +126,7 @@ namespace Umbraco.Web.WebApi.Filters ApplicationContext.Current.Services.UserService, ApplicationContext.Current.Services.ContentService, ApplicationContext.Current.Services.EntityService, - nodeId, - _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null, - ignoreUserStartNodes: ignoreUserStartNodes)) + nodeId, _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null)) { base.OnActionExecuting(actionContext); } diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index 22ce7a5ce7..33c99cd60a 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -3,12 +3,11 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net.Http; -using System.Web; using System.Web.Http.Filters; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; -using Umbraco.Web.Trees; +using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.WebApi.Filters { @@ -78,20 +77,9 @@ namespace Umbraco.Web.WebApi.Filters protected virtual void FilterItems(IUser user, IList items) { - - Guid? dataTypeId = Guid.TryParse(HttpContext.Current.Request.QueryString.Get(TreeQueryStringParameters.DataTypeId), out var temp) ? (Guid?)temp : null; - - if (dataTypeId.HasValue == false) return; - - var ignoreUserStartNodes = ApplicationContext.Current.Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); - - if (ignoreUserStartNodes == false) - { - FilterBasedOnStartNode(items, user); - } + FilterBasedOnStartNode(items, user); } - internal void FilterBasedOnStartNode(IList items, IUser user) { var toRemove = new List(); From 9f45d41a4a6b9e88a89b31b60a7b695943022434 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 13:18:15 +0200 Subject: [PATCH 034/776] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1479 - Test fixes --- .../src/common/resources/entity.resource.js | 6 ++++-- .../grid/editors/rte.controller.js | 2 +- src/Umbraco.Web/Editors/EntityController.cs | 15 +++++++++------ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 416fd0a286..dce2cd8347 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -187,8 +187,10 @@ function entityResource($q, $http, umbRequestHelper) { $http.post( umbRequestHelper.getApiUrl( "entityApiBaseUrl", - "GetAnchors", - { rteContent: rteContent })), + 'GetAnchors'), + { + rteContent: rteContent + }), 'Failed to anchors data for rte content ' + rteContent); }, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js index 601c0a36d2..da869dbf01 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js @@ -12,7 +12,7 @@ function openLinkPicker(editor, currentTarget, anchorElement) { - entityResource.getAnchors($scope.model.value).then(function(anchorValues) { + entityResource.getAnchors(JSON.stringify($scope.model.value)).then(function(anchorValues) { vm.linkPickerOverlay = { view: "linkpicker", currentTarget: currentTarget, diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 6c9cc7c36b..ebdfe59210 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -276,20 +276,23 @@ namespace Umbraco.Web.Editors [HttpGet] - public UrlAndAnchors GetUrlAndAnchors(int id) + public UrlAndAnchors GetUrlAndAnchors([FromUri]int id) { - var x = GetResultForId(id, UmbracoEntityTypes.Document); - var url = Umbraco.Url(id); var anchorValues = Services.ContentService.GetAnchorValuesFromRTEs(id); return new UrlAndAnchors(url, anchorValues); } - [HttpPost] - public IEnumerable GetAnchors(string rteContent) + public class AnchorsModel { + public string RteContent { get; set; } + } - var anchorValues = Services.ContentService.GetAnchorValuesFromRTEContent(rteContent); + [HttpGet] + [HttpPost] + public IEnumerable GetAnchors(AnchorsModel model) + { + var anchorValues = Services.ContentService.GetAnchorValuesFromRTEContent(model.RteContent); return anchorValues; } From 2f309a2fd625ffa4ddc07f51f2516466dd0e3822 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Jun 2019 15:09:37 +0200 Subject: [PATCH 035/776] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1085 - moved the code that adds the ufprt token to the context items, into the constructor of UmbracoForm. Because it needs to happen before the @Html.AntiForgeryToken() is called. The dispose method is too late. --- src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 2017 +++++++++-------- 1 file changed, 1009 insertions(+), 1008 deletions(-) diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index a64c00a03c..0e2a2a6450 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -1,1008 +1,1009 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Web; -using System.Web.Helpers; -using System.Web.Mvc; -using System.Web.Mvc.Html; -using System.Web.Routing; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Dynamics; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Core.Profiling; -using Umbraco.Web.Models; -using Umbraco.Web.Mvc; -using Constants = Umbraco.Core.Constants; -using Member = umbraco.cms.businesslogic.member.Member; - -namespace Umbraco.Web -{ - /// - /// HtmlHelper extensions for use in templates - /// - public static class HtmlHelperRenderExtensions - { - /// - /// Renders the markup for the profiler - /// - /// - /// - public static IHtmlString RenderProfiler(this HtmlHelper helper) - { - return new HtmlString(ProfilerResolver.Current.Profiler.Render()); - } - - /// - /// Renders a partial view that is found in the specified area - /// - /// - /// - /// - /// - /// - /// - public static MvcHtmlString AreaPartial(this HtmlHelper helper, string partial, string area, object model = null, ViewDataDictionary viewData = null) - { - var originalArea = helper.ViewContext.RouteData.DataTokens["area"]; - helper.ViewContext.RouteData.DataTokens["area"] = area; - var result = helper.Partial(partial, model, viewData); - helper.ViewContext.RouteData.DataTokens["area"] = originalArea; - return result; - } - - /// - /// Will render the preview badge when in preview mode which is not required ever unless the MVC page you are - /// using does not inherit from UmbracoTemplatePage - /// - /// - /// - /// - /// See: http://issues.umbraco.org/issue/U4-1614 - /// - public static MvcHtmlString PreviewBadge(this HtmlHelper helper) - { - if (UmbracoContext.Current.InPreviewMode) - { - var htmlBadge = - String.Format(UmbracoConfig.For.UmbracoSettings().Content.PreviewBadge, - IOHelper.ResolveUrl(SystemDirectories.Umbraco), - IOHelper.ResolveUrl(SystemDirectories.UmbracoClient), - UmbracoContext.Current.HttpContext.Server.UrlEncode(UmbracoContext.Current.HttpContext.Request.Path)); - return new MvcHtmlString(htmlBadge); - } - return new MvcHtmlString(""); - - } - - public static IHtmlString CachedPartial( - this HtmlHelper htmlHelper, - string partialViewName, - object model, - int cachedSeconds, - bool cacheByPage = false, - bool cacheByMember = false, - ViewDataDictionary viewData = null, - Func contextualKeyBuilder = null) - { - var cacheKey = new StringBuilder(partialViewName); - if (cacheByPage) - { - if (UmbracoContext.Current == null) - { - throw new InvalidOperationException("Cannot cache by page if the UmbracoContext has not been initialized, this parameter can only be used in the context of an Umbraco request"); - } - cacheKey.AppendFormat("{0}-", UmbracoContext.Current.PageId); - } - if (cacheByMember) - { - var currentMember = Member.GetCurrentMember(); - cacheKey.AppendFormat("m{0}-", currentMember == null ? 0 : currentMember.Id); - } - if (contextualKeyBuilder != null) - { - var contextualKey = contextualKeyBuilder(model, viewData); - cacheKey.AppendFormat("c{0}-", contextualKey); - } - return ApplicationContext.Current.ApplicationCache.CachedPartialView(htmlHelper, partialViewName, model, cachedSeconds, cacheKey.ToString(), viewData); - } - - public static MvcHtmlString EditorFor(this HtmlHelper htmlHelper, string templateName = "", string htmlFieldName = "", object additionalViewData = null) - where T : new() - { - var model = new T(); - var typedHelper = new HtmlHelper( - htmlHelper.ViewContext.CopyWithModel(model), - htmlHelper.ViewDataContainer.CopyWithModel(model)); - - return typedHelper.EditorFor(x => model, templateName, htmlFieldName, additionalViewData); - } - - /// - /// A validation summary that lets you pass in a prefix so that the summary only displays for elements - /// containing the prefix. This allows you to have more than on validation summary on a page. - /// - /// - /// - /// - /// - /// - /// - public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, - string prefix = "", - bool excludePropertyErrors = false, - string message = "", - IDictionary htmlAttributes = null) - { - if (prefix.IsNullOrWhiteSpace()) - { - return htmlHelper.ValidationSummary(excludePropertyErrors, message, htmlAttributes); - } - - //if there's a prefix applied, we need to create a new html helper with a filtered ModelState collection so that it only looks for - //specific model state with the prefix. - var filteredHtmlHelper = new HtmlHelper(htmlHelper.ViewContext, htmlHelper.ViewDataContainer.FilterContainer(prefix)); - return filteredHtmlHelper.ValidationSummary(excludePropertyErrors, message, htmlAttributes); - } - - /// - /// Returns the result of a child action of a strongly typed SurfaceController - /// - /// - /// - /// - /// - public static IHtmlString Action(this HtmlHelper htmlHelper, string actionName) - where T : SurfaceController - { - return htmlHelper.Action(actionName, typeof(T)); - } - - /// - /// Returns the result of a child action of a SurfaceController - /// - /// - /// - /// - /// - /// - public static IHtmlString Action(this HtmlHelper htmlHelper, string actionName, Type surfaceType) - { - Mandate.ParameterNotNull(surfaceType, "surfaceType"); - Mandate.ParameterNotNullOrEmpty(actionName, "actionName"); - - var routeVals = new RouteValueDictionary(new {area = ""}); - - var surfaceController = SurfaceControllerResolver.Current.RegisteredSurfaceControllers - .SingleOrDefault(x => x == surfaceType); - if (surfaceController == null) - throw new InvalidOperationException("Could not find the surface controller of type " + surfaceType.FullName); - var metaData = PluginController.GetMetadata(surfaceController); - if (!metaData.AreaName.IsNullOrWhiteSpace()) - { - //set the area to the plugin area - if (routeVals.ContainsKey("area")) - { - routeVals["area"] = metaData.AreaName; - } - else - { - routeVals.Add("area", metaData.AreaName); - } - } - - return htmlHelper.Action(actionName, metaData.ControllerName, routeVals); - } - - #region GetCropUrl - - [Obsolete("Use the UrlHelper.GetCropUrl extension instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, IPublishedContent mediaItem, string cropAlias) - { - return new HtmlString(mediaItem.GetCropUrl(cropAlias: cropAlias, useCropDimensions: true)); - } - - [Obsolete("Use the UrlHelper.GetCropUrl extension instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, IPublishedContent mediaItem, string propertyAlias, string cropAlias) - { - return new HtmlString(mediaItem.GetCropUrl(propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true)); - } - - [Obsolete("Use the UrlHelper.GetCropUrl extension instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, - IPublishedContent mediaItem, - int? width = null, - int? height = null, - string propertyAlias = Constants.Conventions.Media.File, - string cropAlias = null, - int? quality = null, - ImageCropMode? imageCropMode = null, - ImageCropAnchor? imageCropAnchor = null, - bool preferFocalPoint = false, - bool useCropDimensions = false, - bool cacheBuster = true, - string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true) - { - return - new HtmlString(mediaItem.GetCropUrl(width, height, propertyAlias, cropAlias, quality, imageCropMode, - imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode, - upScale)); - } - - [Obsolete("Use the UrlHelper.GetCropUrl extension instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, - string imageUrl, - int? width = null, - int? height = null, - string imageCropperValue = null, - string cropAlias = null, - int? quality = null, - ImageCropMode? imageCropMode = null, - ImageCropAnchor? imageCropAnchor = null, - bool preferFocalPoint = false, - bool useCropDimensions = false, - string cacheBusterValue = null, - string furtherOptions = null, - ImageCropRatioMode? ratioMode = null, - bool upScale = true) - { - return - new HtmlString(imageUrl.GetCropUrl(width, height, imageCropperValue, cropAlias, quality, imageCropMode, - imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, - upScale)); - } - - #endregion - - #region BeginUmbracoForm - - /// - /// Used for rendering out the Form for BeginUmbracoForm - /// - internal class UmbracoForm : MvcForm - { - /// - /// Creates an UmbracoForm - /// - /// - /// - /// - /// - /// - /// - public UmbracoForm( - ViewContext viewContext, - string controllerName, - string controllerAction, - string area, - FormMethod method, - object additionalRouteVals = null) - : base(viewContext) - { - _viewContext = viewContext; - _method = method; - _controllerName = controllerName; - _encryptedString = UmbracoHelper.CreateEncryptedRouteString(controllerName, controllerAction, area, additionalRouteVals); - } - - private readonly ViewContext _viewContext; - private readonly FormMethod _method; - private bool _disposed; - private readonly string _encryptedString; - private readonly string _controllerName; - - protected override void Dispose(bool disposing) - { - if (this._disposed) - return; - this._disposed = true; - - //For UmbracoForm's we want to add our routing string to the httpcontext items in the case where anti-forgery tokens are used. - //In which case our custom UmbracoAntiForgeryAdditionalDataProvider will kick in and validate the values in the request against - //the values that will be appended to the token. This essentially means that when anti-forgery tokens are used with UmbracoForm's forms, - //that each token is unique to the controller/action/area instead of the default ASP.Net implementation which is that the token is unique - //per user. - _viewContext.HttpContext.Items["ufprt"] = _encryptedString; - - //Detect if the call is targeting UmbRegisterController/UmbProfileController/UmbLoginStatusController/UmbLoginController and if it is we automatically output a AntiForgeryToken() - // We have a controllerName and area so we can match - if (_controllerName == "UmbRegister" - || _controllerName == "UmbProfile" - || _controllerName == "UmbLoginStatus" - || _controllerName == "UmbLogin") - { - _viewContext.Writer.Write(AntiForgery.GetHtml().ToString()); - } - - //write out the hidden surface form routes - _viewContext.Writer.Write(""); - - base.Dispose(disposing); - } - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, FormMethod method) - { - return html.BeginUmbracoForm(action, controllerName, null, new Dictionary(), method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName) - { - return html.BeginUmbracoForm(action, controllerName, null, new Dictionary()); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, object additionalRouteVals, FormMethod method) - { - return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary(), method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, object additionalRouteVals) - { - return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary()); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, - object additionalRouteVals, - object htmlAttributes, - FormMethod method) - { - return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes), method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, - object additionalRouteVals, - object htmlAttributes) - { - return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, - object additionalRouteVals, - IDictionary htmlAttributes, - FormMethod method) - { - Mandate.ParameterNotNullOrEmpty(action, "action"); - Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); - - return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes, method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, - object additionalRouteVals, - IDictionary htmlAttributes) - { - Mandate.ParameterNotNullOrEmpty(action, "action"); - Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); - - return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, FormMethod method) - { - return html.BeginUmbracoForm(action, surfaceType, null, new Dictionary(), method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType) - { - return html.BeginUmbracoForm(action, surfaceType, null, new Dictionary()); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, FormMethod method) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T), method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T)); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, - object additionalRouteVals, FormMethod method) - { - return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary(), method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, - object additionalRouteVals) - { - return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary()); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, object additionalRouteVals, FormMethod method) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, object additionalRouteVals) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, - object additionalRouteVals, - object htmlAttributes, - FormMethod method) - { - return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes), method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, - object additionalRouteVals, - object htmlAttributes) - { - return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, - object additionalRouteVals, - object htmlAttributes, - FormMethod method) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes, method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, - object additionalRouteVals, - object htmlAttributes) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, - object additionalRouteVals, - IDictionary htmlAttributes, - FormMethod method) - { - Mandate.ParameterNotNullOrEmpty(action, "action"); - Mandate.ParameterNotNull(surfaceType, "surfaceType"); - - var area = ""; - - var surfaceController = SurfaceControllerResolver.Current.RegisteredSurfaceControllers - .SingleOrDefault(x => x == surfaceType); - if (surfaceController == null) - throw new InvalidOperationException("Could not find the surface controller of type " + surfaceType.FullName); - var metaData = PluginController.GetMetadata(surfaceController); - if (metaData.AreaName.IsNullOrWhiteSpace() == false) - { - //set the area to the plugin area - area = metaData.AreaName; - } - return html.BeginUmbracoForm(action, metaData.ControllerName, area, additionalRouteVals, htmlAttributes, method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// The surface controller to route to - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, - object additionalRouteVals, - IDictionary htmlAttributes) - { - return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, htmlAttributes, FormMethod.Post); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, - object additionalRouteVals, - IDictionary htmlAttributes, - FormMethod method) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes, method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, - object additionalRouteVals, - IDictionary htmlAttributes) - where T : SurfaceController - { - return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, FormMethod method) - { - return html.BeginUmbracoForm(action, controllerName, area, null, new Dictionary(), method); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area) - { - return html.BeginUmbracoForm(action, controllerName, area, null, new Dictionary()); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, - object additionalRouteVals, - IDictionary htmlAttributes, - FormMethod method) - { - Mandate.ParameterNotNullOrEmpty(action, "action"); - Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); - - var formAction = UmbracoContext.Current.OriginalRequestUrl.PathAndQuery; - return html.RenderForm(formAction, method, htmlAttributes, controllerName, action, area, additionalRouteVals); - } - - /// - /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin - /// - /// - /// - /// - /// - /// - /// - /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, - object additionalRouteVals, - IDictionary htmlAttributes) - { - return html.BeginUmbracoForm(action, controllerName, area, additionalRouteVals, htmlAttributes, FormMethod.Post); - } - - /// - /// This renders out the form for us - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// This code is pretty much the same as the underlying MVC code that writes out the form - /// - private static MvcForm RenderForm(this HtmlHelper htmlHelper, - string formAction, - FormMethod method, - IDictionary htmlAttributes, - string surfaceController, - string surfaceAction, - string area, - object additionalRouteVals = null) - { - - //ensure that the multipart/form-data is added to the html attributes - if (htmlAttributes.ContainsKey("enctype") == false) - { - htmlAttributes.Add("enctype", "multipart/form-data"); - } - - var tagBuilder = new TagBuilder("form"); - tagBuilder.MergeAttributes(htmlAttributes); - // action is implicitly generated, so htmlAttributes take precedence. - tagBuilder.MergeAttribute("action", formAction); - // method is an explicit parameter, so it takes precedence over the htmlAttributes. - tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true); - var traditionalJavascriptEnabled = htmlHelper.ViewContext.ClientValidationEnabled && htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled == false; - if (traditionalJavascriptEnabled) - { - // forms must have an ID for client validation - tagBuilder.GenerateId("form" + Guid.NewGuid().ToString("N")); - } - htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag)); - - //new UmbracoForm: - var theForm = new UmbracoForm(htmlHelper.ViewContext, surfaceController, surfaceAction, area, method, additionalRouteVals); - - if (traditionalJavascriptEnabled) - { - htmlHelper.ViewContext.FormContext.FormId = tagBuilder.Attributes["id"]; - } - return theForm; - } - - #endregion - - #region Wrap - - public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, string innerText, params IHtmlTagWrapper[] children) - { - var item = html.Wrap(tag, innerText, (object)null); - foreach (var child in children) - { - item.AddChild(child); - } - return item; - } - - public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, object inner, object anonymousAttributes, params IHtmlTagWrapper[] children) - { - string innerText = null; - if (inner != null && inner.GetType() != typeof(DynamicNull)) - { - innerText = string.Format("{0}", inner); - } - var item = html.Wrap(tag, innerText, anonymousAttributes); - foreach (var child in children) - { - item.AddChild(child); - } - return item; - } - public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, object inner) - { - string innerText = null; - if (inner != null && inner.GetType() != typeof(DynamicNull)) - { - innerText = string.Format("{0}", inner); - } - return html.Wrap(tag, innerText, (object)null); - } - - public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, string innerText, object anonymousAttributes, params IHtmlTagWrapper[] children) - { - var wrap = new HtmlTagWrapper(tag); - if (anonymousAttributes != null) - { - wrap.ReflectAttributesFromAnonymousType(anonymousAttributes); - } - if (!string.IsNullOrWhiteSpace(innerText)) - { - wrap.AddChild(new HtmlTagWrapperTextNode(innerText)); - } - foreach (var child in children) - { - wrap.AddChild(child); - } - return wrap; - } - - public static HtmlTagWrapper Wrap(this HtmlHelper html, bool visible, string tag, string innerText, object anonymousAttributes, params IHtmlTagWrapper[] children) - { - var item = html.Wrap(tag, innerText, anonymousAttributes, children); - item.Visible = visible; - return item; - } - - #endregion - - #region canvasdesigner - - public static IHtmlString EnableCanvasDesigner(this HtmlHelper html, - UrlHelper url, - UmbracoContext umbCtx) - { - return html.EnableCanvasDesigner(url, umbCtx, string.Empty, string.Empty); - } - - public static IHtmlString EnableCanvasDesigner(this HtmlHelper html, - UrlHelper url, - UmbracoContext umbCtx, string canvasdesignerConfigPath) - { - return html.EnableCanvasDesigner(url, umbCtx, canvasdesignerConfigPath, string.Empty); - } - - public static IHtmlString EnableCanvasDesigner(this HtmlHelper html, - UrlHelper url, - UmbracoContext umbCtx, string canvasdesignerConfigPath, string canvasdesignerPalettesPath) - { - - var umbracoPath = url.Content(SystemDirectories.Umbraco); - - string previewLink = @"" + - @"" + - @"" + - @"" + - @""; - - string noPreviewLinks = @""; - - // Get page value - int pageId = umbCtx.PublishedContentRequest.UmbracoPage.PageID; - string[] path = umbCtx.PublishedContentRequest.UmbracoPage.SplitPath; - string result = string.Empty; - string cssPath = CanvasDesignerUtility.GetStylesheetPath(path, false); - - if (umbCtx.InPreviewMode) - { - canvasdesignerConfigPath = string.IsNullOrEmpty(canvasdesignerConfigPath) == false - ? canvasdesignerConfigPath - : string.Format("{0}/js/canvasdesigner.config.js", umbracoPath); - canvasdesignerPalettesPath = string.IsNullOrEmpty(canvasdesignerPalettesPath) == false - ? canvasdesignerPalettesPath - : string.Format("{0}/js/canvasdesigner.palettes.js", umbracoPath); - - if (string.IsNullOrEmpty(cssPath) == false) - result = string.Format(noPreviewLinks, cssPath) + Environment.NewLine; - - result = result + string.Format(previewLink, umbracoPath, canvasdesignerConfigPath, canvasdesignerPalettesPath, pageId); - } - else - { - // Get css path for current page - if (string.IsNullOrEmpty(cssPath) == false) - result = string.Format(noPreviewLinks, cssPath); - } - - return new HtmlString(result); - - } - - #endregion - - #region RelatedLink - - /// - /// Renders an anchor element for a RelatedLink instance. - /// Format: <a href="relatedLink.Link" target="_blank/_self">relatedLink.Caption</a> - /// - /// The HTML helper instance that this method extends. - /// The RelatedLink instance - /// An anchor element - public static MvcHtmlString GetRelatedLinkHtml(this HtmlHelper htmlHelper, RelatedLink relatedLink) - { - return htmlHelper.GetRelatedLinkHtml(relatedLink, null); - } - - /// - /// Renders an anchor element for a RelatedLink instance, accepting htmlAttributes. - /// Format: <a href="relatedLink.Link" target="_blank/_self" htmlAttributes>relatedLink.Caption</a> - /// - /// The HTML helper instance that this method extends. - /// The RelatedLink instance - /// An object that contains the HTML attributes to set for the element. - /// - public static MvcHtmlString GetRelatedLinkHtml(this HtmlHelper htmlHelper, RelatedLink relatedLink, object htmlAttributes) - { - var tagBuilder = new TagBuilder("a"); - tagBuilder.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); - tagBuilder.MergeAttribute("href", relatedLink.Link); - tagBuilder.MergeAttribute("target", relatedLink.NewWindow ? "_blank" : "_self"); - tagBuilder.InnerHtml = HttpUtility.HtmlEncode(relatedLink.Caption); - return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.Normal)); - } - #endregion - } -} +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Web; +using System.Web.Helpers; +using System.Web.Mvc; +using System.Web.Mvc.Html; +using System.Web.Routing; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Dynamics; +using Umbraco.Core.IO; +using Umbraco.Core.Models; +using Umbraco.Core.Profiling; +using Umbraco.Web.Models; +using Umbraco.Web.Mvc; +using Constants = Umbraco.Core.Constants; +using Member = umbraco.cms.businesslogic.member.Member; + +namespace Umbraco.Web +{ + /// + /// HtmlHelper extensions for use in templates + /// + public static class HtmlHelperRenderExtensions + { + /// + /// Renders the markup for the profiler + /// + /// + /// + public static IHtmlString RenderProfiler(this HtmlHelper helper) + { + return new HtmlString(ProfilerResolver.Current.Profiler.Render()); + } + + /// + /// Renders a partial view that is found in the specified area + /// + /// + /// + /// + /// + /// + /// + public static MvcHtmlString AreaPartial(this HtmlHelper helper, string partial, string area, object model = null, ViewDataDictionary viewData = null) + { + var originalArea = helper.ViewContext.RouteData.DataTokens["area"]; + helper.ViewContext.RouteData.DataTokens["area"] = area; + var result = helper.Partial(partial, model, viewData); + helper.ViewContext.RouteData.DataTokens["area"] = originalArea; + return result; + } + + /// + /// Will render the preview badge when in preview mode which is not required ever unless the MVC page you are + /// using does not inherit from UmbracoTemplatePage + /// + /// + /// + /// + /// See: http://issues.umbraco.org/issue/U4-1614 + /// + public static MvcHtmlString PreviewBadge(this HtmlHelper helper) + { + if (UmbracoContext.Current.InPreviewMode) + { + var htmlBadge = + String.Format(UmbracoConfig.For.UmbracoSettings().Content.PreviewBadge, + IOHelper.ResolveUrl(SystemDirectories.Umbraco), + IOHelper.ResolveUrl(SystemDirectories.UmbracoClient), + UmbracoContext.Current.HttpContext.Server.UrlEncode(UmbracoContext.Current.HttpContext.Request.Path)); + return new MvcHtmlString(htmlBadge); + } + return new MvcHtmlString(""); + + } + + public static IHtmlString CachedPartial( + this HtmlHelper htmlHelper, + string partialViewName, + object model, + int cachedSeconds, + bool cacheByPage = false, + bool cacheByMember = false, + ViewDataDictionary viewData = null, + Func contextualKeyBuilder = null) + { + var cacheKey = new StringBuilder(partialViewName); + if (cacheByPage) + { + if (UmbracoContext.Current == null) + { + throw new InvalidOperationException("Cannot cache by page if the UmbracoContext has not been initialized, this parameter can only be used in the context of an Umbraco request"); + } + cacheKey.AppendFormat("{0}-", UmbracoContext.Current.PageId); + } + if (cacheByMember) + { + var currentMember = Member.GetCurrentMember(); + cacheKey.AppendFormat("m{0}-", currentMember == null ? 0 : currentMember.Id); + } + if (contextualKeyBuilder != null) + { + var contextualKey = contextualKeyBuilder(model, viewData); + cacheKey.AppendFormat("c{0}-", contextualKey); + } + return ApplicationContext.Current.ApplicationCache.CachedPartialView(htmlHelper, partialViewName, model, cachedSeconds, cacheKey.ToString(), viewData); + } + + public static MvcHtmlString EditorFor(this HtmlHelper htmlHelper, string templateName = "", string htmlFieldName = "", object additionalViewData = null) + where T : new() + { + var model = new T(); + var typedHelper = new HtmlHelper( + htmlHelper.ViewContext.CopyWithModel(model), + htmlHelper.ViewDataContainer.CopyWithModel(model)); + + return typedHelper.EditorFor(x => model, templateName, htmlFieldName, additionalViewData); + } + + /// + /// A validation summary that lets you pass in a prefix so that the summary only displays for elements + /// containing the prefix. This allows you to have more than on validation summary on a page. + /// + /// + /// + /// + /// + /// + /// + public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, + string prefix = "", + bool excludePropertyErrors = false, + string message = "", + IDictionary htmlAttributes = null) + { + if (prefix.IsNullOrWhiteSpace()) + { + return htmlHelper.ValidationSummary(excludePropertyErrors, message, htmlAttributes); + } + + //if there's a prefix applied, we need to create a new html helper with a filtered ModelState collection so that it only looks for + //specific model state with the prefix. + var filteredHtmlHelper = new HtmlHelper(htmlHelper.ViewContext, htmlHelper.ViewDataContainer.FilterContainer(prefix)); + return filteredHtmlHelper.ValidationSummary(excludePropertyErrors, message, htmlAttributes); + } + + /// + /// Returns the result of a child action of a strongly typed SurfaceController + /// + /// + /// + /// + /// + public static IHtmlString Action(this HtmlHelper htmlHelper, string actionName) + where T : SurfaceController + { + return htmlHelper.Action(actionName, typeof(T)); + } + + /// + /// Returns the result of a child action of a SurfaceController + /// + /// + /// + /// + /// + /// + public static IHtmlString Action(this HtmlHelper htmlHelper, string actionName, Type surfaceType) + { + Mandate.ParameterNotNull(surfaceType, "surfaceType"); + Mandate.ParameterNotNullOrEmpty(actionName, "actionName"); + + var routeVals = new RouteValueDictionary(new {area = ""}); + + var surfaceController = SurfaceControllerResolver.Current.RegisteredSurfaceControllers + .SingleOrDefault(x => x == surfaceType); + if (surfaceController == null) + throw new InvalidOperationException("Could not find the surface controller of type " + surfaceType.FullName); + var metaData = PluginController.GetMetadata(surfaceController); + if (!metaData.AreaName.IsNullOrWhiteSpace()) + { + //set the area to the plugin area + if (routeVals.ContainsKey("area")) + { + routeVals["area"] = metaData.AreaName; + } + else + { + routeVals.Add("area", metaData.AreaName); + } + } + + return htmlHelper.Action(actionName, metaData.ControllerName, routeVals); + } + + #region GetCropUrl + + [Obsolete("Use the UrlHelper.GetCropUrl extension instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, IPublishedContent mediaItem, string cropAlias) + { + return new HtmlString(mediaItem.GetCropUrl(cropAlias: cropAlias, useCropDimensions: true)); + } + + [Obsolete("Use the UrlHelper.GetCropUrl extension instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, IPublishedContent mediaItem, string propertyAlias, string cropAlias) + { + return new HtmlString(mediaItem.GetCropUrl(propertyAlias: propertyAlias, cropAlias: cropAlias, useCropDimensions: true)); + } + + [Obsolete("Use the UrlHelper.GetCropUrl extension instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, + IPublishedContent mediaItem, + int? width = null, + int? height = null, + string propertyAlias = Constants.Conventions.Media.File, + string cropAlias = null, + int? quality = null, + ImageCropMode? imageCropMode = null, + ImageCropAnchor? imageCropAnchor = null, + bool preferFocalPoint = false, + bool useCropDimensions = false, + bool cacheBuster = true, + string furtherOptions = null, + ImageCropRatioMode? ratioMode = null, + bool upScale = true) + { + return + new HtmlString(mediaItem.GetCropUrl(width, height, propertyAlias, cropAlias, quality, imageCropMode, + imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBuster, furtherOptions, ratioMode, + upScale)); + } + + [Obsolete("Use the UrlHelper.GetCropUrl extension instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static IHtmlString GetCropUrl(this HtmlHelper htmlHelper, + string imageUrl, + int? width = null, + int? height = null, + string imageCropperValue = null, + string cropAlias = null, + int? quality = null, + ImageCropMode? imageCropMode = null, + ImageCropAnchor? imageCropAnchor = null, + bool preferFocalPoint = false, + bool useCropDimensions = false, + string cacheBusterValue = null, + string furtherOptions = null, + ImageCropRatioMode? ratioMode = null, + bool upScale = true) + { + return + new HtmlString(imageUrl.GetCropUrl(width, height, imageCropperValue, cropAlias, quality, imageCropMode, + imageCropAnchor, preferFocalPoint, useCropDimensions, cacheBusterValue, furtherOptions, ratioMode, + upScale)); + } + + #endregion + + #region BeginUmbracoForm + + /// + /// Used for rendering out the Form for BeginUmbracoForm + /// + internal class UmbracoForm : MvcForm + { + /// + /// Creates an UmbracoForm + /// + /// + /// + /// + /// + /// + /// + public UmbracoForm( + ViewContext viewContext, + string controllerName, + string controllerAction, + string area, + FormMethod method, + object additionalRouteVals = null) + : base(viewContext) + { + _viewContext = viewContext; + _method = method; + _controllerName = controllerName; + _encryptedString = UmbracoHelper.CreateEncryptedRouteString(controllerName, controllerAction, area, additionalRouteVals); + + //For UmbracoForm's we want to add our routing string to the httpcontext items in the case where anti-forgery tokens are used. + //In which case our custom UmbracoAntiForgeryAdditionalDataProvider will kick in and validate the values in the request against + //the values that will be appended to the token. This essentially means that when anti-forgery tokens are used with UmbracoForm's forms, + //that each token is unique to the controller/action/area instead of the default ASP.Net implementation which is that the token is unique + //per user. + _viewContext.HttpContext.Items["ufprt"] = _encryptedString; + + } + + + private readonly ViewContext _viewContext; + private readonly FormMethod _method; + private bool _disposed; + private readonly string _encryptedString; + private readonly string _controllerName; + + protected override void Dispose(bool disposing) + { + if (this._disposed) + return; + this._disposed = true; + //Detect if the call is targeting UmbRegisterController/UmbProfileController/UmbLoginStatusController/UmbLoginController and if it is we automatically output a AntiForgeryToken() + // We have a controllerName and area so we can match + if (_controllerName == "UmbRegister" + || _controllerName == "UmbProfile" + || _controllerName == "UmbLoginStatus" + || _controllerName == "UmbLogin") + { + _viewContext.Writer.Write(AntiForgery.GetHtml().ToString()); + } + + //write out the hidden surface form routes + _viewContext.Writer.Write(""); + + base.Dispose(disposing); + } + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, FormMethod method) + { + return html.BeginUmbracoForm(action, controllerName, null, new Dictionary(), method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName) + { + return html.BeginUmbracoForm(action, controllerName, null, new Dictionary()); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, object additionalRouteVals, FormMethod method) + { + return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary(), method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, object additionalRouteVals) + { + return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary()); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + object additionalRouteVals, + object htmlAttributes, + FormMethod method) + { + return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes), method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + object additionalRouteVals, + object htmlAttributes) + { + return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + object additionalRouteVals, + IDictionary htmlAttributes, + FormMethod method) + { + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + + return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes, method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + object additionalRouteVals, + IDictionary htmlAttributes) + { + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + + return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, FormMethod method) + { + return html.BeginUmbracoForm(action, surfaceType, null, new Dictionary(), method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType) + { + return html.BeginUmbracoForm(action, surfaceType, null, new Dictionary()); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, FormMethod method) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T)); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals, FormMethod method) + { + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary(), method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals) + { + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary()); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, object additionalRouteVals, FormMethod method) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, object additionalRouteVals) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals, + object htmlAttributes, + FormMethod method) + { + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes), method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals, + object htmlAttributes) + { + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + object additionalRouteVals, + object htmlAttributes, + FormMethod method) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes, method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + object additionalRouteVals, + object htmlAttributes) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals, + IDictionary htmlAttributes, + FormMethod method) + { + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNull(surfaceType, "surfaceType"); + + var area = ""; + + var surfaceController = SurfaceControllerResolver.Current.RegisteredSurfaceControllers + .SingleOrDefault(x => x == surfaceType); + if (surfaceController == null) + throw new InvalidOperationException("Could not find the surface controller of type " + surfaceType.FullName); + var metaData = PluginController.GetMetadata(surfaceController); + if (metaData.AreaName.IsNullOrWhiteSpace() == false) + { + //set the area to the plugin area + area = metaData.AreaName; + } + return html.BeginUmbracoForm(action, metaData.ControllerName, area, additionalRouteVals, htmlAttributes, method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals, + IDictionary htmlAttributes) + { + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, htmlAttributes, FormMethod.Post); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + object additionalRouteVals, + IDictionary htmlAttributes, + FormMethod method) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes, method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + object additionalRouteVals, + IDictionary htmlAttributes) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, FormMethod method) + { + return html.BeginUmbracoForm(action, controllerName, area, null, new Dictionary(), method); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area) + { + return html.BeginUmbracoForm(action, controllerName, area, null, new Dictionary()); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, + object additionalRouteVals, + IDictionary htmlAttributes, + FormMethod method) + { + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + + var formAction = UmbracoContext.Current.OriginalRequestUrl.PathAndQuery; + return html.RenderForm(formAction, method, htmlAttributes, controllerName, action, area, additionalRouteVals); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, + object additionalRouteVals, + IDictionary htmlAttributes) + { + return html.BeginUmbracoForm(action, controllerName, area, additionalRouteVals, htmlAttributes, FormMethod.Post); + } + + /// + /// This renders out the form for us + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// This code is pretty much the same as the underlying MVC code that writes out the form + /// + private static MvcForm RenderForm(this HtmlHelper htmlHelper, + string formAction, + FormMethod method, + IDictionary htmlAttributes, + string surfaceController, + string surfaceAction, + string area, + object additionalRouteVals = null) + { + + //ensure that the multipart/form-data is added to the html attributes + if (htmlAttributes.ContainsKey("enctype") == false) + { + htmlAttributes.Add("enctype", "multipart/form-data"); + } + + var tagBuilder = new TagBuilder("form"); + tagBuilder.MergeAttributes(htmlAttributes); + // action is implicitly generated, so htmlAttributes take precedence. + tagBuilder.MergeAttribute("action", formAction); + // method is an explicit parameter, so it takes precedence over the htmlAttributes. + tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true); + var traditionalJavascriptEnabled = htmlHelper.ViewContext.ClientValidationEnabled && htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled == false; + if (traditionalJavascriptEnabled) + { + // forms must have an ID for client validation + tagBuilder.GenerateId("form" + Guid.NewGuid().ToString("N")); + } + htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag)); + + //new UmbracoForm: + var theForm = new UmbracoForm(htmlHelper.ViewContext, surfaceController, surfaceAction, area, method, additionalRouteVals); + + if (traditionalJavascriptEnabled) + { + htmlHelper.ViewContext.FormContext.FormId = tagBuilder.Attributes["id"]; + } + return theForm; + } + + #endregion + + #region Wrap + + public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, string innerText, params IHtmlTagWrapper[] children) + { + var item = html.Wrap(tag, innerText, (object)null); + foreach (var child in children) + { + item.AddChild(child); + } + return item; + } + + public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, object inner, object anonymousAttributes, params IHtmlTagWrapper[] children) + { + string innerText = null; + if (inner != null && inner.GetType() != typeof(DynamicNull)) + { + innerText = string.Format("{0}", inner); + } + var item = html.Wrap(tag, innerText, anonymousAttributes); + foreach (var child in children) + { + item.AddChild(child); + } + return item; + } + public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, object inner) + { + string innerText = null; + if (inner != null && inner.GetType() != typeof(DynamicNull)) + { + innerText = string.Format("{0}", inner); + } + return html.Wrap(tag, innerText, (object)null); + } + + public static HtmlTagWrapper Wrap(this HtmlHelper html, string tag, string innerText, object anonymousAttributes, params IHtmlTagWrapper[] children) + { + var wrap = new HtmlTagWrapper(tag); + if (anonymousAttributes != null) + { + wrap.ReflectAttributesFromAnonymousType(anonymousAttributes); + } + if (!string.IsNullOrWhiteSpace(innerText)) + { + wrap.AddChild(new HtmlTagWrapperTextNode(innerText)); + } + foreach (var child in children) + { + wrap.AddChild(child); + } + return wrap; + } + + public static HtmlTagWrapper Wrap(this HtmlHelper html, bool visible, string tag, string innerText, object anonymousAttributes, params IHtmlTagWrapper[] children) + { + var item = html.Wrap(tag, innerText, anonymousAttributes, children); + item.Visible = visible; + return item; + } + + #endregion + + #region canvasdesigner + + public static IHtmlString EnableCanvasDesigner(this HtmlHelper html, + UrlHelper url, + UmbracoContext umbCtx) + { + return html.EnableCanvasDesigner(url, umbCtx, string.Empty, string.Empty); + } + + public static IHtmlString EnableCanvasDesigner(this HtmlHelper html, + UrlHelper url, + UmbracoContext umbCtx, string canvasdesignerConfigPath) + { + return html.EnableCanvasDesigner(url, umbCtx, canvasdesignerConfigPath, string.Empty); + } + + public static IHtmlString EnableCanvasDesigner(this HtmlHelper html, + UrlHelper url, + UmbracoContext umbCtx, string canvasdesignerConfigPath, string canvasdesignerPalettesPath) + { + + var umbracoPath = url.Content(SystemDirectories.Umbraco); + + string previewLink = @"" + + @"" + + @"" + + @"" + + @""; + + string noPreviewLinks = @""; + + // Get page value + int pageId = umbCtx.PublishedContentRequest.UmbracoPage.PageID; + string[] path = umbCtx.PublishedContentRequest.UmbracoPage.SplitPath; + string result = string.Empty; + string cssPath = CanvasDesignerUtility.GetStylesheetPath(path, false); + + if (umbCtx.InPreviewMode) + { + canvasdesignerConfigPath = string.IsNullOrEmpty(canvasdesignerConfigPath) == false + ? canvasdesignerConfigPath + : string.Format("{0}/js/canvasdesigner.config.js", umbracoPath); + canvasdesignerPalettesPath = string.IsNullOrEmpty(canvasdesignerPalettesPath) == false + ? canvasdesignerPalettesPath + : string.Format("{0}/js/canvasdesigner.palettes.js", umbracoPath); + + if (string.IsNullOrEmpty(cssPath) == false) + result = string.Format(noPreviewLinks, cssPath) + Environment.NewLine; + + result = result + string.Format(previewLink, umbracoPath, canvasdesignerConfigPath, canvasdesignerPalettesPath, pageId); + } + else + { + // Get css path for current page + if (string.IsNullOrEmpty(cssPath) == false) + result = string.Format(noPreviewLinks, cssPath); + } + + return new HtmlString(result); + + } + + #endregion + + #region RelatedLink + + /// + /// Renders an anchor element for a RelatedLink instance. + /// Format: <a href="relatedLink.Link" target="_blank/_self">relatedLink.Caption</a> + /// + /// The HTML helper instance that this method extends. + /// The RelatedLink instance + /// An anchor element + public static MvcHtmlString GetRelatedLinkHtml(this HtmlHelper htmlHelper, RelatedLink relatedLink) + { + return htmlHelper.GetRelatedLinkHtml(relatedLink, null); + } + + /// + /// Renders an anchor element for a RelatedLink instance, accepting htmlAttributes. + /// Format: <a href="relatedLink.Link" target="_blank/_self" htmlAttributes>relatedLink.Caption</a> + /// + /// The HTML helper instance that this method extends. + /// The RelatedLink instance + /// An object that contains the HTML attributes to set for the element. + /// + public static MvcHtmlString GetRelatedLinkHtml(this HtmlHelper htmlHelper, RelatedLink relatedLink, object htmlAttributes) + { + var tagBuilder = new TagBuilder("a"); + tagBuilder.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes)); + tagBuilder.MergeAttribute("href", relatedLink.Link); + tagBuilder.MergeAttribute("target", relatedLink.NewWindow ? "_blank" : "_self"); + tagBuilder.InnerHtml = HttpUtility.HtmlEncode(relatedLink.Caption); + return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.Normal)); + } + #endregion + } +} From cf53ba363d1f32e6daedce98da8b10cb69f789d2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 26 Jun 2019 13:29:05 +1000 Subject: [PATCH 036/776] Internalizes new methods classes + cleanup --- src/Umbraco.Core/Constants-DataTypes.cs | 2 +- ...nitionExtensions.cs => DataTypeExtensions.cs} | 16 ++++++++++++---- .../Services/DateTypeServiceExtensions.cs | 7 +++++-- 3 files changed, 18 insertions(+), 7 deletions(-) rename src/Umbraco.Core/Models/{DataTypeDefinitionExtensions.cs => DataTypeExtensions.cs} (79%) diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs index d5d0e99dab..0e95e57b1b 100644 --- a/src/Umbraco.Core/Constants-DataTypes.cs +++ b/src/Umbraco.Core/Constants-DataTypes.cs @@ -7,7 +7,7 @@ namespace Umbraco.Core /// /// Defines the identifiers for Umbraco data types as constants for easy centralized access/management. /// - public static class DataTypes + internal static class DataTypes { public static class ReservedPreValueKeys diff --git a/src/Umbraco.Core/Models/DataTypeDefinitionExtensions.cs b/src/Umbraco.Core/Models/DataTypeExtensions.cs similarity index 79% rename from src/Umbraco.Core/Models/DataTypeDefinitionExtensions.cs rename to src/Umbraco.Core/Models/DataTypeExtensions.cs index 45c4420820..47702de2c5 100644 --- a/src/Umbraco.Core/Models/DataTypeDefinitionExtensions.cs +++ b/src/Umbraco.Core/Models/DataTypeExtensions.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Umbraco.Core.Models { - public static class DataTypeDefinitionExtensions + internal static class DataTypeExtensions { private static readonly ISet IdsOfBuildInDataTypes = new HashSet() { @@ -37,11 +37,19 @@ namespace Umbraco.Core.Models /// /// Returns true if this date type is build-in/default. /// - /// The data type definition. + /// The data type definition. /// - public static bool IsBuildInDataType(this IDataTypeDefinition dataTypeDefinition) + public static bool IsBuildInDataType(this IDataTypeDefinition dataType) { - return IdsOfBuildInDataTypes.Contains(dataTypeDefinition.Key); + return IsBuildInDataType(dataType.Key); + } + + /// + /// Returns true if this date type is build-in/default. + /// + public static bool IsBuildInDataType(Guid key) + { + return IdsOfBuildInDataTypes.Contains(key); } } } diff --git a/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs index 63006653ab..bea83558a5 100644 --- a/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs +++ b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs @@ -1,17 +1,20 @@ using System; +using Umbraco.Core.Models; namespace Umbraco.Core.Services { - public static class DateTypeServiceExtensions + internal static class DateTypeServiceExtensions { public static bool IsDataTypeIgnoringUserStartNodes(this IDataTypeService dataTypeService, Guid key) { + if (DataTypeExtensions.IsBuildInDataType(key)) return false; //built in ones can never be ignoring start nodes + var dataType = dataTypeService.GetDataTypeDefinitionById(key); if (dataType != null) { var preValues = dataTypeService.GetPreValuesCollectionByDataTypeId(dataType.Id); - if (preValues.PreValuesAsDictionary.TryGetValue( + if (preValues.FormatAsDictionary().TryGetValue( Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, out var preValue)) return preValue.Value.InvariantEquals("1"); } From 23b8e1cce8107aabd8ba5ebbcb0a3ee33aff212d Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 26 Jun 2019 13:31:04 +1000 Subject: [PATCH 037/776] Removes the remaining part of the EntityRepository that was loading in ALL properties for media which we don't want whatsoever which means some other code is cleaned up/removed --- src/Umbraco.Core/Models/UmbracoEntity.cs | 39 +---- .../Repositories/EntityRepository.cs | 146 ++---------------- src/Umbraco.Core/Services/EntityService.cs | 21 --- .../Models/UmbracoEntityTests.cs | 27 +--- .../Trees/ContentTreeController.cs | 3 - .../Trees/ContentTreeControllerBase.cs | 8 +- src/Umbraco.Web/Trees/MediaTreeController.cs | 8 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 - .../WebServices/FolderBrowserService.cs | 114 -------------- .../umbraco/Trees/BaseMediaTree.cs | 31 +--- .../umbraco/Trees/BaseTree.cs | 2 +- 11 files changed, 27 insertions(+), 373 deletions(-) delete mode 100644 src/Umbraco.Web/WebServices/FolderBrowserService.cs diff --git a/src/Umbraco.Core/Models/UmbracoEntity.cs b/src/Umbraco.Core/Models/UmbracoEntity.cs index b789698704..7783331952 100644 --- a/src/Umbraco.Core/Models/UmbracoEntity.cs +++ b/src/Umbraco.Core/Models/UmbracoEntity.cs @@ -225,42 +225,5 @@ namespace Umbraco.Core.Models return clone; } - /// - /// A struction that can be contained in the additional data of an UmbracoEntity representing - /// a user defined property - /// - public class EntityProperty : IDeepCloneable - { - public string PropertyEditorAlias { get; set; } - public object Value { get; set; } - public object DeepClone() - { - //Memberwise clone on Entity will work since it doesn't have any deep elements - // for any sub class this will work for standard properties as well that aren't complex object's themselves. - var clone = MemberwiseClone(); - return clone; - } - - protected bool Equals(EntityProperty other) - { - return PropertyEditorAlias.Equals(other.PropertyEditorAlias) && string.Equals(Value, other.Value); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((EntityProperty) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (PropertyEditorAlias.GetHashCode() * 397) ^ (Value != null ? Value.GetHashCode() : 0); - } - } - } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index 495c0109ad..7b10b19e1e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -20,27 +20,22 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class EntityRepository : DisposableObjectSlim, IEntityRepository { - private readonly IDatabaseUnitOfWork _work; - public EntityRepository(IDatabaseUnitOfWork work) { - _work = work; + UnitOfWork = work; } /// /// 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; } + get { return (Guid)UnitOfWork.Key; } } #region Query Methods @@ -69,75 +64,8 @@ namespace Umbraco.Core.Persistence.Repositories IEnumerable result; - if (isMedia) - { - //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag! - var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); - - var ids = pagedResult.Items.Select(x => (int) x.id).InGroupsOf(2000); - var entities = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); - - //Now we need to merge in the property data since we need paging and we can't do this the way that the big media query was working before - foreach (var idGroup in ids) - { - var propSql = GetPropertySql(Constants.ObjectTypes.Media) - .Where("contentNodeId IN (@ids)", new { ids = idGroup }); - propSql = (orderDirection == Direction.Descending) ? propSql.OrderByDescending("contentNodeId") : propSql.OrderBy("contentNodeId"); - - //This does NOT fetch all data into memory in a list, this will read - // over the records as a data reader, this is much better for performance and memory, - // but it means that during the reading of this data set, nothing else can be read - // from SQL server otherwise we'll get an exception. - var allPropertyData = _work.Database.Query(propSql); - - //keep track of the current property data item being enumerated - var propertyDataSetEnumerator = allPropertyData.GetEnumerator(); - var hasCurrent = false; // initially there is no enumerator.Current - - try - { - //This must be sorted by node id (which is done by SQL) because this is how we are sorting the query to lookup property types above, - // which allows us to more efficiently iterate over the large data set of property values. - foreach (var entity in entities) - { - // assemble the dtos for this def - // use the available enumerator.Current if any else move to next - while (hasCurrent || propertyDataSetEnumerator.MoveNext()) - { - if (propertyDataSetEnumerator.Current.contentNodeId == entity.Id) - { - hasCurrent = false; // enumerator.Current is not available - - //the property data goes into the additional data - entity.AdditionalData[propertyDataSetEnumerator.Current.propertyTypeAlias] = new UmbracoEntity.EntityProperty - { - PropertyEditorAlias = propertyDataSetEnumerator.Current.propertyEditorAlias, - Value = StringExtensions.IsNullOrWhiteSpace(propertyDataSetEnumerator.Current.dataNtext) - ? propertyDataSetEnumerator.Current.dataNvarchar - : StringExtensions.ConvertToJsonIfPossible(propertyDataSetEnumerator.Current.dataNtext) - }; - } - else - { - hasCurrent = true; // enumerator.Current is available for another def - break; // no more propertyDataDto for this def - } - } - } - } - finally - { - propertyDataSetEnumerator.Dispose(); - } - } - - result = entities; - } - else - { - var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); - result = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); - } + var pagedResult = UnitOfWork.Database.Page(pageIndex + 1, pageSize, pagedSql); + result = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); //The total items from the PetaPoco page query will be wrong due to the Outer join used on parent, depending on the search this will //return duplicate results when the COUNT is used in conjuction with it, so we need to get the total on our own. @@ -159,7 +87,7 @@ namespace Umbraco.Core.Persistence.Repositories var translatorCount = new SqlTranslator(sqlCountClause, query); var countSql = translatorCount.Translate(); - totalRecords = _work.Database.ExecuteScalar(countSql); + totalRecords = UnitOfWork.Database.ExecuteScalar(countSql); return result; } @@ -167,7 +95,7 @@ namespace Umbraco.Core.Persistence.Repositories 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; @@ -187,7 +115,7 @@ namespace Umbraco.Core.Persistence.Repositories var factory = new UmbracoEntityFactory(); //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(sql); + var dtos = UnitOfWork.Database.Query(sql); var collection = new EntityDefinitionCollection(); foreach (var dto in dtos) { @@ -202,7 +130,7 @@ namespace Umbraco.Core.Persistence.Repositories 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; @@ -222,7 +150,7 @@ namespace Umbraco.Core.Persistence.Repositories var factory = new UmbracoEntityFactory(); //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(sql); + var dtos = UnitOfWork.Database.Query(sql); var collection = new EntityDefinitionCollection(); foreach (var dto in dtos) { @@ -256,7 +184,7 @@ namespace Umbraco.Core.Persistence.Repositories var factory = new UmbracoEntityFactory(); //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(sql); + var dtos = UnitOfWork.Database.Query(sql); var collection = new EntityDefinitionCollection(); foreach (var dto in dtos) { @@ -283,7 +211,7 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = new Sql("SELECT id, path FROM umbracoNode WHERE umbracoNode.nodeObjectType=@type", new { type = objectTypeId }); if (filter != null) filter(sql); - return _work.Database.Fetch(sql); + return UnitOfWork.Database.Fetch(sql); } public virtual IEnumerable GetByQuery(IQuery query) @@ -292,7 +220,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(); @@ -306,10 +234,6 @@ namespace Umbraco.Core.Persistence.Repositories /// /// /// - /// - /// Note that this will also fetch all property data for media items, which can cause performance problems - /// when used without paging, in sites with large amounts of data in cmsPropertyData. - /// public virtual IEnumerable GetByQuery(IQuery query, Guid objectTypeId) { var isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; @@ -323,27 +247,7 @@ namespace Umbraco.Core.Persistence.Repositories return GetByQueryInternal(entitySql, isContent, isMedia); } - /// - /// Gets entities by query without fetching property data. - /// - /// - /// - /// - /// - /// This is supposed to be internal and can be used when getting all entities without paging, without causing - /// performance issues. - /// - internal IEnumerable GetMediaByQueryWithoutPropertyData(IQuery query) - { - var sqlClause = GetBaseWhere(GetBase, false, true, null, UmbracoObjectTypes.Media.GetGuid()); - - var translator = new SqlTranslator(sqlClause, query); - var entitySql = translator.Translate(); - - return GetByQueryInternal(entitySql, false, true); - } - - internal IEnumerable GetByQueryInternal(Sql entitySql, bool isContent, bool isMedia) + private IEnumerable GetByQueryInternal(Sql entitySql, bool isContent, bool isMedia) { var factory = new UmbracoEntityFactory(); @@ -351,7 +255,7 @@ namespace Umbraco.Core.Persistence.Repositories var finalSql = entitySql.Append(GetGroupBy(isContent, isMedia)); //query = read forward data reader, do not load everything into mem - var dtos = _work.Database.Query(finalSql); + var dtos = UnitOfWork.Database.Query(finalSql); var collection = new EntityDefinitionCollection(); foreach (var dto in dtos) { @@ -392,22 +296,6 @@ namespace Umbraco.Core.Persistence.Repositories return entitySql.Append(GetGroupBy(isContent, true, false)); } - private Sql GetPropertySql(string nodeObjectType) - { - var sql = new Sql() - .Select("contentNodeId, versionId, dataNvarchar, dataNtext, propertyEditorAlias, alias as propertyTypeAlias") - .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 = nodeObjectType }); - - return sql; - } - protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter) { return GetBase(isContent, isMedia, customFilter, false); @@ -638,13 +526,13 @@ namespace Umbraco.Core.Persistence.Repositories public bool Exists(Guid key) { var sql = new Sql().Select("COUNT(*)").From("umbracoNode").Where("uniqueID=@uniqueID", new {uniqueID = key}); - return _work.Database.ExecuteScalar(sql) > 0; + return UnitOfWork.Database.ExecuteScalar(sql) > 0; } public bool Exists(int id) { var sql = new Sql().Select("COUNT(*)").From("umbracoNode").Where("id=@id", new { id = id }); - return _work.Database.ExecuteScalar(sql) > 0; + return UnitOfWork.Database.ExecuteScalar(sql) > 0; } #region private classes diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index 906ece1081..f4b1b71732 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -234,27 +234,6 @@ namespace Umbraco.Core.Services } } - /// - /// Gets a collection of children by the parent's Id and UmbracoObjectType without adding property data - /// - /// Id of the parent to retrieve children for - /// An enumerable list of objects - internal IEnumerable GetMediaChildrenWithoutPropertyData(int parentId) - { - var objectTypeId = UmbracoObjectTypes.Media.GetGuid(); - using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) - { - var repository = RepositoryFactory.CreateEntityRepository(uow); - var query = Query.Builder.Where(x => x.ParentId == parentId); - - // Not pretty having to cast the repository, but it is the only way to get to use an internal method that we - // do not want to make public on the interface. Unfortunately also prevents this from being unit tested. - // See this issue for details on why we need this: - // https://github.com/umbraco/Umbraco-CMS/issues/3457 - return ((EntityRepository)repository).GetMediaByQueryWithoutPropertyData(query); - } - } - /// /// Returns a paged collection of children /// diff --git a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs index 7186474999..1ab6bbea31 100644 --- a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs +++ b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs @@ -177,18 +177,7 @@ namespace Umbraco.Tests.Models }; item.AdditionalData.Add("test1", 3); item.AdditionalData.Add("test2", "valuie"); - - item.AdditionalData.Add("test3", new UmbracoEntity.EntityProperty() - { - Value = "test", - PropertyEditorAlias = "TestPropertyEditor" - }); - item.AdditionalData.Add("test4", new UmbracoEntity.EntityProperty() - { - Value = "test2", - PropertyEditorAlias = "TestPropertyEditor2" - }); - + var clone = (UmbracoEntity)item.DeepClone(); Assert.AreNotSame(clone, item); @@ -250,20 +239,10 @@ namespace Umbraco.Tests.Models }; item.AdditionalData.Add("test1", 3); item.AdditionalData.Add("test2", "valuie"); - item.AdditionalData.Add("test3", new UmbracoEntity.EntityProperty() - { - Value = "test", - PropertyEditorAlias = "TestPropertyEditor" - }); - item.AdditionalData.Add("test4", new UmbracoEntity.EntityProperty() - { - Value = "test2", - PropertyEditorAlias = "TestPropertyEditor2" - }); - + var result = ss.ToStream(item); var json = result.ResultStream.ToJsonString(); Debug.Print(json); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 54adb24b74..e7fb0b8d84 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -207,9 +207,6 @@ namespace Umbraco.Web.Trees return HasPathAccess(entity, queryStrings); } - internal override IEnumerable GetChildrenFromEntityService(int entityId) - => Services.EntityService.GetChildren(entityId, UmbracoObjectType).ToList(); - /// /// Returns a collection of all menu items that can be on a content node /// diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 2624c89b56..4e4086a3c6 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -205,12 +205,8 @@ namespace Umbraco.Web.Trees return GetChildrenFromEntityService(entityId); } - /// - /// Abstract method to fetch the entities from the entity service - /// - /// - /// - internal abstract IEnumerable GetChildrenFromEntityService(int entityId); + private IEnumerable GetChildrenFromEntityService(int entityId) + => Services.EntityService.GetChildren(entityId, UmbracoObjectType).ToList(); /// /// Returns true or false if the current user has access to the node based on the user's allowed start node (path) access diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index f2f59b6bd7..50336dbc10 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -177,12 +177,6 @@ namespace Umbraco.Web.Trees { return _treeSearcher.ExamineSearch(Umbraco, query, UmbracoEntityTypes.Media, pageSize, pageIndex, out totalFound, searchFrom); } - - internal override IEnumerable GetChildrenFromEntityService(int entityId) - // Not pretty having to cast the service, but it is the only way to get to use an internal method that we - // do not want to make public on the interface. Unfortunately also prevents this from being unit tested. - // See this issue for details on why we need this: - // https://github.com/umbraco/Umbraco-CMS/issues/3457 - => ((EntityService)Services.EntityService).GetMediaChildrenWithoutPropertyData(entityId).ToList(); + } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index d4fd27c323..32eaae1077 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1812,7 +1812,6 @@ - diff --git a/src/Umbraco.Web/WebServices/FolderBrowserService.cs b/src/Umbraco.Web/WebServices/FolderBrowserService.cs deleted file mode 100644 index 15a6c10880..0000000000 --- a/src/Umbraco.Web/WebServices/FolderBrowserService.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Web.Script.Serialization; -using Umbraco.Core; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Web.Media.ThumbnailProviders; -using umbraco.BusinessLogic; -using umbraco.cms.businesslogic.Tags; -using Umbraco.Web.BaseRest; -using Tag = umbraco.cms.businesslogic.Tags.Tag; - -namespace Umbraco.Web.WebServices -{ - //TODO: Can we convert this to MVC please instead of /base? - [Obsolete("Thumbnails are generated by ImageProcessor, use that instead")] - [RestExtension("FolderBrowserService")] - public class FolderBrowserService - { - [RestExtensionMethod(ReturnXml = false)] - public static string GetChildren(int parentId) - { - var currentUser = GetCurrentUser(); - AuthorizeAccess(parentId, currentUser); - - // Get children and filter - var data = new List(); - var service = ApplicationContext.Current.Services.EntityService; - - var entities = service.GetChildren(parentId, UmbracoObjectTypes.Media); - foreach (UmbracoEntity entity in entities) - { - var uploadFieldProperty = entity.AdditionalData - .Select(x => x.Value as UmbracoEntity.EntityProperty) - .Where(x => x != null) - .FirstOrDefault(x => x.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias); - - //var uploadFieldProperty = entity.UmbracoProperties.FirstOrDefault(x => x.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias); - - var thumbnailUrl = uploadFieldProperty == null ? "" : ThumbnailProvidersResolver.Current.GetThumbnailUrl((string)uploadFieldProperty.Value); - - var item = new - { - Id = entity.Id, - Path = entity.Path, - Name = entity.Name, - Tags = string.Join(",", Tag.GetTags(entity.Id).Select(x => x.TagCaption)), - MediaTypeAlias = entity.ContentTypeAlias, - EditUrl = string.Format("editMedia.aspx?id={0}", entity.Id), - FileUrl = uploadFieldProperty == null - ? "" - : uploadFieldProperty.Value, - ThumbnailUrl = string.IsNullOrEmpty(thumbnailUrl) - ? IOHelper.ResolveUrl(string.Format("{0}/images/thumbnails/{1}", SystemDirectories.Umbraco, entity.ContentTypeThumbnail)) - : thumbnailUrl - }; - - data.Add(item); - - } - - return new JavaScriptSerializer().Serialize(data); - } - - [RestExtensionMethod(ReturnXml = false)] - public static string Delete(string nodeIds) - { - var currentUser = GetCurrentUser(); - - var nodeIdParts = nodeIds.Split(','); - - foreach (var nodeIdPart in nodeIdParts.Where(x => string.IsNullOrEmpty(x) == false)) - { - int nodeId; - if (Int32.TryParse(nodeIdPart, out nodeId) == false) - continue; - - var node = new global::umbraco.cms.businesslogic.media.Media(nodeId); - AuthorizeAccess(node, currentUser); - - node.delete(("," + node.Path + ",").Contains(",-21,")); - } - - return new JavaScriptSerializer().Serialize(new { success = true }); - } - - private static User GetCurrentUser() - { - var currentUser = User.GetCurrent(); - if (currentUser == null) - throw new UnauthorizedAccessException("You must be logged in to use this service"); - - return currentUser; - } - - private static void AuthorizeAccess(global::umbraco.cms.businesslogic.media.Media mediaItem, User currentUser) - { - if (("," + mediaItem.Path + ",").Contains("," + currentUser.StartMediaId + ",") == false) - throw new UnauthorizedAccessException("You do not have access to this Media node"); - } - - private static void AuthorizeAccess(int parentId, User currentUser) - { - var service = ApplicationContext.Current.Services.EntityService; - var parentMedia = service.Get(parentId, UmbracoObjectTypes.Media); - var mediaPath = parentMedia == null ? parentId.ToString(CultureInfo.InvariantCulture) : parentMedia.Path; - - if (("," + mediaPath + ",").Contains("," + currentUser.StartMediaId + ",") == false) - throw new UnauthorizedAccessException("You do not have access to this Media node"); - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseMediaTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseMediaTree.cs index f72457d867..f7412f6915 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseMediaTree.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseMediaTree.cs @@ -189,35 +189,8 @@ function openMedia(id) { return ""; } - /// - /// NOTE: New implementation of the legacy GetLinkValue. This is however a bit quirky as a media item can have multiple "Linkable DataTypes". - /// Returns the value for a link in WYSIWYG mode, by default only media items that have a - /// DataTypeUploadField are linkable, however, a custom tree can be created which overrides - /// this method, or another GUID for a custom data type can be added to the LinkableMediaDataTypes - /// list on application startup. - /// - /// - /// - internal virtual string GetLinkValue(UmbracoEntity entity) - { - foreach (var property in entity.AdditionalData - .Select(x => x.Value as UmbracoEntity.EntityProperty) - .Where(x => x != null)) - { - - - //required for backwards compatibility with v7 with changing the GUID -> alias - var controlId = LegacyPropertyEditorIdToAliasConverter.GetLegacyIdFromAlias(property.PropertyEditorAlias, LegacyPropertyEditorIdToAliasConverter.NotFoundLegacyIdResponseBehavior.ReturnNull); - if (controlId != null) - { - if (LinkableMediaDataTypes.Contains(controlId.Value) - && string.IsNullOrEmpty((string)property.Value) == false) - - return property.Value.ToString(); - } - } - return ""; - } + [Obsolete("Just like this class is and is not used whatsoever for a very long time, this method will return an empty string")] + internal virtual string GetLinkValue(UmbracoEntity entity) => string.Empty; /// /// By default, any media type that is to be "linkable" in the WYSIWYG editor must contain diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs index 49d8c7214d..ab18a1316e 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs @@ -652,4 +652,4 @@ namespace umbraco.cms.presentation.Trees } -} \ No newline at end of file +} From ca15f340b09b8a9df53a49a5089f3ceb7edc6a15 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 26 Jun 2019 13:32:16 +1000 Subject: [PATCH 038/776] Fixes a bunch of N+1, content/media trees should be much more snappy --- src/Umbraco.Core/Models/UserExtensions.cs | 13 ----- .../Factories/UmbracoEntityFactory.cs | 16 +++--- src/Umbraco.Core/Umbraco.Core.csproj | 2 +- .../Trees/ContentTreeControllerBase.cs | 54 ++++++++++++++++--- src/Umbraco.Web/Trees/TreeControllerBase.cs | 16 ------ 5 files changed, 58 insertions(+), 43 deletions(-) diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index d989876607..6c29ab56bb 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -193,19 +193,6 @@ namespace Umbraco.Core.Models // check for a start node in the path return startNodeIds.Any(x => formattedPath.Contains(string.Concat(",", x, ","))); - } - - internal static bool IsInBranchOfStartNode(this IUser user, IUmbracoEntity entity, IEntityService entityService, int recycleBinId, out bool hasPathAccess) - { - switch (recycleBinId) - { - case Constants.System.RecycleBinMedia: - return IsInBranchOfStartNode(entity.Path, user.CalculateMediaStartNodeIds(entityService), user.GetMediaStartNodePaths(entityService), out hasPathAccess); - case Constants.System.RecycleBinContent: - return IsInBranchOfStartNode(entity.Path, user.CalculateContentStartNodeIds(entityService), user.GetContentStartNodePaths(entityService), out hasPathAccess); - default: - throw new NotSupportedException("Path access is only determined on content or media"); - } } internal static bool IsInBranchOfStartNode(string path, int[] startNodeIds, string[] startNodePaths, out bool hasPathAccess) diff --git a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs index 86594836db..f12af4d27a 100644 --- a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs @@ -12,14 +12,18 @@ namespace Umbraco.Core.Persistence.Factories { internal class UmbracoEntityFactory { + private static readonly Lazy EntityProperties = new Lazy(() => typeof(IUmbracoEntity).GetPublicProperties().Select(x => x.Name).ToArray()); + + /// + /// Figure out what extra properties we have that are not on the IUmbracoEntity and add them to additional data + /// + /// + /// internal void AddAdditionalData(UmbracoEntity entity, IDictionary originalEntityProperties) - { - var entityProps = typeof(IUmbracoEntity).GetPublicProperties().Select(x => x.Name).ToArray(); - - //figure out what extra properties we have that are not on the IUmbracoEntity and add them to additional data + { foreach (var k in originalEntityProperties.Keys .Select(x => new { orig = x, title = x.ToCleanString(CleanStringType.PascalCase | CleanStringType.Ascii | CleanStringType.ConvertCase) }) - .Where(x => entityProps.InvariantContains(x.title) == false)) + .Where(x => EntityProperties.Value.InvariantContains(x.title) == false)) { entity.AdditionalData[k.title] = originalEntityProperties[k.orig]; } @@ -78,4 +82,4 @@ namespace Umbraco.Core.Persistence.Factories } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 49f5e8b5d2..3b5d0aaff6 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -377,7 +377,7 @@ - + diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 4e4086a3c6..d5c7c04235 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -87,11 +87,12 @@ namespace Umbraco.Web.Trees /// /// /// - internal TreeNode GetSingleTreeNodeWithAccessCheck(IUmbracoEntity e, string parentId, FormDataCollection queryStrings) + internal TreeNode GetSingleTreeNodeWithAccessCheck(IUmbracoEntity e, string parentId, FormDataCollection queryStrings, + int[] startNodeIds, string[] startNodePaths, bool ignoreUserStartNodes) { bool hasPathAccess; - var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out hasPathAccess); - if (IgnoreUserStartNodes(queryStrings) == false && entityIsAncestorOfStartNodes == false) + var entityIsAncestorOfStartNodes = UserExtensions.IsInBranchOfStartNode(e.Path, startNodeIds, startNodePaths, out hasPathAccess); + if (ignoreUserStartNodes == false && entityIsAncestorOfStartNodes == false) return null; var treeNode = GetSingleTreeNode(e, parentId, queryStrings); @@ -101,13 +102,30 @@ namespace Umbraco.Web.Trees //the node so we need to return null; return null; } - if (IgnoreUserStartNodes(queryStrings) == false && hasPathAccess == false) + if (ignoreUserStartNodes == false && hasPathAccess == false) { treeNode.AdditionalData["noAccess"] = true; } return treeNode; } + private void GetUserStartNodes(out int[] startNodeIds, out string[] startNodePaths) + { + switch (RecycleBinId) + { + case Constants.System.RecycleBinMedia: + startNodeIds = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + startNodePaths = Security.CurrentUser.GetMediaStartNodePaths(Services.EntityService); + break; + case Constants.System.RecycleBinContent: + startNodeIds = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + startNodePaths = Security.CurrentUser.GetContentStartNodePaths(Services.EntityService); + break; + default: + throw new NotSupportedException("Path access is only determined on content or media"); + } + } + /// /// Returns the /// @@ -134,6 +152,8 @@ namespace Umbraco.Web.Trees ? queryStrings.GetValue(TreeQueryStringParameters.StartNodeId) : string.Empty; + var ignoreUserStartNodes = IgnoreUserStartNodes(queryStrings); + if (string.IsNullOrEmpty(startNodeId) == false && startNodeId != "undefined" && startNodeId != rootIdString) { // request has been made to render from a specific, non-root, start node @@ -141,7 +161,7 @@ namespace Umbraco.Web.Trees // ensure that the user has access to that node, otherwise return the empty tree nodes collection // TODO: in the future we could return a validation statement so we can have some UI to notify the user they don't have access - if (IgnoreUserStartNodes(queryStrings) == false && HasPathAccess(id, queryStrings) == false) + if (ignoreUserStartNodes == false && HasPathAccess(id, queryStrings) == false) { LogHelper.Warn("User " + Security.CurrentUser.Username + " does not have access to node with id " + id); return nodes; @@ -159,7 +179,11 @@ namespace Umbraco.Web.Trees // get child entities - if id is root, but user's start nodes do not contain the // root node, this returns the start nodes instead of root's children var entities = GetChildEntities(id, queryStrings).ToList(); - nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings)).Where(x => x != null)); + + //get the current user start node/paths + GetUserStartNodes(out var userStartNodes, out var userStartNodePaths); + + nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths, ignoreUserStartNodes)).Where(x => x != null)); // if the user does not have access to the root node, what we have is the start nodes, // but to provide some context we also need to add their topmost nodes when they are not @@ -170,7 +194,7 @@ namespace Umbraco.Web.Trees if (topNodeIds.Length > 0) { var topNodes = Services.EntityService.GetAll(UmbracoObjectType, topNodeIds.ToArray()); - nodes.AddRange(topNodes.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings)).Where(x => x != null)); + nodes.AddRange(topNodes.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths, ignoreUserStartNodes)).Where(x => x != null)); } } @@ -482,5 +506,21 @@ namespace Umbraco.Web.Trees } private readonly ConcurrentDictionary _entityCache = new ConcurrentDictionary(); + + /// + /// If the request should allows a user to choose nodes that they normally don't have access to + /// + /// + /// + internal bool IgnoreUserStartNodes(FormDataCollection queryStrings) + { + var dataTypeId = queryStrings.GetValue(TreeQueryStringParameters.DataTypeId); + if (dataTypeId.HasValue) + { + return Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); + } + + return false; + } } } diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index 995cb031d0..8962a9ca10 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -350,22 +350,6 @@ namespace Umbraco.Web.Trees return queryStrings.GetValue(TreeQueryStringParameters.IsDialog); } - /// - /// If the request should allows a user to choose nodes that they normally don't have access to - /// - /// - /// - protected bool IgnoreUserStartNodes(FormDataCollection queryStrings) - { - var dataTypeId = queryStrings.GetValue(TreeQueryStringParameters.DataTypeId); - if (dataTypeId.HasValue) - { - return Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); - } - - return false; - } - /// /// An event that allows developers to modify the tree node collection that is being rendered /// From 1b83e0a87da3a5c5287d4253700fd2de9ac8e2bf Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 26 Jun 2019 13:49:45 +1000 Subject: [PATCH 039/776] cleanup --- .../src/common/resources/entity.resource.js | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index dce2cd8347..455fcab956 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -327,24 +327,13 @@ function entityResource($q, $http, umbRequestHelper) { * */ getAncestors: function (id, type, options) { - var defaults = { - dataTypeId: null - }; - 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; - - + var args = [ { id: id }, { type: type } ]; - if(options.dataTypeId){ - args.push({dataTypeId: options.dataTypeId}); + if (options.dataTypeId) { + args.push({ dataTypeId: options.dataTypeId }); } return umbRequestHelper.resourcePromise( From a55323176f02e2c93150152da30302582f07f933 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 26 Jun 2019 14:35:36 +1000 Subject: [PATCH 040/776] Updates EntityController to support passing in the dataTypeId for the GetChildren methods and updates the mediapicker to pass along that data --- .../src/common/resources/entity.resource.js | 12 +- .../mediaPicker/mediapicker.controller.js | 2 +- src/Umbraco.Web/Editors/EntityController.cs | 126 +++++++++++++----- 3 files changed, 103 insertions(+), 37 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 455fcab956..5e6f1095e4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -358,14 +358,19 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity. * */ - getChildren: function (id, type) { + getChildren: function (id, type, options) { + + var args = [{ id: id }, { type: type }]; + if (options.dataTypeId) { + args.push({ dataTypeId: options.dataTypeId }); + } return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetChildren", - [{ id: id }, { type: type }])), + args)), 'Failed to retrieve child data for id ' + id); }, @@ -433,7 +438,8 @@ function entityResource($q, $http, umbRequestHelper) { pageSize: options.pageSize, orderBy: options.orderBy, orderDirection: options.orderDirection, - filter: encodeURIComponent(options.filter) + filter: encodeURIComponent(options.filter), + dataTypeId: options.dataTypeId } )), 'Failed to retrieve child data for id ' + parentId); 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 400d3a7bb5..96354d7696 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 @@ -388,7 +388,7 @@ angular.module("umbraco") function getChildren(id) { $scope.loading = true; - return entityResource.getChildren(id, "Media") + return entityResource.getChildren(id, "Media", $scope.searchOptions) .then(function(data) { for (i=0;i /// - /// Some objects such as macros are not based on CMSNode + /// + /// This controller allows resolving basic entity data for various entities without placing the hard restrictions on users that may not have access + /// to the sections these entities entities exist in. This is to allow pickers, etc... of data to work for all users. In some cases such as accessing + /// Members, more explicit security checks are done. + /// + /// Some objects such as macros are not based on CMSNode /// [EntityControllerConfiguration] [PluginController("UmbracoApi")] @@ -88,8 +93,7 @@ namespace Umbraco.Web.Editors [HttpGet] public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null, Guid? dataTypeId = null) { - //TODO: Should we restrict search results based on what app the user has access to? - // - Theoretically you shouldn't be able to see member data if you don't have access to members right? + // NOTE: Theoretically you shouldn't be able to see member data if you don't have access to members right? ... but there is a member picker, so can't really do that if (string.IsNullOrEmpty(query)) return Enumerable.Empty(); @@ -195,6 +199,9 @@ namespace Umbraco.Web.Editors /// Int id of the entity to fetch URL for /// The tpye of entity such as Document, Media, Member /// The URL or path to the item + /// + /// We are not restricting this with security because there is no sensitive data + /// public HttpResponseMessage GetUrl(int id, UmbracoEntityTypes type) { var returnUrl = string.Empty; @@ -428,9 +435,52 @@ namespace Umbraco.Web.Editors return GetResultForKeys(ids, type); } - public IEnumerable GetChildren(int id, UmbracoEntityTypes type) + public IEnumerable GetChildren(int id, UmbracoEntityTypes type, Guid? dataTypeId = null) { - return GetResultForChildren(id, type); + var objectType = ConvertToObjectType(type); + if (objectType.HasValue) + { + //TODO: Need to check for Object types that support hierarchy here, some might not. + + int[] startNodes = null; + switch (type) + { + case UmbracoEntityTypes.Document: + startNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + startNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + } + + var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); + + // root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes + if (id == Constants.System.Root && startNodes.Length > 0 && startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes) + { + var nodes = Services.EntityService.GetAll(objectType.Value, startNodes).ToArray(); + if (nodes.Length == 0) + return Enumerable.Empty(); + var pr = new List(nodes.Select(Mapper.Map)); + return pr; + } + + // else proceed as usual + + return Services.EntityService.GetChildren(id, objectType.Value) + .WhereNotNull() + .Select(Mapper.Map); + } + //now we need to convert the unknown ones + switch (type) + { + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); + } } /// @@ -451,7 +501,8 @@ namespace Umbraco.Web.Editors int pageSize, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, - string filter = "") + string filter = "", + Guid? dataTypeId = null) { int intId; @@ -480,7 +531,7 @@ namespace Umbraco.Web.Editors //the EntityService can search paged members from the root intId = -1; - return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter); + return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter, dataTypeId); } //the EntityService cannot search members of a certain type, this is currently not supported and would require @@ -513,7 +564,8 @@ namespace Umbraco.Web.Editors int pageSize, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, - string filter = "") + string filter = "", + Guid? dataTypeId = null) { if (pageNumber <= 0) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -523,8 +575,40 @@ namespace Umbraco.Web.Editors var objectType = ConvertToObjectType(type); if (objectType.HasValue) { + IEnumerable entities; long totalRecords; - var entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + + int[] startNodes = null; + switch (type) + { + case UmbracoEntityTypes.Document: + startNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + startNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + } + + var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); + + // root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes + if (id == Constants.System.Root && startNodes.Length > 0 && startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes) + { + if (pageNumber > 0) + return new PagedResult(0, 0, 0); + var nodes = Services.EntityService.GetAll(objectType.Value, startNodes).ToArray(); + if (nodes.Length == 0) + return new PagedResult(0, 0, 0); + if (pageSize < nodes.Length) pageSize = nodes.Length; // bah + var pr = new PagedResult(nodes.Length, pageNumber, pageSize) + { + Items = nodes.Select(Mapper.Map) + }; + return pr; + } + + // else proceed as usual + entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); if (totalRecords == 0) { @@ -653,30 +737,6 @@ namespace Umbraco.Web.Editors return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, searchFrom, ignoreUserStartNodes); } - - private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes entityType) - { - var objectType = ConvertToObjectType(entityType); - if (objectType.HasValue) - { - //TODO: Need to check for Object types that support hierarchic here, some might not. - - return Services.EntityService.GetChildren(id, objectType.Value) - .WhereNotNull() - .Select(Mapper.Map); - } - //now we need to convert the unknown ones - switch (entityType) - { - case UmbracoEntityTypes.Domain: - case UmbracoEntityTypes.Language: - case UmbracoEntityTypes.User: - case UmbracoEntityTypes.Macro: - default: - throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); - } - } - private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, Guid? dataTypeId = null) { var objectType = ConvertToObjectType(entityType); From e7a30d0a5dacad7e821714bc2376cdf7f438144b Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 26 Jun 2019 07:39:34 +0200 Subject: [PATCH 041/776] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1085 - Fixes for gulp build --- .../src/common/resources/entity.resource.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 5e6f1095e4..c85a85bb57 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -327,12 +327,12 @@ function entityResource($q, $http, umbRequestHelper) { * */ getAncestors: function (id, type, options) { - + var args = [ { id: id }, { type: type } ]; - if (options.dataTypeId) { + if (options && options.dataTypeId) { args.push({ dataTypeId: options.dataTypeId }); } @@ -361,7 +361,7 @@ function entityResource($q, $http, umbRequestHelper) { getChildren: function (id, type, options) { var args = [{ id: id }, { type: type }]; - if (options.dataTypeId) { + if (options && options.dataTypeId) { args.push({ dataTypeId: options.dataTypeId }); } From 571a3bc257d3289d0853cd400b799b0d2c21fa40 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 26 Jun 2019 16:48:39 +1000 Subject: [PATCH 042/776] fixes test --- src/Umbraco.Tests/Plugins/PluginManagerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs index ef2c61f6b2..94f56ba6e7 100644 --- a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs +++ b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs @@ -327,7 +327,7 @@ AnotherContentFinder public void Resolves_RestExtensions() { var types = _manager.ResolveRestExtensions(); - Assert.AreEqual(3, types.Count()); + Assert.AreEqual(2, types.Count()); } [Test] From 8033931e3163329a3d841d72364079aa62153a9a Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 26 Jun 2019 09:58:01 +0200 Subject: [PATCH 043/776] https://umbraco.visualstudio.com/D-Team%20Tracker/_workitems/edit/1085 - Fixed merge conflict --- src/Umbraco.Web/Umbraco.Web.csproj | 6076 +++++++++------------------- 1 file changed, 2025 insertions(+), 4051 deletions(-) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 6174fbb511..0b601f8f94 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1,4054 +1,2028 @@ - - - - - 9.0.30729 - 2.0 - {651E1350-91B6-44B7-BD60-7207006D7003} - Debug - AnyCPU - - - - - umbraco - - - JScript - Grid - IE50 - false - Library - Umbraco.Web - OnBuildSuccess - - - - - - - - - - - - - - - 4.0 - v4.5.2 - - ..\ - true - latest - - - bin\Debug\ - false - 285212672 - false - - - DEBUG;TRACE - - - true - 4096 - false - - - false - false - false - false - 4 - full - prompt - AllRules.ruleset - false - Off - latest - - - bin\Release\ - false - 285212672 - false - - - TRACE - bin\Release\umbraco.xml - true - 4096 - false - - - true - false - false - false - 4 - pdbonly - prompt - AllRules.ruleset - false - Off - - - - {07fbc26b-2927-4a22-8d96-d644c667fecc} - UmbracoExamine - - - ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll - True - - - ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll - True - - - ..\packages\ClientDependency.1.9.7\lib\net45\ClientDependency.Core.dll - - - ..\packages\dotless.1.5.2\lib\dotless.Core.dll - - - ..\packages\Examine.0.1.90\lib\net45\Examine.dll - - - ..\packages\HtmlAgilityPack.1.8.8\lib\Net45\HtmlAgilityPack.dll - - - ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - - - ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll - - - ..\packages\Markdown.1.14.7\lib\net45\MarkdownSharp.dll - True - - - ..\packages\Microsoft.AspNet.Identity.Core.2.2.2\lib\net45\Microsoft.AspNet.Identity.Core.dll - - - ..\packages\Microsoft.AspNet.Identity.Owin.2.2.2\lib\net45\Microsoft.AspNet.Identity.Owin.dll - - - ..\packages\Microsoft.AspNet.SignalR.Core.2.4.1\lib\net45\Microsoft.AspNet.SignalR.Core.dll - - - - ..\packages\Microsoft.Owin.4.0.1\lib\net45\Microsoft.Owin.dll - - - ..\packages\Microsoft.Owin.Host.SystemWeb.4.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - - - ..\packages\Microsoft.Owin.Security.4.0.1\lib\net45\Microsoft.Owin.Security.dll - - - ..\packages\Microsoft.Owin.Security.Cookies.4.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll - - - ..\packages\Microsoft.Owin.Security.OAuth.4.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll - - - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - True - - - ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll - True - - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - ..\packages\semver.1.1.2\lib\net451\Semver.dll - - - System - - - - - - System.Data - - - - - System.Drawing - - - - - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll - - - - - - ..\packages\System.Threading.Tasks.Dataflow.4.9.0\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll - - - ..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll - - - - 3.5 - - - - - - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.Helpers.dll - - - ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll - - - ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.dll - - - ..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll - - - ..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll - - - System.Web.Services - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.dll - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Deployment.dll - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Razor.dll - - - - System.XML - - - - {5BA5425F-27A7-4677-865E-82246498AA2E} - SqlCE4Umbraco - - - {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} - Umbraco.Core - - - {6EDD2061-82F2-461B-BB6E-879245A832DE} - umbraco.controls - - - umbraco.businesslogic - {E469A9CE-1BEC-423F-AC44-713CD72457EA} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - {CCD75EC3-63DB-4184-B49D-51C1DD337230} - umbraco.cms - - - {C7CB79F0-1C97-4B33-BFA7-00731B579AE2} - umbraco.datalayer - - - umbraco.interfaces - {511F6D8D-7717-440A-9A57-A507E9A8B27F} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - {D7636876-0756-43CB-A192-138C6F0D5E42} - umbraco.providers - - - - - Properties\SolutionInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - True - True - Reference.map - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - AssignDomain2.aspx - ASPXCodeBehind - - - AssignDomain2.aspx - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - True - True - Strings.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - - Code - - - Code - - - Code - - - Code - - - Code - - - - Code - - - True - True - Settings.settings - - - Code - - - - - Code - - - Code - - - Code - - - Code - - - - ASPXCodeBehind - - - Code - - - Code - - - Code - - - - delete.aspx - ASPXCodeBehind - - - delete.aspx - - - editContent.aspx - ASPXCodeBehind - - - editContent.aspx - - - preview.aspx - ASPXCodeBehind - - - preview.aspx - - - publish.aspx - ASPXCodeBehind - - - publish.aspx - - - - - - ASPXCodeBehind - - - - - - - ProgressBar.ascx - ASPXCodeBehind - - - ProgressBar.ascx - - - - ASPXCodeBehind - - - Component - - - - - Code - - - - - - - - - - FeedProxy.aspx - ASPXCodeBehind - - - FeedProxy.aspx - - - EditRelationType.aspx - ASPXCodeBehind - - - EditRelationType.aspx - - - NewRelationType.aspx - ASPXCodeBehind - - - NewRelationType.aspx - - - - RelationTypesWebService.asmx - Component - - - - - Preview.aspx - ASPXCodeBehind - - - Preview.aspx - - - MemberSearch.ascx - ASPXCodeBehind - - - MemberSearch.ascx - - - - - - xsltVisualize.aspx - ASPXCodeBehind - - - xsltVisualize.aspx - - - insertMasterpageContent.aspx - ASPXCodeBehind - - - insertMasterpageContent.aspx - - - insertMasterpagePlaceholder.aspx - ASPXCodeBehind - - - insertMasterpagePlaceholder.aspx - - - republish.aspx - ASPXCodeBehind - - - republish.aspx - - - search.aspx - ASPXCodeBehind - - - search.aspx - - - SendPublish.aspx - ASPXCodeBehind - - - SendPublish.aspx - - - Code - - - Code - - - assemblyBrowser.aspx - ASPXCodeBehind - - - assemblyBrowser.aspx - - - editPackage.aspx - ASPXCodeBehind - - - editPackage.aspx - - - getXsltStatus.asmx - Component - - - xsltChooseExtension.aspx - ASPXCodeBehind - - - xsltChooseExtension.aspx - - - xsltInsertValueOf.aspx - ASPXCodeBehind - - - xsltInsertValueOf.aspx - - - exportDocumenttype.aspx - ASPXCodeBehind - - - importDocumenttype.aspx - ASPXCodeBehind - - - rollBack.aspx - ASPXCodeBehind - - - rollBack.aspx - - - sendToTranslation.aspx - ASPXCodeBehind - - - sendToTranslation.aspx - - - viewAuditTrail.aspx - ASPXCodeBehind - - - viewAuditTrail.aspx - - - EditMemberGroup.aspx - ASPXCodeBehind - - - EditMemberGroup.aspx - - - search.aspx - ASPXCodeBehind - - - search.aspx - - - ViewMembers.aspx - ASPXCodeBehind - - - ViewMembers.aspx - - - Code - - - - - - - - - - tinymce3tinymceCompress.aspx - ASPXCodeBehind - - - tinymce3tinymceCompress.aspx - - - QuickSearchHandler.ashx - - - DictionaryItemList.aspx - ASPXCodeBehind - - - DictionaryItemList.aspx - - - editLanguage.aspx - ASPXCodeBehind - - - editLanguage.aspx - - - - - - Code - - - - - - - - - - - - - - - - - MacroContainerService.asmx - Component - - - TagsAutoCompleteHandler.ashx - - - TreeClientService.asmx - Component - - - - - - - - True - True - Resources.resx - - - default.aspx - ASPXCodeBehind - - - default.aspx - - - details.aspx - ASPXCodeBehind - - - details.aspx - - - preview.aspx - ASPXCodeBehind - - - preview.aspx - - - xml.aspx - ASPXCodeBehind - - - xml.aspx - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - TreeDataService.ashx - - - - - - - XmlTree.xsd - - - - - CacheRefresher.asmx - Component - - - CheckForUpgrade.asmx - Component - - - CMSNode.asmx - Component - - - codeEditorSave.asmx - Component - - - legacyAjaxCalls.asmx - Component - - - nodeSorter.asmx - Component - - - progressStatus.asmx - Component - - - publication.asmx - Component - - - - ASPXCodeBehind - - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - - - - - - True - True - Reference.map - - - - - - - - - - - - Component - - - - Component - - - - - Mvc\web.config - - - - MSDiscoCodeGenerator - Reference.cs - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - ASPXCodeBehind - - - - - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - - - - - - - Reference.map - - - Reference.map - - - SettingsSingleFileGenerator - Settings1.Designer.cs - - - - - - ASPXCodeBehind - - - - - - - - - - Form - - - Designer - - - - - Form - - - - - - - Form - - - - - - - - - - XmlTree.xsd - - - - - - MSDiscoCodeGenerator - Reference.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - ResXFileCodeGenerator - Strings.Designer.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - - - - package.xsd - - - umbraco.xsd - - - umbraco.xsd - - - umbraco.xsd - - - - - - - - Dynamic - Web References\org.umbraco.our\ - https://our.umbraco.com/umbraco/webservices/api/repository.asmx - - - - - Settings - umbraco_org_umbraco_our_Repository - - - Dynamic - Web References\org.umbraco.update\ - http://update.umbraco.org/checkforupgrade.asmx - - - - - Settings - umbraco_org_umbraco_update_CheckForUpgrade - - - - - - - 11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v15.0 - - - - - - - - - - - - - - -  - - - 9.0.30729 - 2.0 - {651E1350-91B6-44B7-BD60-7207006D7003} - Debug - AnyCPU - - - - - umbraco - - - JScript - Grid - IE50 - false - Library - Umbraco.Web - OnBuildSuccess - - - - - - - - - - - - - - - 4.0 - v4.5.2 - - ..\ - true - latest - - - bin\Debug\ - false - 285212672 - false - - - DEBUG;TRACE - - - true - 4096 - false - - - false - false - false - false - 4 - full - prompt - AllRules.ruleset - false - Off - latest - - - bin\Release\ - false - 285212672 - false - - - TRACE - bin\Release\umbraco.xml - true - 4096 - false - - - true - false - false - false - 4 - pdbonly - prompt - AllRules.ruleset - false - Off - - - - {07fbc26b-2927-4a22-8d96-d644c667fecc} - UmbracoExamine - - - ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll - True - - - ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll - True - - - ..\packages\ClientDependency.1.9.7\lib\net45\ClientDependency.Core.dll - - - ..\packages\dotless.1.5.2\lib\dotless.Core.dll - - - ..\packages\Examine.0.1.90\lib\net45\Examine.dll - - - ..\packages\HtmlAgilityPack.1.8.8\lib\Net45\HtmlAgilityPack.dll - - - ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - - - ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll - - - ..\packages\Markdown.1.14.7\lib\net45\MarkdownSharp.dll - True - - - ..\packages\Microsoft.AspNet.Identity.Core.2.2.2\lib\net45\Microsoft.AspNet.Identity.Core.dll - - - ..\packages\Microsoft.AspNet.Identity.Owin.2.2.2\lib\net45\Microsoft.AspNet.Identity.Owin.dll - - - ..\packages\Microsoft.AspNet.SignalR.Core.2.4.1\lib\net45\Microsoft.AspNet.SignalR.Core.dll - - - - ..\packages\Microsoft.Owin.4.0.1\lib\net45\Microsoft.Owin.dll - - - ..\packages\Microsoft.Owin.Host.SystemWeb.4.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - - - ..\packages\Microsoft.Owin.Security.4.0.1\lib\net45\Microsoft.Owin.Security.dll - - - ..\packages\Microsoft.Owin.Security.Cookies.4.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll - - - ..\packages\Microsoft.Owin.Security.OAuth.4.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll - - - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - True - - - ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll - True - - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - ..\packages\semver.1.1.2\lib\net451\Semver.dll - - - System - - - - - - System.Data - - - - - System.Drawing - - - - - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll - - - - - - ..\packages\System.Threading.Tasks.Dataflow.4.9.0\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll - - - ..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll - - - - 3.5 - - - - - - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.Helpers.dll - - - ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll - - - ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.dll - - - ..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll - - - ..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll - - - System.Web.Services - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.dll - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Deployment.dll - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Razor.dll - - - - System.XML - - - - {5BA5425F-27A7-4677-865E-82246498AA2E} - SqlCE4Umbraco - - - {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} - Umbraco.Core - - - {6EDD2061-82F2-461B-BB6E-879245A832DE} - umbraco.controls - - - umbraco.businesslogic - {E469A9CE-1BEC-423F-AC44-713CD72457EA} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - {CCD75EC3-63DB-4184-B49D-51C1DD337230} - umbraco.cms - - - {C7CB79F0-1C97-4B33-BFA7-00731B579AE2} - umbraco.datalayer - - - umbraco.interfaces - {511F6D8D-7717-440A-9A57-A507E9A8B27F} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - {D7636876-0756-43CB-A192-138C6F0D5E42} - umbraco.providers - - - - - Properties\SolutionInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - True - True - Reference.map - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - AssignDomain2.aspx - ASPXCodeBehind - - - AssignDomain2.aspx - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - True - True - Strings.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - - Code - - - Code - - - Code - - - Code - - - Code - - - - Code - - - True - True - Settings.settings - - - Code - - - - - Code - - - Code - - - Code - - - Code - - - - ASPXCodeBehind - - - Code - - - Code - - - Code - - - - delete.aspx - ASPXCodeBehind - - - delete.aspx - - - editContent.aspx - ASPXCodeBehind - - - editContent.aspx - - - preview.aspx - ASPXCodeBehind - - - preview.aspx - - - publish.aspx - ASPXCodeBehind - - - publish.aspx - - - - - - ASPXCodeBehind - - - - - - - ProgressBar.ascx - ASPXCodeBehind - - - ProgressBar.ascx - - - - ASPXCodeBehind - - - Component - - - - - Code - - - - - - - - - - FeedProxy.aspx - ASPXCodeBehind - - - FeedProxy.aspx - - - EditRelationType.aspx - ASPXCodeBehind - - - EditRelationType.aspx - - - NewRelationType.aspx - ASPXCodeBehind - - - NewRelationType.aspx - - - - RelationTypesWebService.asmx - Component - - - - - Preview.aspx - ASPXCodeBehind - - - Preview.aspx - - - MemberSearch.ascx - ASPXCodeBehind - - - MemberSearch.ascx - - - - - - xsltVisualize.aspx - ASPXCodeBehind - - - xsltVisualize.aspx - - - insertMasterpageContent.aspx - ASPXCodeBehind - - - insertMasterpageContent.aspx - - - insertMasterpagePlaceholder.aspx - ASPXCodeBehind - - - insertMasterpagePlaceholder.aspx - - - republish.aspx - ASPXCodeBehind - - - republish.aspx - - - search.aspx - ASPXCodeBehind - - - search.aspx - - - SendPublish.aspx - ASPXCodeBehind - - - SendPublish.aspx - - - Code - - - Code - - - assemblyBrowser.aspx - ASPXCodeBehind - - - assemblyBrowser.aspx - - - editPackage.aspx - ASPXCodeBehind - - - editPackage.aspx - - - getXsltStatus.asmx - Component - - - xsltChooseExtension.aspx - ASPXCodeBehind - - - xsltChooseExtension.aspx - - - xsltInsertValueOf.aspx - ASPXCodeBehind - - - xsltInsertValueOf.aspx - - - exportDocumenttype.aspx - ASPXCodeBehind - - - importDocumenttype.aspx - ASPXCodeBehind - - - rollBack.aspx - ASPXCodeBehind - - - rollBack.aspx - - - sendToTranslation.aspx - ASPXCodeBehind - - - sendToTranslation.aspx - - - viewAuditTrail.aspx - ASPXCodeBehind - - - viewAuditTrail.aspx - - - EditMemberGroup.aspx - ASPXCodeBehind - - - EditMemberGroup.aspx - - - search.aspx - ASPXCodeBehind - - - search.aspx - - - ViewMembers.aspx - ASPXCodeBehind - - - ViewMembers.aspx - - - Code - - - - - - - - - - tinymce3tinymceCompress.aspx - ASPXCodeBehind - - - tinymce3tinymceCompress.aspx - - - QuickSearchHandler.ashx - - - DictionaryItemList.aspx - ASPXCodeBehind - - - DictionaryItemList.aspx - - - editLanguage.aspx - ASPXCodeBehind - - - editLanguage.aspx - - - - - - Code - - - - - - - - - - - - - - - - - MacroContainerService.asmx - Component - - - TagsAutoCompleteHandler.ashx - - - TreeClientService.asmx - Component - - - - - - - - True - True - Resources.resx - - - default.aspx - ASPXCodeBehind - - - default.aspx - - - details.aspx - ASPXCodeBehind - - - details.aspx - - - preview.aspx - ASPXCodeBehind - - - preview.aspx - - - xml.aspx - ASPXCodeBehind - - - xml.aspx - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - TreeDataService.ashx - - - - - - - XmlTree.xsd - - - - - CacheRefresher.asmx - Component - - - CheckForUpgrade.asmx - Component - - - CMSNode.asmx - Component - - - codeEditorSave.asmx - Component - - - legacyAjaxCalls.asmx - Component - - - nodeSorter.asmx - Component - - - progressStatus.asmx - Component - - - publication.asmx - Component - - - - ASPXCodeBehind - - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - - - - - - True - True - Reference.map - - - - - - - - - - - Component - - - - Component - - - - - Mvc\web.config - - - - MSDiscoCodeGenerator - Reference.cs - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - ASPXCodeBehind - - - - - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - - - - - - - Reference.map - - - Reference.map - - - SettingsSingleFileGenerator - Settings1.Designer.cs - - - - - - ASPXCodeBehind - - - - - - - - - - Form - - - Designer - - - - - Form - - - - - - - Form - - - - - - - - - - XmlTree.xsd - - - - - - MSDiscoCodeGenerator - Reference.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - ResXFileCodeGenerator - Strings.Designer.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - - - - package.xsd - - - umbraco.xsd - - - umbraco.xsd - - - umbraco.xsd - - - - - - - - Dynamic - Web References\org.umbraco.our\ - https://our.umbraco.com/umbraco/webservices/api/repository.asmx - - - - - Settings - umbraco_org_umbraco_our_Repository - - - Dynamic - Web References\org.umbraco.update\ - http://update.umbraco.org/checkforupgrade.asmx - - - - - Settings - umbraco_org_umbraco_update_CheckForUpgrade - - - - - - - 11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v15.0 - - - - - - - - - - - - - - - + + + 9.0.30729 + 2.0 + {651E1350-91B6-44B7-BD60-7207006D7003} + Debug + AnyCPU + + + + + umbraco + + + JScript + Grid + IE50 + false + Library + Umbraco.Web + OnBuildSuccess + + + + + + + + + + + + + + + 4.0 + v4.5.2 + + ..\ + true + latest + + + bin\Debug\ + false + 285212672 + false + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + false + 4 + full + prompt + AllRules.ruleset + false + Off + latest + + + bin\Release\ + false + 285212672 + false + + + TRACE + bin\Release\umbraco.xml + true + 4096 + false + + + true + false + false + false + 4 + pdbonly + prompt + AllRules.ruleset + false + Off + + + + {07fbc26b-2927-4a22-8d96-d644c667fecc} + UmbracoExamine + + + ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll + True + + + ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll + True + + + ..\packages\ClientDependency.1.9.7\lib\net45\ClientDependency.Core.dll + + + ..\packages\dotless.1.5.2\lib\dotless.Core.dll + + + ..\packages\Examine.0.1.90\lib\net45\Examine.dll + + + ..\packages\HtmlAgilityPack.1.8.8\lib\Net45\HtmlAgilityPack.dll + + + ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll + + + ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll + + + ..\packages\Markdown.1.14.7\lib\net45\MarkdownSharp.dll + True + + + ..\packages\Microsoft.AspNet.Identity.Core.2.2.2\lib\net45\Microsoft.AspNet.Identity.Core.dll + + + ..\packages\Microsoft.AspNet.Identity.Owin.2.2.2\lib\net45\Microsoft.AspNet.Identity.Owin.dll + + + ..\packages\Microsoft.AspNet.SignalR.Core.2.4.1\lib\net45\Microsoft.AspNet.SignalR.Core.dll + + + + ..\packages\Microsoft.Owin.4.0.1\lib\net45\Microsoft.Owin.dll + + + ..\packages\Microsoft.Owin.Host.SystemWeb.4.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + + + ..\packages\Microsoft.Owin.Security.4.0.1\lib\net45\Microsoft.Owin.Security.dll + + + ..\packages\Microsoft.Owin.Security.Cookies.4.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll + + + ..\packages\Microsoft.Owin.Security.OAuth.4.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll + + + ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + True + + + ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll + True + + + ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + ..\packages\semver.1.1.2\lib\net451\Semver.dll + + + System + + + + + + System.Data + + + + + System.Drawing + + + + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll + + + + + + ..\packages\System.Threading.Tasks.Dataflow.4.9.0\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll + + + + 3.5 + + + + + + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.Helpers.dll + + + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll + + + ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.dll + + + ..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll + + + ..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll + + + System.Web.Services + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.dll + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Deployment.dll + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Razor.dll + + + + System.XML + + + + {5BA5425F-27A7-4677-865E-82246498AA2E} + SqlCE4Umbraco + + + {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} + Umbraco.Core + + + {6EDD2061-82F2-461B-BB6E-879245A832DE} + umbraco.controls + + + umbraco.businesslogic + {E469A9CE-1BEC-423F-AC44-713CD72457EA} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + {CCD75EC3-63DB-4184-B49D-51C1DD337230} + umbraco.cms + + + {C7CB79F0-1C97-4B33-BFA7-00731B579AE2} + umbraco.datalayer + + + umbraco.interfaces + {511F6D8D-7717-440A-9A57-A507E9A8B27F} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + {D7636876-0756-43CB-A192-138C6F0D5E42} + umbraco.providers + + + + + Properties\SolutionInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + True + True + Reference.map + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + AssignDomain2.aspx + ASPXCodeBehind + + + AssignDomain2.aspx + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + True + True + Strings.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + + Code + + + Code + + + Code + + + Code + + + Code + + + + Code + + + True + True + Settings.settings + + + Code + + + + + Code + + + Code + + + Code + + + Code + + + + ASPXCodeBehind + + + Code + + + Code + + + Code + + + + delete.aspx + ASPXCodeBehind + + + delete.aspx + + + editContent.aspx + ASPXCodeBehind + + + editContent.aspx + + + preview.aspx + ASPXCodeBehind + + + preview.aspx + + + publish.aspx + ASPXCodeBehind + + + publish.aspx + + + + + + ASPXCodeBehind + + + + + + + ProgressBar.ascx + ASPXCodeBehind + + + ProgressBar.ascx + + + + ASPXCodeBehind + + + Component + + + + + Code + + + + + + + + + + FeedProxy.aspx + ASPXCodeBehind + + + FeedProxy.aspx + + + EditRelationType.aspx + ASPXCodeBehind + + + EditRelationType.aspx + + + NewRelationType.aspx + ASPXCodeBehind + + + NewRelationType.aspx + + + + RelationTypesWebService.asmx + Component + + + + + Preview.aspx + ASPXCodeBehind + + + Preview.aspx + + + MemberSearch.ascx + ASPXCodeBehind + + + MemberSearch.ascx + + + + + + xsltVisualize.aspx + ASPXCodeBehind + + + xsltVisualize.aspx + + + insertMasterpageContent.aspx + ASPXCodeBehind + + + insertMasterpageContent.aspx + + + insertMasterpagePlaceholder.aspx + ASPXCodeBehind + + + insertMasterpagePlaceholder.aspx + + + republish.aspx + ASPXCodeBehind + + + republish.aspx + + + search.aspx + ASPXCodeBehind + + + search.aspx + + + SendPublish.aspx + ASPXCodeBehind + + + SendPublish.aspx + + + Code + + + Code + + + assemblyBrowser.aspx + ASPXCodeBehind + + + assemblyBrowser.aspx + + + editPackage.aspx + ASPXCodeBehind + + + editPackage.aspx + + + getXsltStatus.asmx + Component + + + xsltChooseExtension.aspx + ASPXCodeBehind + + + xsltChooseExtension.aspx + + + xsltInsertValueOf.aspx + ASPXCodeBehind + + + xsltInsertValueOf.aspx + + + exportDocumenttype.aspx + ASPXCodeBehind + + + importDocumenttype.aspx + ASPXCodeBehind + + + rollBack.aspx + ASPXCodeBehind + + + rollBack.aspx + + + sendToTranslation.aspx + ASPXCodeBehind + + + sendToTranslation.aspx + + + viewAuditTrail.aspx + ASPXCodeBehind + + + viewAuditTrail.aspx + + + EditMemberGroup.aspx + ASPXCodeBehind + + + EditMemberGroup.aspx + + + search.aspx + ASPXCodeBehind + + + search.aspx + + + ViewMembers.aspx + ASPXCodeBehind + + + ViewMembers.aspx + + + Code + + + + + + + + + + tinymce3tinymceCompress.aspx + ASPXCodeBehind + + + tinymce3tinymceCompress.aspx + + + QuickSearchHandler.ashx + + + DictionaryItemList.aspx + ASPXCodeBehind + + + DictionaryItemList.aspx + + + editLanguage.aspx + ASPXCodeBehind + + + editLanguage.aspx + + + + + + Code + + + + + + + + + + + + + + + + + MacroContainerService.asmx + Component + + + TagsAutoCompleteHandler.ashx + + + TreeClientService.asmx + Component + + + + + + + + True + True + Resources.resx + + + default.aspx + ASPXCodeBehind + + + default.aspx + + + details.aspx + ASPXCodeBehind + + + details.aspx + + + preview.aspx + ASPXCodeBehind + + + preview.aspx + + + xml.aspx + ASPXCodeBehind + + + xml.aspx + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + TreeDataService.ashx + + + + + + + XmlTree.xsd + + + + + CacheRefresher.asmx + Component + + + CheckForUpgrade.asmx + Component + + + CMSNode.asmx + Component + + + codeEditorSave.asmx + Component + + + legacyAjaxCalls.asmx + Component + + + nodeSorter.asmx + Component + + + progressStatus.asmx + Component + + + publication.asmx + Component + + + + ASPXCodeBehind + + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + + + + + + True + True + Reference.map + + + + + + + + + + + Component + + + + Component + + + + + Mvc\web.config + + + + MSDiscoCodeGenerator + Reference.cs + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + ASPXCodeBehind + + + + + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + + + + + + + Reference.map + + + Reference.map + + + SettingsSingleFileGenerator + Settings1.Designer.cs + + + + + + ASPXCodeBehind + + + + + + + + + + Form + + + Designer + + + + + Form + + + + + + + Form + + + + + + + + + + XmlTree.xsd + + + + + + MSDiscoCodeGenerator + Reference.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + ResXFileCodeGenerator + Strings.Designer.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + + + + + package.xsd + + + umbraco.xsd + + + umbraco.xsd + + + umbraco.xsd + + + + + + + + Dynamic + Web References\org.umbraco.our\ + https://our.umbraco.com/umbraco/webservices/api/repository.asmx + + + + + Settings + umbraco_org_umbraco_our_Repository + + + Dynamic + Web References\org.umbraco.update\ + http://update.umbraco.org/checkforupgrade.asmx + + + + + Settings + umbraco_org_umbraco_update_CheckForUpgrade + + + + + + + 11.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v15.0 + + + + + + + + + + + + + + + \ No newline at end of file From fad33a97b24f835c8a09025bcbdff1ac7d52ce96 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 27 Jun 2019 15:47:25 +1000 Subject: [PATCH 044/776] don't throw JS exception when no MediaPath, instead warn. --- .../src/common/services/mediahelper.service.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js index 2f31d36b62..38a3de8202 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js @@ -3,7 +3,7 @@ * @name umbraco.services.mediaHelper * @description A helper object used for dealing with media items **/ -function mediaHelper(umbRequestHelper) { +function mediaHelper(umbRequestHelper, $log) { //container of fileresolvers var _mediaFileResolvers = {}; @@ -144,7 +144,9 @@ function mediaHelper(umbRequestHelper) { resolveFileFromEntity: function (mediaEntity, thumbnail) { if (!angular.isObject(mediaEntity.metaData) || !mediaEntity.metaData.MediaPath) { - throw "Cannot resolve the file url from the mediaEntity, it does not contain the required metaData"; + //don't throw since this image legitimately might not contain a media path, but output a warning + $log.warn("Cannot resolve the file url from the mediaEntity, it does not contain the required metaData"); + return null; } if (thumbnail) { From 44e567aa34399660c3e61068b9b47b8781fe5715 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 27 Jun 2019 12:42:14 +0200 Subject: [PATCH 045/776] Prepare MB future - implement manifest filters to tweak manifests after they have been parsed - make IEditorValidator public - stop relying on InternalsVisibleTo Umbraco.ModelsBuilder --- src/Umbraco.Core/CompositionExtensions.cs | 10 ++++++- src/Umbraco.Core/ConfigsExtensions.cs | 8 +++++- .../Configuration/Grid/GridConfig.cs | 5 ++-- .../Configuration/Grid/GridEditorsConfig.cs | 13 ++++----- src/Umbraco.Core/Manifest/IManifestFilter.cs | 19 +++++++++++++ .../Manifest/ManifestFilterCollection.cs | 28 +++++++++++++++++++ .../ManifestFilterCollectionBuilder.cs | 12 ++++++++ src/Umbraco.Core/Manifest/ManifestParser.cs | 11 ++++++-- src/Umbraco.Core/Manifest/PackageManifest.cs | 10 +++++++ .../Runtime/CoreInitialComposer.cs | 5 +++- src/Umbraco.Core/Umbraco.Core.csproj | 3 ++ .../Manifest/ManifestParserTests.cs | 2 +- src/Umbraco.Web/Editors/IEditorValidator.cs | 2 +- src/Umbraco.Web/Properties/AssemblyInfo.cs | 3 -- 14 files changed, 110 insertions(+), 21 deletions(-) create mode 100644 src/Umbraco.Core/Manifest/IManifestFilter.cs create mode 100644 src/Umbraco.Core/Manifest/ManifestFilterCollection.cs create mode 100644 src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs diff --git a/src/Umbraco.Core/CompositionExtensions.cs b/src/Umbraco.Core/CompositionExtensions.cs index 828a577c34..5dd33c2a60 100644 --- a/src/Umbraco.Core/CompositionExtensions.cs +++ b/src/Umbraco.Core/CompositionExtensions.cs @@ -4,6 +4,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Dictionary; using Umbraco.Core.IO; using Umbraco.Core.Logging.Viewer; +using Umbraco.Core.Manifest; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PackageActions; using Umbraco.Core.Persistence.Mappers; @@ -66,9 +67,16 @@ namespace Umbraco.Core /// Gets the validators collection builder. /// /// The composition. - internal static ManifestValueValidatorCollectionBuilder Validators(this Composition composition) + internal static ManifestValueValidatorCollectionBuilder ManifestValueValidators(this Composition composition) => composition.WithCollectionBuilder(); + /// + /// Gets the manifest filter collection builder. + /// + /// The composition. + public static ManifestFilterCollectionBuilder ManifestFilters(this Composition composition) + => composition.WithCollectionBuilder(); + /// /// Gets the components collection builder. /// diff --git a/src/Umbraco.Core/ConfigsExtensions.cs b/src/Umbraco.Core/ConfigsExtensions.cs index 6fdf7ea3b9..d1672c6c7f 100644 --- a/src/Umbraco.Core/ConfigsExtensions.cs +++ b/src/Umbraco.Core/ConfigsExtensions.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Configuration.HealthChecks; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Manifest; namespace Umbraco.Core { @@ -41,7 +42,12 @@ namespace Umbraco.Core configs.Add(() => new CoreDebug()); // GridConfig depends on runtime caches, manifest parsers... and cannot be available during composition - configs.Add(factory => new GridConfig(factory.GetInstance(), factory.GetInstance(), configDir, factory.GetInstance().Debug)); + configs.Add(factory => new GridConfig( + factory.GetInstance(), + factory.GetInstance(), + configDir, + factory.GetInstance(), + factory.GetInstance().Debug)); } } } diff --git a/src/Umbraco.Core/Configuration/Grid/GridConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs index b2dae09fc9..9aead74886 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs @@ -1,14 +1,15 @@ using System.IO; using Umbraco.Core.Cache; using Umbraco.Core.Logging; +using Umbraco.Core.Manifest; namespace Umbraco.Core.Configuration.Grid { class GridConfig : IGridConfig { - public GridConfig(ILogger logger, AppCaches appCaches, DirectoryInfo configFolder, bool isDebug) + public GridConfig(ILogger logger, AppCaches appCaches, DirectoryInfo configFolder, ManifestParser manifestParser, bool isDebug) { - EditorsConfig = new GridEditorsConfig(logger, appCaches, configFolder, isDebug); + EditorsConfig = new GridEditorsConfig(logger, appCaches, configFolder, manifestParser, isDebug); } public IGridEditorsConfig EditorsConfig { get; } diff --git a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs index 0e7ef62c58..d434da8c70 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; using System.IO; -using Newtonsoft.Json.Linq; using Umbraco.Core.Cache; -using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.PropertyEditors; @@ -15,13 +13,15 @@ namespace Umbraco.Core.Configuration.Grid private readonly ILogger _logger; private readonly AppCaches _appCaches; private readonly DirectoryInfo _configFolder; + private readonly ManifestParser _manifestParser; private readonly bool _isDebug; - public GridEditorsConfig(ILogger logger, AppCaches appCaches, DirectoryInfo configFolder, bool isDebug) + public GridEditorsConfig(ILogger logger, AppCaches appCaches, DirectoryInfo configFolder, ManifestParser manifestParser, bool isDebug) { _logger = logger; _appCaches = appCaches; _configFolder = configFolder; + _manifestParser = manifestParser; _isDebug = isDebug; } @@ -31,9 +31,6 @@ namespace Umbraco.Core.Configuration.Grid { List GetResult() { - // TODO: should use the common one somehow! + ignoring _appPlugins here! - var parser = new ManifestParser(_appCaches, Current.ManifestValidators, _logger); - var editors = new List(); var gridConfig = Path.Combine(_configFolder.FullName, "grid.editors.config.js"); if (File.Exists(gridConfig)) @@ -42,7 +39,7 @@ namespace Umbraco.Core.Configuration.Grid try { - editors.AddRange(parser.ParseGridEditors(sourceString)); + editors.AddRange(_manifestParser.ParseGridEditors(sourceString)); } catch (Exception ex) { @@ -51,7 +48,7 @@ namespace Umbraco.Core.Configuration.Grid } // add manifest editors, skip duplicates - foreach (var gridEditor in parser.Manifest.GridEditors) + foreach (var gridEditor in _manifestParser.Manifest.GridEditors) { if (editors.Contains(gridEditor) == false) editors.Add(gridEditor); } diff --git a/src/Umbraco.Core/Manifest/IManifestFilter.cs b/src/Umbraco.Core/Manifest/IManifestFilter.cs new file mode 100644 index 0000000000..505f13d385 --- /dev/null +++ b/src/Umbraco.Core/Manifest/IManifestFilter.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Manifest +{ + /// + /// Provides filtering for package manifests. + /// + public interface IManifestFilter + { + /// + /// Filters package manifests. + /// + /// The package manifests. + /// + /// It is possible to remove, change, or add manifests. + /// + void Filter(List manifests); + } +} diff --git a/src/Umbraco.Core/Manifest/ManifestFilterCollection.cs b/src/Umbraco.Core/Manifest/ManifestFilterCollection.cs new file mode 100644 index 0000000000..febdb7e356 --- /dev/null +++ b/src/Umbraco.Core/Manifest/ManifestFilterCollection.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Manifest +{ + /// + /// Contains the manifest filters. + /// + public class ManifestFilterCollection : BuilderCollectionBase + { + /// + /// Initializes a new instance of the class. + /// + public ManifestFilterCollection(IEnumerable items) + : base(items) + { } + + /// + /// Filters package manifests. + /// + /// The package manifests. + public void Filter(List manifests) + { + foreach (var filter in this) + filter.Filter(manifests); + } + } +} diff --git a/src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs b/src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs new file mode 100644 index 0000000000..4d98700f93 --- /dev/null +++ b/src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs @@ -0,0 +1,12 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Manifest +{ + public class ManifestFilterCollectionBuilder : OrderedCollectionBuilderBase + { + protected override ManifestFilterCollectionBuilder This => this; + + // do NOT cache this, it's only used once + protected override Lifetime CollectionLifetime => Lifetime.Transient; + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Manifest/ManifestParser.cs b/src/Umbraco.Core/Manifest/ManifestParser.cs index dc40cd90a2..efd9e92b1f 100644 --- a/src/Umbraco.Core/Manifest/ManifestParser.cs +++ b/src/Umbraco.Core/Manifest/ManifestParser.cs @@ -22,24 +22,26 @@ namespace Umbraco.Core.Manifest private readonly IAppPolicyCache _cache; private readonly ILogger _logger; private readonly ManifestValueValidatorCollection _validators; + private readonly ManifestFilterCollection _filters; private string _path; /// /// Initializes a new instance of the class. /// - public ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ILogger logger) - : this(appCaches, validators, "~/App_Plugins", logger) + public ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ManifestFilterCollection filters, ILogger logger) + : this(appCaches, validators, filters, "~/App_Plugins", logger) { } /// /// Initializes a new instance of the class. /// - private ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, string path, ILogger logger) + private ManifestParser(AppCaches appCaches, ManifestValueValidatorCollection validators, ManifestFilterCollection filters, string path, ILogger logger) { if (appCaches == null) throw new ArgumentNullException(nameof(appCaches)); _cache = appCaches.RuntimeCache; _validators = validators ?? throw new ArgumentNullException(nameof(validators)); + _filters = filters ?? throw new ArgumentNullException(nameof(filters)); if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullOrEmptyException(nameof(path)); Path = path; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -78,6 +80,7 @@ namespace Umbraco.Core.Manifest if (string.IsNullOrWhiteSpace(text)) continue; var manifest = ParseManifest(text); + manifest.Source = path; manifests.Add(manifest); } catch (Exception e) @@ -86,6 +89,8 @@ namespace Umbraco.Core.Manifest } } + _filters.Filter(manifests); + return manifests; } diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index 475ee8a7f8..e50eb69467 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -9,6 +9,16 @@ namespace Umbraco.Core.Manifest /// public class PackageManifest { + /// + /// Gets the source path of the manifest. + /// + /// + /// Gets the full absolute file path of the manifest, + /// using system directory separators. + /// + [JsonIgnore] + public string Source { get; set; } + /// /// Gets or sets the scripts listed in the manifest. /// diff --git a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs index f32a46f3f8..1f004846d0 100644 --- a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs +++ b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs @@ -58,7 +58,7 @@ namespace Umbraco.Core.Runtime composition.RegisterUnique(); // register our predefined validators - composition.WithCollectionBuilder() + composition.ManifestValueValidators() .Add() .Add() .Add() @@ -66,6 +66,9 @@ namespace Umbraco.Core.Runtime .Add() .Add(); + // register the manifest filter collection builder (collection is empty by default) + composition.ManifestFilters(); + // properties and parameters derive from data editors composition.WithCollectionBuilder() .Add(() => composition.TypeLoader.GetDataEditors()); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 7ac1e9d06e..dcd15427e9 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -219,6 +219,9 @@ + + + diff --git a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs index 6605bc4546..1c90f68d62 100644 --- a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs +++ b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs @@ -44,7 +44,7 @@ namespace Umbraco.Tests.Manifest new RequiredValidator(Mock.Of()), new RegexValidator(Mock.Of(), null) }; - _parser = new ManifestParser(AppCaches.Disabled, new ManifestValueValidatorCollection(validators), Mock.Of()); + _parser = new ManifestParser(AppCaches.Disabled, new ManifestValueValidatorCollection(validators), new ManifestFilterCollection(Array.Empty()), Mock.Of()); } [Test] diff --git a/src/Umbraco.Web/Editors/IEditorValidator.cs b/src/Umbraco.Web/Editors/IEditorValidator.cs index bec978788c..d469d9d9eb 100644 --- a/src/Umbraco.Web/Editors/IEditorValidator.cs +++ b/src/Umbraco.Web/Editors/IEditorValidator.cs @@ -21,7 +21,7 @@ namespace Umbraco.Web.Editors /// /// Provides a general object validator. /// - internal interface IEditorValidator : IDiscoverable + public interface IEditorValidator : IDiscoverable { /// /// Gets the object type validated by this validator. diff --git a/src/Umbraco.Web/Properties/AssemblyInfo.cs b/src/Umbraco.Web/Properties/AssemblyInfo.cs index 359e8ac3b6..9a1b248284 100644 --- a/src/Umbraco.Web/Properties/AssemblyInfo.cs +++ b/src/Umbraco.Web/Properties/AssemblyInfo.cs @@ -32,9 +32,6 @@ using System.Runtime.InteropServices; // Umbraco Headless [assembly: InternalsVisibleTo("Umbraco.Headless")] -// Umbraco ModelsBuilder -[assembly: InternalsVisibleTo("Umbraco.ModelsBuilder")] - // code analysis // IDE1006 is broken, wants _value syntax for consts, etc - and it's even confusing ppl at MS, kill it [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "~_~")] From a018ed9e526fbde52ebed7f6afafb8f92150d5ba Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Thu, 27 Jun 2019 13:27:34 +0100 Subject: [PATCH 046/776] V7: Less-Urgent-But-Still-Quite-Important - Add Giphy EmbeddedMedia support (#5736) --- src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config | 7 +++++++ src/Umbraco.Web.UI/config/EmbeddedMedia.config | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config b/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config index 603683cf30..f8c2be088d 100644 --- a/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config +++ b/src/Umbraco.Web.UI/config/EmbeddedMedia.Release.config @@ -130,5 +130,12 @@ + + + + + + + diff --git a/src/Umbraco.Web.UI/config/EmbeddedMedia.config b/src/Umbraco.Web.UI/config/EmbeddedMedia.config index ccefad2983..d88e1d2b4d 100644 --- a/src/Umbraco.Web.UI/config/EmbeddedMedia.config +++ b/src/Umbraco.Web.UI/config/EmbeddedMedia.config @@ -130,5 +130,12 @@ + + + + + + + From f370e738da2fda81e7f48bc5d272ad1a4ed4c281 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 27 Jun 2019 21:34:19 +0200 Subject: [PATCH 047/776] =?UTF-8?q?V8:=20Use=20an=20overlay=20style=20dial?= =?UTF-8?q?og=20for=20deleting=20Nested=20Content=20ite=E2=80=A6=20(#5357)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nestedcontent/nestedcontent.controller.js | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 4e8b35a276..7ead8974d8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -93,8 +93,9 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop "iconHelper", "clipboardService", "eventsService", - - function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService) { + "overlayService", + + function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService) { var contentTypeAliases = []; _.each($scope.model.config.contentTypes, function (contentType) { @@ -239,10 +240,23 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop }; $scope.requestDeleteNode = function (idx) { if ($scope.model.config.confirmDeletes === true) { - localizationService.localize("content_nestedContentDeleteItem").then(function (value) { - if (confirm(value)) { - $scope.deleteNode(idx); - } + localizationService.localizeMany(["content_nestedContentDeleteItem", "general_delete", "general_cancel", "contentTypeEditor_yesDelete"]).then(function (data) { + const overlay = { + title: data[1], + content: data[0], + closeButtonLabel: data[2], + submitButtonLabel: data[3], + submitButtonStyle: "danger", + close: function () { + overlayService.close(); + }, + submit: function () { + $scope.deleteNode(idx); + overlayService.close(); + } + }; + + overlayService.open(overlay); }); } else { $scope.deleteNode(idx); From 12dff9bca31aa1486aa418a0d8b66d42154896c9 Mon Sep 17 00:00:00 2001 From: Morten Bock Date: Mon, 24 Jun 2019 20:43:12 +0200 Subject: [PATCH 048/776] Reduce size of LocalizedText output. Refactor localizationService to not expose internal dictionary. Refactor controllers to not access dictionary directly. --- .../common/services/localization.service.js | 56 ++++++++++++------- .../contentpicker/contentpicker.controller.js | 25 ++++++--- .../propertyeditors/grid/grid.controller.js | 8 +-- .../mediapicker/mediapicker.controller.js | 48 +++++++++------- .../multiurlpicker.controller.js | 17 +++++- .../Editors/BackOfficeController.cs | 24 ++++++-- 6 files changed, 121 insertions(+), 57 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js index ea2ad73263..6081cbd9ad 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/localization.service.js @@ -29,35 +29,48 @@ angular.module('umbraco.services') var resourceFileLoadStatus = "none"; var resourceLoadingPromise = []; - function _lookup(value, tokens, dictionary) { + // array to hold the localized resource string entries + var innerDictionary = []; + + function _lookup(alias, tokens, dictionary, fallbackValue) { //strip the key identifier if its there - if (value && value[0] === "@") { - value = value.substring(1); + if (alias && alias[0] === "@") { + alias = alias.substring(1); } + var underscoreIndex = alias.indexOf("_"); //if no area specified, add general_ - if (value && value.indexOf("_") < 0) { - value = "general_" + value; + if (alias && underscoreIndex < 0) { + alias = "general_" + alias; + underscoreIndex = alias.indexOf("_"); } - var entry = dictionary[value]; - if (entry) { - return service.tokenReplace(entry, tokens); + var areaAlias = alias.substring(0, underscoreIndex); + var valueAlias = alias.substring(underscoreIndex + 1); + + var areaEntry = dictionary[areaAlias]; + if (areaEntry) { + var valueEntry = areaEntry[valueAlias]; + if (valueEntry) { + return service.tokenReplace(valueEntry, tokens); + } } - return "[" + value + "]"; + + if (fallbackValue) return fallbackValue; + + return "[" + alias + "]"; } var service = { - // array to hold the localized resource string entries - dictionary: [], + // loads the language resource file from the server initLocalizedResources: function () { var deferred = $q.defer(); if (resourceFileLoadStatus === "loaded") { - deferred.resolve(service.dictionary); + deferred.resolve(innerDictionary); return deferred.promise; } @@ -77,7 +90,7 @@ angular.module('umbraco.services') $http({ method: "GET", url: url, cache: false }) .then(function (response) { resourceFileLoadStatus = "loaded"; - service.dictionary = response.data; + innerDictionary = response.data; eventsService.emit("localizationService.updated", response.data); @@ -159,11 +172,14 @@ angular.module('umbraco.services') * @param {Array} tokens if specified this array will be sent as parameter values * This replaces %0% and %1% etc in the dictionary key value with the passed in strings * + * @param {String} fallbackValue if specified this string will be returned if no matching + * entry was found in the dictionary + * * @returns {String} localized resource string */ - localize: function (value, tokens) { + localize: function (value, tokens, fallbackValue) { return service.initLocalizedResources().then(function (dic) { - return _lookup(value, tokens, dic); + return _lookup(value, tokens, dic, fallbackValue); }); }, @@ -244,8 +260,8 @@ angular.module('umbraco.services') //Build a concat string by looping over the array of resolved promises/translations var returnValue = ""; - for(var i = 0; i < localizedValues.length; i++){ - returnValue += localizedValues[i]; + for(var j = 0; j < localizedValues.length; j++){ + returnValue += localizedValues[j]; } return returnValue; @@ -292,11 +308,11 @@ angular.module('umbraco.services') return $q.all(promises).then(function(localizedValues){ //Replace {0} and {1} etc in message with the localized values - for(var i = 0; i < localizedValues.length; i++){ - var token = "%" + i + "%"; + for(var j = 0; j < localizedValues.length; j++){ + var token = "%" + j + "%"; var regex = new RegExp(token, "g"); - message = message.replace(regex, localizedValues[i]); + message = message.replace(regex, localizedValues[j]); } return message; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 9d810fb433..e657906202 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -14,6 +14,12 @@ */ function contentPickerController($scope, entityResource, editorState, iconHelper, $routeParams, angularHelper, navigationService, $location, localizationService, editorService, $q) { + var vm = { + labels: { + general_recycleBin: "" + } + }; + var unsubscribe; function subscribe() { @@ -405,7 +411,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper angular.forEach($scope.renderModel, function (item) { if (item.id === entity.id) { if (entity.trashed) { - item.url = localizationService.dictionary.general_recycleBin; + item.url = vm.labels.general_recycleBin; } else { item.url = data; } @@ -463,12 +469,17 @@ function contentPickerController($scope, entityResource, editorState, iconHelper } function init() { - syncRenderModel(false).then(function () { - //everything is loaded, start the watch on the model - startWatch(); - subscribe(); - validate(); - }); + localizationService.localizeMany(["general_recycleBin"]) + .then(function(data) { + vm.labels.general_recycleBin = data[0]; + + syncRenderModel(false).then(function () { + //everything is loaded, start the watch on the model + startWatch(); + subscribe(); + validate(); + }); + }); } init(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js index 1b84ce4463..ccc390252b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js @@ -920,11 +920,9 @@ angular.module("umbraco") //Localize the grid editor names angular.forEach($scope.availableEditors, function (value, key) { //If no translation is provided, keep using the editor name from the manifest - if (localizationService.dictionary.hasOwnProperty("grid_" + value.alias)) { - localizationService.localize("grid_" + value.alias).then(function (v) { - value.name = v; - }); - } + localizationService.localize("grid_" + value.alias, undefined, value.name).then(function (v) { + value.name = v; + }); }); $scope.contentReady = true; 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 d7cac59348..e6e877fc72 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 @@ -3,6 +3,12 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerController", function ($scope, entityResource, mediaHelper, $timeout, userService, localizationService, editorService) { + var vm = { + labels: { + mediaPicker_deletedItem: "" + } + } + //check the pre-values for multi-picker var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false; var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false; @@ -50,7 +56,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl return found; } else { return { - name: localizationService.dictionary.mediaPicker_deletedItem, + name: vm.labels.mediaPicker_deletedItem, id: $scope.model.config.idType !== "udi" ? id : null, udi: $scope.model.config.idType === "udi" ? id : null, icon: "icon-picture", @@ -106,27 +112,31 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl function init() { - userService.getCurrentUser().then(function (userData) { - if (!$scope.model.config.startNodeId) { - $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; - } - // only allow users to add and edit media if they have access to the media section - var hasAccessToMedia = userData.allowedSections.indexOf("media") !== -1; - $scope.allowEditMedia = hasAccessToMedia; - $scope.allowAddMedia = hasAccessToMedia; + localizationService.localizeMany(["mediaPicker_deletedItem"]) + .then(function(data) { + vm.labels.mediaPicker_deletedItem = data[0]; - setupViewModel(); - - //When the model value changes sync the view model - $scope.$watch("model.value", - function (newVal, oldVal) { - if (newVal !== oldVal) { - setupViewModel(); + userService.getCurrentUser().then(function (userData) { + if (!$scope.model.config.startNodeId) { + $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; } - }); - }); + // only allow users to add and edit media if they have access to the media section + var hasAccessToMedia = userData.allowedSections.indexOf("media") !== -1; + $scope.allowEditMedia = hasAccessToMedia; + $scope.allowAddMedia = hasAccessToMedia; + setupViewModel(); + + //When the model value changes sync the view model + $scope.$watch("model.value", + function (newVal, oldVal) { + if (newVal !== oldVal) { + setupViewModel(); + } + }); + }); + }); } $scope.remove = function (index) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js index 734b06536d..3de94a09cf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js @@ -1,5 +1,11 @@ function multiUrlPickerController($scope, angularHelper, localizationService, entityResource, iconHelper, editorService) { + var vm = { + labels: { + general_recycleBin: "" + } + }; + $scope.renderModel = []; if ($scope.preview) { @@ -102,7 +108,7 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en link.published = (data.metaData && data.metaData.IsPublished === false && entityType === "Document") ? false : true; link.trashed = data.trashed; if (link.trashed) { - item.url = localizationService.dictionary.general_recycleBin; + item.url = vm.labels.general_recycleBin; } }); } else { @@ -120,6 +126,15 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en }; editorService.linkPicker(linkPicker); }; + + function init() { + localizationService.localizeMany(["general_recycleBin"]) + .then(function (data) { + vm.labels.general_recycleBin = data[0]; + }); + } + + init(); } angular.module("umbraco").controller("Umbraco.PropertyEditors.MultiUrlPickerController", multiUrlPickerController); diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 7ed87c44dd..34022f8f9f 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -170,12 +170,26 @@ namespace Umbraco.Web.Editors : CultureInfo.GetCultureInfo(GlobalSettings.DefaultUILanguage) : CultureInfo.GetCultureInfo(culture); - var textForCulture = Services.TextService.GetAllStoredValues(cultureInfo) - //the dictionary returned is fine but the delimiter between an 'area' and a 'value' is a '/' but the javascript - // in the back office requires the delimiter to be a '_' so we'll just replace it - .ToDictionary(key => key.Key.Replace("/", "_"), val => val.Value); + var allValues = Services.TextService.GetAllStoredValues(cultureInfo); + var pathedValues = allValues.Select(kv => + { + var slashIndex = kv.Key.IndexOf('/'); + var areaAlias = kv.Key.Substring(0, slashIndex); + var valueAlias = kv.Key.Substring(slashIndex+1); + return new + { + areaAlias, + valueAlias, + value = kv.Value + }; + }); - return new JsonNetResult { Data = textForCulture, Formatting = Formatting.Indented }; + Dictionary> nestedDictionary = pathedValues + .GroupBy(pv => pv.areaAlias) + .ToDictionary(pv => pv.Key, pv => + pv.ToDictionary(pve => pve.valueAlias, pve => pve.value)); + + return new JsonNetResult { Data = nestedDictionary, Formatting = Formatting.Indented }; } /// From 734086fcf557c4e416f7cd7ad5ab55b64d5cfc45 Mon Sep 17 00:00:00 2001 From: Patrick de Mooij Date: Thu, 13 Jun 2019 09:35:21 +0200 Subject: [PATCH 049/776] temp-5612: Being able to add your own roles/startnodes etc --- .../src/views/users/views/user/details.html | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html index 031bff0a47..793f404f0c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html @@ -71,12 +71,10 @@ sections="userGroup.sections" content-start-node="userGroup.contentStartNode" media-start-node="userGroup.mediaStartNode" - allow-remove="!model.user.isCurrentUser" on-remove="model.removeSelectedItem($index, model.user.userGroups)"> @@ -102,7 +99,6 @@ @@ -128,7 +123,6 @@ Date: Thu, 27 Jun 2019 22:17:31 +0200 Subject: [PATCH 050/776] v8: Add keyboard support to slider (#5651) --- src/Umbraco.Web.UI.Client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index ef1a9cc28d..35d808056c 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -35,7 +35,7 @@ "lazyload-js": "1.0.0", "moment": "2.22.2", "ng-file-upload": "12.2.13", - "nouislider": "12.1.0", + "nouislider": "14.0.1", "npm": "^6.4.1", "signalr": "2.4.0", "spectrum-colorpicker": "1.8.0", From b3e3475bf562fc5d0f6ae9ce9a79daaab1589ad0 Mon Sep 17 00:00:00 2001 From: BatJan Date: Mon, 10 Jun 2019 15:06:32 +0200 Subject: [PATCH 051/776] Make sure padding is not overwritten by other selectors --- .../src/less/components/umb-form-check.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less index d8a5ebf9d8..deb573920f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less @@ -6,7 +6,7 @@ flex-wrap: wrap; align-items: center; position: relative; - padding: 0; + padding: 0 !important; margin: 0; min-height: 22px; line-height: 22px; From 390613c796365f3ffbd281f0d26247cdf3d5f0b3 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sun, 9 Jun 2019 10:56:44 +0200 Subject: [PATCH 052/776] Fixed issue with node deletion not removing related records from the public access tables --- .../Persistence/Repositories/Implement/DocumentRepository.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 6c08e05995..7e867c924c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -205,7 +205,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement "DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersionCultureVariation + " WHERE versionId IN (SELECT id FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id)", "DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersion + " WHERE nodeId = @id", "DELETE FROM " + Constants.DatabaseSchema.Tables.Content + " WHERE nodeId = @id", + "DELETE FROM " + Constants.DatabaseSchema.Tables.AccessRule + " WHERE accessId IN (SELECT id FROM " + Constants.DatabaseSchema.Tables.Access + " WHERE nodeId = @id OR loginNodeId = @id OR noAccessNodeId = @id)", "DELETE FROM " + Constants.DatabaseSchema.Tables.Access + " WHERE nodeId = @id", + "DELETE FROM " + Constants.DatabaseSchema.Tables.Access + " WHERE loginNodeId = @id", + "DELETE FROM " + Constants.DatabaseSchema.Tables.Access + " WHERE noAccessNodeId = @id", "DELETE FROM " + Constants.DatabaseSchema.Tables.Node + " WHERE id = @id" }; return list; From 2f7bf413ede66570fd8839429a3670b559e5f10b Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 27 Jun 2019 22:27:42 +0200 Subject: [PATCH 053/776] V8: Make dialogs prompt to discard changes on outside click and ESC (#5740) --- .../umbcontextdialog.directive.js | 47 ++++++++++++++++--- .../content/content.protect.controller.js | 6 +++ .../content/content.rights.controller.js | 1 + 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbcontextdialog/umbcontextdialog.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbcontextdialog/umbcontextdialog.directive.js index 99a5dad58c..904a2ce8ca 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbcontextdialog/umbcontextdialog.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbcontextdialog/umbcontextdialog.directive.js @@ -1,16 +1,20 @@ (function() { 'use strict'; - function UmbContextDialog(navigationService, keyboardService) { + function UmbContextDialog(navigationService, keyboardService, localizationService, overlayService) { function link($scope) { - - $scope.outSideClick = function() { - navigationService.hideDialog(); - } - keyboardService.bind("esc", function() { - navigationService.hideDialog(); + $scope.dialog = { + confirmDiscardChanges: false + }; + + $scope.outSideClick = function() { + hide(); + }; + + keyboardService.bind("esc", function () { + hide(); }); //ensure to unregister from all events! @@ -18,6 +22,35 @@ keyboardService.unbind("esc"); }); + function hide() { + if ($scope.dialog.confirmDiscardChanges) { + localizationService.localizeMany(["prompt_unsavedChanges", "prompt_unsavedChangesWarning", "prompt_discardChanges", "prompt_stay"]).then( + function (values) { + var overlay = { + "view": "default", + "title": values[0], + "content": values[1], + "disableBackdropClick": true, + "disableEscKey": true, + "submitButtonLabel": values[2], + "closeButtonLabel": values[3], + submit: function () { + overlayService.close(); + navigationService.hideDialog(); + }, + close: function () { + overlayService.close(); + } + }; + + overlayService.open(overlay); + } + ); + } + else { + navigationService.hideDialog(); + } + } } var directive = { diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js index 8d80f308ab..fcd0294849 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.protect.controller.js @@ -102,6 +102,7 @@ }; }); navigationService.syncTree({ tree: "content", path: $scope.currentNode.path, forceReload: true }); + $scope.dialog.confirmDiscardChanges = true; }, function (error) { vm.error = error; vm.buttonState = "error"; @@ -117,6 +118,7 @@ function toggle(group) { group.selected = !group.selected; + $scope.dialog.confirmDiscardChanges = true; } function pickGroup() { @@ -137,6 +139,7 @@ }); editorService.close(); navigationService.allowHideDialog(true); + $scope.dialog.confirmDiscardChanges = true; }, close: function() { editorService.close(); @@ -147,6 +150,7 @@ function removeGroup(group) { vm.groups = _.reject(vm.groups, function(g) { return g.id === group.id }); + $scope.dialog.confirmDiscardChanges = true; } function pickMember() { @@ -186,6 +190,7 @@ $q.all(promises).then(function() { vm.loading = false; }); + $scope.dialog.confirmDiscardChanges = true; } }, close: function () { @@ -219,6 +224,7 @@ } editorService.close(); navigationService.allowHideDialog(true); + $scope.dialog.confirmDiscardChanges = true; }, close: function () { editorService.close(); diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.rights.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.rights.controller.js index 60a8694eca..0e40fc2e6c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.rights.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.rights.controller.js @@ -93,6 +93,7 @@ function setPermissions(group) { assignGroupPermissions(group); setViewSate("manageGroups"); + $scope.dialog.confirmDiscardChanges = true; } /** From fbe184ef6efb5f6c350ac7342423baadeff801de Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Thu, 27 Jun 2019 21:33:19 +0100 Subject: [PATCH 054/776] Prevent deletion of System DataTypes (#5623) --- src/Umbraco.Core/Constants-DataTypes.cs | 5 ++- .../Migrations/Install/DatabaseDataCreator.cs | 36 +++++++++---------- .../Trees/DataTypeTreeController.cs | 10 +++++- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs index f2b31be28f..63c51e12fd 100644 --- a/src/Umbraco.Core/Constants-DataTypes.cs +++ b/src/Umbraco.Core/Constants-DataTypes.cs @@ -4,23 +4,26 @@ { public static class DataTypes { - public const int LabelString = -92; + public const int LabelString = System.DefaultLabelDataTypeId; public const int LabelInt = -91; public const int LabelBigint = -93; public const int LabelDateTime = -94; public const int LabelTime = -98; public const int LabelDecimal = -99; + public const int Textarea = -89; public const int Textbox = -88; public const int Boolean = -49; public const int DateTime = -36; public const int DropDownSingle = -39; public const int DropDownMultiple = -42; + public const int Upload = -90; public const int DefaultContentListView = -95; public const int DefaultMediaListView = -96; public const int DefaultMembersListView = -97; + public const int ImageCropper = 1043; public const int Tags = 1041; } } diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 1de983636b..6a6d31ac2f 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -101,33 +101,33 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -1, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1", SortOrder = 0, UniqueId = new Guid("916724a5-173d-4619-b97e-b9de133dd6f5"), Text = "SYSTEM DATA: umbraco master root", NodeObjectType = Constants.ObjectTypes.SystemRoot, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -20, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,-20", SortOrder = 0, UniqueId = new Guid("0F582A79-1E41-4CF0-BFA0-76340651891A"), Text = "Recycle Bin", NodeObjectType = Constants.ObjectTypes.ContentRecycleBin, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -21, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,-21", SortOrder = 0, UniqueId = new Guid("BF7C7CBC-952F-4518-97A2-69E9C7B33842"), Text = "Recycle Bin", NodeObjectType = Constants.ObjectTypes.MediaRecycleBin, CreateDate = DateTime.Now }); - InsertDataTypeNodeDto(Constants.DataTypes.LabelString, 35, "f0bc4bfb-b499-40d6-ba86-058885a5178c", "Label"); + InsertDataTypeNodeDto(Constants.DataTypes.LabelString, 35, "f0bc4bfb-b499-40d6-ba86-058885a5178c", "Label (string)"); InsertDataTypeNodeDto(Constants.DataTypes.LabelInt, 36, "8e7f995c-bd81-4627-9932-c40e568ec788", "Label (integer)"); InsertDataTypeNodeDto(Constants.DataTypes.LabelBigint, 36, "930861bf-e262-4ead-a704-f99453565708", "Label (bigint)"); InsertDataTypeNodeDto(Constants.DataTypes.LabelDateTime, 37, "0e9794eb-f9b5-4f20-a788-93acd233a7e4", "Label (datetime)"); InsertDataTypeNodeDto(Constants.DataTypes.LabelTime, 38, "a97cec69-9b71-4c30-8b12-ec398860d7e8", "Label (time)"); InsertDataTypeNodeDto(Constants.DataTypes.LabelDecimal, 39, "8f1ef1e1-9de4-40d3-a072-6673f631ca64", "Label (decimal)"); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -90, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-90", SortOrder = 34, UniqueId = new Guid("84c6b441-31df-4ffe-b67e-67d5bc3ae65a"), Text = "Upload", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -89, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-89", SortOrder = 33, UniqueId = new Guid("c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"), Text = "Textarea", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Upload, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Upload}", SortOrder = 34, UniqueId = new Guid("84c6b441-31df-4ffe-b67e-67d5bc3ae65a"), Text = "Upload", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Textarea, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Textarea}", SortOrder = 33, UniqueId = new Guid("c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"), Text = "Textarea", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -88, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-88", SortOrder = 32, UniqueId = new Guid("0cc0eba1-9960-42c9-bf9b-60e150b429ae"), Text = "Textstring", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -87, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-87", SortOrder = 4, UniqueId = new Guid("ca90c950-0aff-4e72-b976-a30b1ac57dad"), Text = "Richtext editor", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = new Guid("2e6d3631-066e-44b8-aec4-96f09099b2b5"), Text = "Numeric", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -49, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-49", SortOrder = 2, UniqueId = new Guid("92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"), Text = "True/false", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Boolean, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Boolean}", SortOrder = 2, UniqueId = new Guid("92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"), Text = "True/false", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -43, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-43", SortOrder = 2, UniqueId = new Guid("fbaf13a8-4036-41f2-93a3-974f678c312a"), Text = "Checkbox list", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DropDownSingle, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DropDownSingle}", SortOrder = 2, UniqueId = new Guid("0b6a45e7-44ba-430d-9da5-4e46060b9e03"), Text = "Dropdown", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -41, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-41", SortOrder = 2, UniqueId = new Guid("5046194e-4237-453c-a547-15db3a07c4e1"), Text = "Date Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -40, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-40", SortOrder = 2, UniqueId = new Guid("bb5f57c9-ce2b-4bb9-b697-4caca783a805"), Text = "Radiobox", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DropDownMultiple, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DropDownMultiple}", SortOrder = 2, UniqueId = new Guid("f38f0ac7-1d27-439c-9f3f-089cd8825a53"), Text = "Dropdown multiple", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = new Guid("0225af17-b302-49cb-9176-b9f35cab9c17"), Text = "Approved Color", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -36, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-36", SortOrder = 2, UniqueId = new Guid("e4d66c0f-b935-4200-81f0-025f7256b89a"), Text = "Date Picker with time", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DateTime, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DateTime}", SortOrder = 2, UniqueId = new Guid("e4d66c0f-b935-4200-81f0-025f7256b89a"), Text = "Date Picker with time", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultContentListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultContentListView}", SortOrder = 2, UniqueId = new Guid("C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMediaListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultMediaListView}", SortOrder = 2, UniqueId = new Guid("3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMembersListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultMembersListView}", SortOrder = 2, UniqueId = new Guid("AA2C52A0-CE87-4E65-A47C-7DF09358585D"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"), Text = Constants.Conventions.MediaTypes.Folder, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"), Text = Constants.Conventions.MediaTypes.Image, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1033, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1033", SortOrder = 2, UniqueId = new Guid("4c52d8ab-54e6-40cd-999c-7a5f24903e4d"), Text = Constants.Conventions.MediaTypes.File, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1041, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1041", SortOrder = 2, UniqueId = new Guid("b6b73142-b9c1-4bf8-a16d-e1c23320b549"), Text = "Tags", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1043, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1043", SortOrder = 2, UniqueId = new Guid("1df9f033-e6d4-451f-b8d2-e0cbc50a836f"), Text = "Image Cropper", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Tags, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Tags}", SortOrder = 2, UniqueId = new Guid("b6b73142-b9c1-4bf8-a16d-e1c23320b549"), Text = "Tags", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.ImageCropper, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.ImageCropper}", SortOrder = 2, UniqueId = new Guid("1df9f033-e6d4-451f-b8d2-e0cbc50a836f"), Text = "Image Cropper", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1044, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1044", SortOrder = 0, UniqueId = new Guid("d59be02f-1df9-4228-aa1e-01917d806cda"), Text = Constants.Conventions.MemberTypes.DefaultAlias, NodeObjectType = Constants.ObjectTypes.MemberType, CreateDate = DateTime.Now }); //New UDI pickers with newer Ids @@ -210,19 +210,19 @@ namespace Umbraco.Core.Migrations.Install private void CreatePropertyTypeData() { - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = 1043, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = Constants.DataTypes.ImageCropper, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 7, UniqueId = 7.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in pixels", Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 8, UniqueId = 8.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in pixels", Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 9, UniqueId = 9.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 10, UniqueId = 10.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = Constants.DataTypes.Upload, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = true, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = "in bytes", Variations = (byte) ContentVariation.Nothing }); //membership property types - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = Constants.DataTypes.Textarea, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = Constants.DataTypes.Boolean, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = Constants.DataTypes.Boolean, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); @@ -266,30 +266,30 @@ namespace Umbraco.Core.Migrations.Install const string layouts = "[" + cardLayout + "," + listLayout + "]"; // TODO: Check which of the DataTypeIds below doesn't exist in umbracoNode, which results in a foreign key constraint errors. - _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -49, EditorAlias = Constants.PropertyEditors.Aliases.Boolean, DbType = "Integer" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.Boolean, EditorAlias = Constants.PropertyEditors.Aliases.Boolean, DbType = "Integer" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -51, EditorAlias = Constants.PropertyEditors.Aliases.Integer, DbType = "Integer" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -87, EditorAlias = Constants.PropertyEditors.Aliases.TinyMce, DbType = "Ntext", Configuration = "{\"value\":\",code,undo,redo,cut,copy,mcepasteword,stylepicker,bold,italic,bullist,numlist,outdent,indent,mcelink,unlink,mceinsertanchor,mceimage,umbracomacro,mceinserttable,umbracoembed,mcecharmap,|1|1,2,3,|0|500,400|1049,|true|\"}" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -88, EditorAlias = Constants.PropertyEditors.Aliases.TextBox, DbType = "Nvarchar" }); - _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -89, EditorAlias = Constants.PropertyEditors.Aliases.TextArea, DbType = "Ntext" }); - _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -90, EditorAlias = Constants.PropertyEditors.Aliases.UploadField, DbType = "Nvarchar" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.Textarea, EditorAlias = Constants.PropertyEditors.Aliases.TextArea, DbType = "Ntext" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.Upload, EditorAlias = Constants.PropertyEditors.Aliases.UploadField, DbType = "Nvarchar" }); InsertDataTypeDto(Constants.DataTypes.LabelString, Constants.PropertyEditors.Aliases.Label, "Nvarchar", "{\"umbracoDataValueType\":\"STRING\"}"); InsertDataTypeDto(Constants.DataTypes.LabelInt, Constants.PropertyEditors.Aliases.Label, "Integer", "{\"umbracoDataValueType\":\"INT\"}"); InsertDataTypeDto(Constants.DataTypes.LabelBigint, Constants.PropertyEditors.Aliases.Label, "Nvarchar", "{\"umbracoDataValueType\":\"BIGINT\"}"); InsertDataTypeDto(Constants.DataTypes.LabelDateTime, Constants.PropertyEditors.Aliases.Label, "Date", "{\"umbracoDataValueType\":\"DATETIME\"}"); InsertDataTypeDto(Constants.DataTypes.LabelDecimal, Constants.PropertyEditors.Aliases.Label, "Decimal", "{\"umbracoDataValueType\":\"DECIMAL\"}"); InsertDataTypeDto(Constants.DataTypes.LabelTime, Constants.PropertyEditors.Aliases.Label, "Date", "{\"umbracoDataValueType\":\"TIME\"}"); - _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -36, EditorAlias = Constants.PropertyEditors.Aliases.DateTime, DbType = "Date" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DateTime, EditorAlias = Constants.PropertyEditors.Aliases.DateTime, DbType = "Date" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -37, EditorAlias = Constants.PropertyEditors.Aliases.ColorPicker, DbType = "Nvarchar" }); InsertDataTypeDto(Constants.DataTypes.DropDownSingle, Constants.PropertyEditors.Aliases.DropDownListFlexible, "Nvarchar", "{\"multiple\":false}"); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -40, EditorAlias = Constants.PropertyEditors.Aliases.RadioButtonList, DbType = "Nvarchar" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -41, EditorAlias = "Umbraco.DateTime", DbType = "Date", Configuration = "{\"format\":\"YYYY-MM-DD\"}" }); InsertDataTypeDto(Constants.DataTypes.DropDownMultiple, Constants.PropertyEditors.Aliases.DropDownListFlexible, "Nvarchar", "{\"multiple\":true}"); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -43, EditorAlias = Constants.PropertyEditors.Aliases.CheckBoxList, DbType = "Nvarchar" }); - _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1041, EditorAlias = Constants.PropertyEditors.Aliases.Tags, DbType = "Ntext", + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.Tags, EditorAlias = Constants.PropertyEditors.Aliases.Tags, DbType = "Ntext", Configuration = "{\"group\":\"default\", \"storageType\":\"Json\"}" }); - _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = 1043, EditorAlias = Constants.PropertyEditors.Aliases.ImageCropper, DbType = "Ntext" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.ImageCropper, EditorAlias = Constants.PropertyEditors.Aliases.ImageCropper, DbType = "Ntext" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DefaultContentListView, EditorAlias = Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar", Configuration = "{\"pageSize\":100, \"orderBy\":\"updateDate\", \"orderDirection\":\"desc\", \"layouts\":" + layouts + ", \"includeProperties\":[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":1},{\"alias\":\"owner\",\"header\":\"Updated by\",\"isSystem\":1}]}" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.DefaultMediaListView, EditorAlias = Constants.PropertyEditors.Aliases.ListView, DbType = "Nvarchar", diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index 75260b586d..c4eea7558c 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -72,7 +72,15 @@ namespace Umbraco.Web.Trees { var systemIds = new[] { - Constants.System.DefaultLabelDataTypeId + Constants.DataTypes.Boolean, // Used by the Member Type: "Member" + Constants.DataTypes.Textarea, // Used by the Member Type: "Member" + Constants.DataTypes.LabelBigint, // Used by the Media Type: "Image"; Used by the Media Type: "File" + Constants.DataTypes.LabelDateTime, // Used by the Member Type: "Member" + Constants.DataTypes.LabelDecimal, // Used by the Member Type: "Member" + Constants.DataTypes.LabelInt, // Used by the Media Type: "Image"; Used by the Member Type: "Member" + Constants.DataTypes.LabelString, // Used by the Media Type: "Image"; Used by the Media Type: "File" + Constants.DataTypes.ImageCropper, // Used by the Media Type: "Image" + Constants.DataTypes.Upload, // Used by the Media Type: "File" }; return systemIds.Concat(GetNonDeletableSystemListViewDataTypeIds()); From 580ba79619cee347512131eff28e53d96af4958b Mon Sep 17 00:00:00 2001 From: leekelleher Date: Fri, 7 Jun 2019 17:02:55 +0100 Subject: [PATCH 055/776] Icon Constants consistency Updated the `Constants.Icons` and replaced the hard-coded strings with the const references. --- src/Umbraco.Core/Constants-Icons.cs | 62 +++++++++++++++++-- .../Migrations/Install/DatabaseDataCreator.cs | 8 +-- .../Implement/MemberTypeRepository.cs | 2 +- .../ContentEditorContentAppFactory.cs | 5 +- .../Editors/ContentTypeController.cs | 2 +- .../Editors/MediaTypeController.cs | 5 +- .../Editors/MemberTypeController.cs | 2 +- .../Models/Mapping/EntityMapDefinition.cs | 18 +++--- .../Models/Mapping/MacroMapDefinition.cs | 2 +- .../Models/Mapping/MemberMapDefinition.cs | 2 +- .../Mapping/MemberTabsAndPropertiesMapper.cs | 2 +- .../Models/Mapping/UserMapDefinition.cs | 6 +- .../PropertyEditors/ListViewPropertyEditor.cs | 2 +- .../MacroContainerPropertyEditor.cs | 2 +- .../MediaPickerPropertyEditor.cs | 2 +- .../MemberGroupPickerPropertyEditor.cs | 2 +- .../MemberPickerPropertyEditor.cs | 2 +- .../UserPickerPropertyEditor.cs | 2 +- src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 8 +-- .../Trees/ContentBlueprintTreeController.cs | 2 +- .../Trees/ContentTypeTreeController.cs | 2 +- .../Trees/DataTypeTreeController.cs | 4 +- src/Umbraco.Web/Trees/FilesTreeController.cs | 2 +- src/Umbraco.Web/Trees/MacrosTreeController.cs | 2 +- .../Trees/MediaTypeTreeController.cs | 2 +- .../Trees/MemberGroupTreeController.cs | 2 +- src/Umbraco.Web/Trees/MemberTreeController.cs | 8 +-- .../Trees/MemberTypeTreeController.cs | 2 +- src/Umbraco.Web/Trees/UserTreeController.cs | 2 +- 29 files changed, 109 insertions(+), 55 deletions(-) diff --git a/src/Umbraco.Core/Constants-Icons.cs b/src/Umbraco.Core/Constants-Icons.cs index d3e8b4ad3b..05213ed1c4 100644 --- a/src/Umbraco.Core/Constants-Icons.cs +++ b/src/Umbraco.Core/Constants-Icons.cs @@ -5,39 +5,89 @@ public static class Icons { /// - /// System contenttype icon + /// System default icon /// - public const string ContentType = "icon-arrangement"; + public const string DefaultIcon = Content; /// - /// System datatype icon + /// System content icon + /// + public const string Content = "icon-document"; + + /// + /// System content type icon + /// + public const string ContentType = "icon-item-arrangement"; + + /// + /// System data type icon /// public const string DataType = "icon-autofill"; /// - /// System property editor icon + /// System list view icon /// - public const string PropertyEditor = "icon-autofill"; + public const string ListView = "icon-thumbnail-list"; /// /// System macro icon /// public const string Macro = "icon-settings-alt"; + /// + /// System media file icon + /// + public const string MediaFile = "icon-document"; + + /// + /// System media folder icon + /// + public const string MediaFolder = "icon-folder"; + + /// + /// System media image icon + /// + public const string MediaImage = "icon-picture"; + + /// + /// System media type icon + /// + public const string MediaType = "icon-thumbnails"; + /// /// System member icon /// public const string Member = "icon-user"; /// - /// System member icon + /// System member group icon + /// + public const string MemberGroup = "icon-users-alt"; + + /// + /// System member type icon /// public const string MemberType = "icon-users"; + /// + /// System property editor icon + /// + public const string PropertyEditor = "icon-autofill"; + /// /// System member icon /// public const string Template = "icon-layout"; + + /// + /// System user icon + /// + public const string User = "icon-user"; + + /// + /// System user group icon + /// + public const string UserGroup = "icon-users"; } } } diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 6a6d31ac2f..67606bc12a 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -155,10 +155,10 @@ namespace Umbraco.Core.Migrations.Install private void CreateContentTypeData() { - _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = "icon-folder", Thumbnail = "icon-folder", IsContainer = false, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = "icon-picture", Thumbnail = "icon-picture", AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = "icon-document", Thumbnail = "icon-document", AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); - _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Constants.Conventions.MemberTypes.DefaultAlias, Icon = "icon-user", Thumbnail = "icon-user", Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = Constants.Icons.MediaFolder, Thumbnail = Constants.Icons.MediaFolder, IsContainer = false, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = Constants.Icons.MediaImage, Thumbnail = Constants.Icons.MediaImage, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = Constants.Icons.MediaFile, Thumbnail = Constants.Icons.MediaFile, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Constants.Conventions.MemberTypes.DefaultAlias, Icon = Constants.Icons.Member, Thumbnail = Constants.Icons.Member, Variations = (byte) ContentVariation.Nothing }); } private void CreateUserData() diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs index a32ec1b422..ecc0b73ed8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs @@ -136,7 +136,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement //set a default icon if one is not specified if (entity.Icon.IsNullOrWhiteSpace()) { - entity.Icon = "icon-user"; + entity.Icon = Constants.Icons.Member; } //By Convention we add 9 standard PropertyTypes to an Umbraco MemberType diff --git a/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs b/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs index ea2dc1b2d6..8c251cacd2 100644 --- a/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs +++ b/src/Umbraco.Web/ContentApps/ContentEditorContentAppFactory.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.ContentEditing; using Umbraco.Core.Models.Membership; @@ -23,7 +24,7 @@ namespace Umbraco.Web.ContentApps { Alias = "umbContent", Name = "Content", - Icon = "icon-document", + Icon = Constants.Icons.Content, View = "views/content/apps/content/content.html", Weight = Weight }); @@ -36,7 +37,7 @@ namespace Umbraco.Web.ContentApps { Alias = "umbContent", Name = "Content", - Icon = "icon-document", + Icon = Constants.Icons.Content, View = "views/media/apps/content/content.html", Weight = Weight }); diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index e2a1e54571..cf8b6c3859 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -373,7 +373,7 @@ namespace Umbraco.Web.Editors else ct = new ContentType(parentId); - ct.Icon = "icon-document"; + ct.Icon = Constants.Icons.Content; var dto = Mapper.Map(ct); return dto; diff --git a/src/Umbraco.Web/Editors/MediaTypeController.cs b/src/Umbraco.Web/Editors/MediaTypeController.cs index 391f3b8cf9..c55f07d559 100644 --- a/src/Umbraco.Web/Editors/MediaTypeController.cs +++ b/src/Umbraco.Web/Editors/MediaTypeController.cs @@ -135,7 +135,10 @@ namespace Umbraco.Web.Editors } public MediaTypeDisplay GetEmpty(int parentId) { - var ct = new MediaType(parentId) {Icon = "icon-picture"}; + var ct = new MediaType(parentId) + { + Icon = Constants.Icons.MediaImage + }; var dto = Mapper.Map(ct); return dto; diff --git a/src/Umbraco.Web/Editors/MemberTypeController.cs b/src/Umbraco.Web/Editors/MemberTypeController.cs index df7faeccf4..f406988ae5 100644 --- a/src/Umbraco.Web/Editors/MemberTypeController.cs +++ b/src/Umbraco.Web/Editors/MemberTypeController.cs @@ -102,7 +102,7 @@ namespace Umbraco.Web.Editors public MemberTypeDisplay GetEmpty() { var ct = new MemberType(-1); - ct.Icon = "icon-user"; + ct.Icon = Constants.Icons.Member; var dto = Mapper.Map(ct); return dto; diff --git a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs index d5bc6adee9..38ec557fdb 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs @@ -43,7 +43,7 @@ namespace Umbraco.Web.Models.Mapping target.Udi = Udi.Create(ObjectTypes.GetUdiType(source.NodeObjectType), source.Key); if (source.NodeObjectType == Constants.ObjectTypes.Member && target.Icon.IsNullOrWhiteSpace()) - target.Icon = "icon-user"; + target.Icon = Constants.Icons.Member; if (source is IContentEntitySlim contentSlim) source.AdditionalData["ContentTypeAlias"] = contentSlim.ContentTypeAlias; @@ -89,7 +89,7 @@ namespace Umbraco.Web.Models.Mapping private static void Map(IUser source, EntityBasic target, MapperContext context) { target.Alias = source.Username; - target.Icon = "icon-user"; + target.Icon = Constants.Icons.User; target.Id = source.Id; target.Key = source.Key; target.Name = source.Name; @@ -101,7 +101,7 @@ namespace Umbraco.Web.Models.Mapping private static void Map(ITemplate source, EntityBasic target, MapperContext context) { target.Alias = source.Alias; - target.Icon = "icon-layout"; + target.Icon = Constants.Icons.Template; target.Id = source.Id; target.Key = source.Key; target.Name = source.Name; @@ -144,15 +144,15 @@ namespace Umbraco.Web.Models.Mapping if (target.Icon.IsNullOrWhiteSpace()) { if (source.NodeObjectType == Constants.ObjectTypes.Member) - target.Icon = "icon-user"; + target.Icon = Constants.Icons.Member; else if (source.NodeObjectType == Constants.ObjectTypes.DataType) - target.Icon = "icon-autofill"; + target.Icon = Constants.Icons.DataType; else if (source.NodeObjectType == Constants.ObjectTypes.DocumentType) - target.Icon = "icon-item-arrangement"; + target.Icon = Constants.Icons.ContentType; else if (source.NodeObjectType == Constants.ObjectTypes.MediaType) - target.Icon = "icon-thumbnails"; + target.Icon = Constants.Icons.MediaType; else if (source.NodeObjectType == Constants.ObjectTypes.TemplateType) - target.Icon = "icon-newspaper-alt"; + target.Icon = Constants.Icons.Template; } } @@ -167,7 +167,7 @@ namespace Umbraco.Web.Models.Mapping //get the icon if there is one target.Icon = source.Values.ContainsKey(UmbracoExamineIndex.IconFieldName) ? source.Values[UmbracoExamineIndex.IconFieldName] - : "icon-document"; + : Constants.Icons.DefaultIcon; target.Name = source.Values.ContainsKey("nodeName") ? source.Values["nodeName"] : "[no name]"; diff --git a/src/Umbraco.Web/Models/Mapping/MacroMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MacroMapDefinition.cs index 089f5d5d71..e5bca22287 100644 --- a/src/Umbraco.Web/Models/Mapping/MacroMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MacroMapDefinition.cs @@ -31,7 +31,7 @@ namespace Umbraco.Web.Models.Mapping private static void Map(IMacro source, EntityBasic target, MapperContext context) { target.Alias = source.Alias; - target.Icon = "icon-settings-alt"; + target.Icon = Constants.Icons.Macro; target.Id = source.Id; target.Key = source.Key; target.Name = source.Name; diff --git a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs index 1c781255e7..96ca16e18b 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberMapDefinition.cs @@ -128,7 +128,7 @@ namespace Umbraco.Web.Models.Mapping { target.CreateDate = source.CreationDate; target.Email = source.Email; - target.Icon = "icon-user"; + target.Icon = Constants.Icons.Member; target.Id = int.MaxValue; target.Key = source.ProviderUserKey.TryConvertTo().Result; target.Name = source.UserName; diff --git a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs index 6ee37ea443..8744b068a7 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs @@ -96,7 +96,7 @@ namespace Umbraco.Web.Models.Mapping linkText = source.ContentType.Name, url = memberTypeLink, target = "_self", - icon = "icon-item-arrangement" + icon = Constants.Icons.ContentType } }; docTypeProperty.View = "urllist"; diff --git a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs index 1b245cdce2..3860d5d525 100644 --- a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs @@ -179,7 +179,7 @@ namespace Umbraco.Web.Models.Mapping target.DefaultPermissions = MapUserGroupDefaultPermissions(source); if (target.Icon.IsNullOrWhiteSpace()) - target.Icon = "icon-users"; + target.Icon = Constants.Icons.UserGroup; } // Umbraco.Code.MapAll -Trashed -Alias -AssignedPermissions @@ -194,7 +194,7 @@ namespace Umbraco.Web.Models.Mapping target.Udi = Udi.Create(ObjectTypes.GetUdiType(source.NodeObjectType), source.Key); if (source.NodeObjectType == Constants.ObjectTypes.Member && target.Icon.IsNullOrWhiteSpace()) - target.Icon = "icon-user"; + target.Icon = Constants.Icons.Member; } // Umbraco.Code.MapAll -ContentStartNode -MediaStartNode -Sections -Notifications -Udi @@ -350,7 +350,7 @@ namespace Umbraco.Web.Models.Mapping target.ContentStartNode = CreateRootNode(_textService.Localize("content/contentRoot")); if (target.Icon.IsNullOrWhiteSpace()) - target.Icon = "icon-users"; + target.Icon = Constants.Icons.UserGroup; } private IDictionary> MapUserGroupDefaultPermissions(IUserGroup source) diff --git a/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs index 8e2732cc0f..f170608545 100644 --- a/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs @@ -8,7 +8,7 @@ namespace Umbraco.Web.PropertyEditors /// /// Represents a list-view editor. /// - [DataEditor(Constants.PropertyEditors.Aliases.ListView, "List view", "listview", HideLabel = true, Group = "lists", Icon = "icon-item-arrangement")] + [DataEditor(Constants.PropertyEditors.Aliases.ListView, "List view", "listview", HideLabel = true, Group = "lists", Icon = Constants.Icons.ListView)] public class ListViewPropertyEditor : DataEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs index d4d23da3a4..99a9f44487 100644 --- a/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MacroContainerPropertyEditor.cs @@ -5,7 +5,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { // TODO: MacroContainerPropertyEditor is deprecated, but what's the alternative? - [DataEditor(Constants.PropertyEditors.Aliases.MacroContainer, "(Obsolete) Macro Picker", "macrocontainer", ValueType = ValueTypes.Text, Group="rich content", Icon="icon-settings-alt", IsDeprecated = true)] + [DataEditor(Constants.PropertyEditors.Aliases.MacroContainer, "(Obsolete) Macro Picker", "macrocontainer", ValueType = ValueTypes.Text, Group = "rich content", Icon = Constants.Icons.Macro, IsDeprecated = true)] public class MacroContainerPropertyEditor : DataEditor { public MacroContainerPropertyEditor(ILogger logger) diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs index 6c768f4932..52e616ffbd 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs @@ -8,7 +8,7 @@ namespace Umbraco.Web.PropertyEditors /// Represents a media picker property editor. /// [DataEditor(Constants.PropertyEditors.Aliases.MediaPicker, EditorType.PropertyValue | EditorType.MacroParameter, - "Media Picker", "mediapicker", ValueType = ValueTypes.Text, Group = "media", Icon = "icon-picture")] + "Media Picker", "mediapicker", ValueType = ValueTypes.Text, Group = "media", Icon = Constants.Icons.MediaImage)] public class MediaPickerPropertyEditor : DataEditor { /// diff --git a/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs index b917145dbd..5d89024692 100644 --- a/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MemberGroupPickerPropertyEditor.cs @@ -4,7 +4,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [DataEditor(Constants.PropertyEditors.Aliases.MemberGroupPicker, "Member Group Picker", "membergrouppicker", ValueType = ValueTypes.Text, Group = "People", Icon = "icon-users")] + [DataEditor(Constants.PropertyEditors.Aliases.MemberGroupPicker, "Member Group Picker", "membergrouppicker", ValueType = ValueTypes.Text, Group = "People", Icon = Constants.Icons.MemberGroup)] public class MemberGroupPickerPropertyEditor : DataEditor { public MemberGroupPickerPropertyEditor(ILogger logger) diff --git a/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs index a0705fb373..858582ab72 100644 --- a/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MemberPickerPropertyEditor.cs @@ -4,7 +4,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [DataEditor(Constants.PropertyEditors.Aliases.MemberPicker, "Member Picker", "memberpicker", ValueType = ValueTypes.String, Group = "People", Icon = "icon-user")] + [DataEditor(Constants.PropertyEditors.Aliases.MemberPicker, "Member Picker", "memberpicker", ValueType = ValueTypes.String, Group = "People", Icon = Constants.Icons.Member)] public class MemberPickerPropertyEditor : DataEditor { public MemberPickerPropertyEditor(ILogger logger) diff --git a/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs index 8cb8b64594..1d3ab05e96 100644 --- a/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/UserPickerPropertyEditor.cs @@ -6,7 +6,7 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - [DataEditor(Constants.PropertyEditors.Aliases.UserPicker, "User picker", "entitypicker", ValueType = ValueTypes.Integer, Group = "People", Icon = "icon-user")] + [DataEditor(Constants.PropertyEditors.Aliases.UserPicker, "User picker", "entitypicker", ValueType = ValueTypes.Integer, Group = "People", Icon = Constants.Icons.User)] public class UserPickerPropertyEditor : DataEditor { public UserPickerPropertyEditor(ILogger logger) diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index 43db9ff0ba..e15e434678 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -356,9 +356,9 @@ namespace Umbraco.Web.Search var m = _mapper.Map(result); //if no icon could be mapped, it will be set to document, so change it to picture - if (m.Icon == "icon-document") + if (m.Icon == Constants.Icons.DefaultIcon) { - m.Icon = "icon-user"; + m.Icon = Constants.Icons.Member; } if (result.Values.ContainsKey("email") && result.Values["email"] != null) @@ -389,9 +389,9 @@ namespace Umbraco.Web.Search { var m = _mapper.Map(result); //if no icon could be mapped, it will be set to document, so change it to picture - if (m.Icon == "icon-document") + if (m.Icon == Constants.Icons.DefaultIcon) { - m.Icon = "icon-picture"; + m.Icon = Constants.Icons.MediaImage; } yield return m; } diff --git a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs index 8c23a91d5a..ac75fd831d 100644 --- a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs @@ -59,7 +59,7 @@ namespace Umbraco.Web.Trees nodes.AddRange(docTypeEntities .Select(entity => { - var treeNode = CreateTreeNode(entity, Constants.ObjectTypes.DocumentBlueprint, id, queryStrings, "icon-item-arrangement", true); + var treeNode = CreateTreeNode(entity, Constants.ObjectTypes.DocumentBlueprint, id, queryStrings, Constants.Icons.ContentType, true); treeNode.Path = $"-1,{entity.Id}"; treeNode.NodeType = "document-type-blueprints"; // TODO: This isn't the best way to ensure a no operation process for clicking a node but it works for now. diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index 4a8cfba9a5..bead6aa141 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -57,7 +57,7 @@ namespace Umbraco.Web.Trees // since 7.4+ child type creation is enabled by a config option. It defaults to on, but can be disabled if we decide to. // need this check to keep supporting sites where children have already been created. var hasChildren = dt.HasChildren; - var node = CreateTreeNode(dt, Constants.ObjectTypes.DocumentType, id, queryStrings, "icon-item-arrangement", hasChildren); + var node = CreateTreeNode(dt, Constants.ObjectTypes.DocumentType, id, queryStrings, Constants.Icons.ContentType, hasChildren); node.Path = dt.Path; return node; diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index c4eea7558c..2465b4d45a 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -53,11 +53,11 @@ namespace Umbraco.Web.Trees .OrderBy(entity => entity.Name) .Select(dt => { - var node = CreateTreeNode(dt.Id.ToInvariantString(), id, queryStrings, dt.Name, "icon-autofill", false); + var node = CreateTreeNode(dt.Id.ToInvariantString(), id, queryStrings, dt.Name, Constants.Icons.DataType, false); node.Path = dt.Path; if (systemListViewDataTypeIds.Contains(dt.Id)) { - node.Icon = "icon-thumbnail-list"; + node.Icon = Constants.Icons.ListView; } return node; })); diff --git a/src/Umbraco.Web/Trees/FilesTreeController.cs b/src/Umbraco.Web/Trees/FilesTreeController.cs index 60adb5048e..ae951bebf4 100644 --- a/src/Umbraco.Web/Trees/FilesTreeController.cs +++ b/src/Umbraco.Web/Trees/FilesTreeController.cs @@ -14,6 +14,6 @@ namespace Umbraco.Web.Trees protected override string[] Extensions => ExtensionsStatic; - protected override string FileIcon => "icon-document"; + protected override string FileIcon => Constants.Icons.MediaFile; } } diff --git a/src/Umbraco.Web/Trees/MacrosTreeController.cs b/src/Umbraco.Web/Trees/MacrosTreeController.cs index fcfd2e0b3b..cbe1946779 100644 --- a/src/Umbraco.Web/Trees/MacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/MacrosTreeController.cs @@ -36,7 +36,7 @@ namespace Umbraco.Web.Trees id, queryStrings, macro.Name, - "icon-settings-alt", + Constants.Icons.Macro, false)); } } diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs index 5798c546fc..53e30a4ee5 100644 --- a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -50,7 +50,7 @@ namespace Umbraco.Web.Trees // since 7.4+ child type creation is enabled by a config option. It defaults to on, but can be disabled if we decide to. // need this check to keep supporting sites where children have already been created. var hasChildren = dt.HasChildren; - var node = CreateTreeNode(dt, Constants.ObjectTypes.MediaType, id, queryStrings, "icon-thumbnails", hasChildren); + var node = CreateTreeNode(dt, Constants.ObjectTypes.MediaType, id, queryStrings, Constants.Icons.MediaType, hasChildren); node.Path = dt.Path; return node; diff --git a/src/Umbraco.Web/Trees/MemberGroupTreeController.cs b/src/Umbraco.Web/Trees/MemberGroupTreeController.cs index ea2412e4bd..54c499d717 100644 --- a/src/Umbraco.Web/Trees/MemberGroupTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberGroupTreeController.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Trees { return Services.MemberGroupService.GetAll() .OrderBy(x => x.Name) - .Select(dt => CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, "icon-item-arrangement", false)); + .Select(dt => CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, Constants.Icons.MemberGroup, false)); } protected override TreeNode CreateRootNode(FormDataCollection queryStrings) diff --git a/src/Umbraco.Web/Trees/MemberTreeController.cs b/src/Umbraco.Web/Trees/MemberTreeController.cs index bb0091af54..2657f13255 100644 --- a/src/Umbraco.Web/Trees/MemberTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTreeController.cs @@ -80,7 +80,7 @@ namespace Umbraco.Web.Trees "-1", queryStrings, member.Name, - "icon-user", + Constants.Icons.Member, false, "", Udi.Create(ObjectTypes.GetUdiType(Constants.ObjectTypes.Member), member.Key)); @@ -110,7 +110,7 @@ namespace Umbraco.Web.Trees "-1", queryStrings, member.UserName, - "icon-user", + Constants.Icons.Member, false); return node; @@ -124,14 +124,14 @@ namespace Umbraco.Web.Trees if (id == Constants.System.RootString) { nodes.Add( - CreateTreeNode(Constants.Conventions.MemberTypes.AllMembersListId, id, queryStrings, Services.TextService.Localize("member/allMembers"), "icon-users", true, + CreateTreeNode(Constants.Conventions.MemberTypes.AllMembersListId, id, queryStrings, Services.TextService.Localize("member/allMembers"), Constants.Icons.MemberType, true, queryStrings.GetRequiredValue("application") + TreeAlias.EnsureStartsWith('/') + "/list/" + Constants.Conventions.MemberTypes.AllMembersListId)); if (_isUmbracoProvider) { nodes.AddRange(Services.MemberTypeService.GetAll() .Select(memberType => - CreateTreeNode(memberType.Alias, id, queryStrings, memberType.Name, "icon-users", true, + CreateTreeNode(memberType.Alias, id, queryStrings, memberType.Name, Constants.Icons.MemberType, true, queryStrings.GetRequiredValue("application") + TreeAlias.EnsureStartsWith('/') + "/list/" + memberType.Alias))); } } diff --git a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs index 3a72460963..bd80f63897 100644 --- a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.Trees { return Services.MemberTypeService.GetAll() .OrderBy(x => x.Name) - .Select(dt => CreateTreeNode(dt, Constants.ObjectTypes.MemberType, id, queryStrings, "icon-item-arrangement", false)); + .Select(dt => CreateTreeNode(dt, Constants.ObjectTypes.MemberType, id, queryStrings, Constants.Icons.MemberType, false)); } } } diff --git a/src/Umbraco.Web/Trees/UserTreeController.cs b/src/Umbraco.Web/Trees/UserTreeController.cs index 7da0b689af..55d98def86 100644 --- a/src/Umbraco.Web/Trees/UserTreeController.cs +++ b/src/Umbraco.Web/Trees/UserTreeController.cs @@ -22,7 +22,7 @@ namespace Umbraco.Web.Trees //this will load in a custom UI instead of the dashboard for the root node root.RoutePath = $"{Constants.Applications.Users}/{Constants.Trees.Users}/users"; - root.Icon = "icon-users"; + root.Icon = Constants.Icons.UserGroup; root.HasChildren = false; return root; From 9ce996cbba49199423f4e79e223497d5c3c8cd4f Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 27 Jun 2019 22:42:05 +0200 Subject: [PATCH 056/776] V8: Don't show multiple open menus (take two) (#5609) --- .../directives/components/events/events.directive.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js index 15e74bbd90..abee5e2358 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js @@ -101,14 +101,14 @@ angular.module('umbraco.directives') var eventBindings = []; function oneTimeClick(event) { - var el = event.target.nodeName; - - //ignore link and button clicks - var els = ["INPUT", "A", "BUTTON"]; - if (els.indexOf(el) >= 0) { return; } + // ignore clicks on button groups toggles (i.e. the save and publish button) + var parents = $(event.target).closest("[data-element='button-group-toggle']"); + if (parents.length > 0) { + return; + } // ignore clicks on new overlay - var parents = $(event.target).parents("a,button,.umb-overlay,.umb-tour"); + parents = $(event.target).parents(".umb-overlay,.umb-tour"); if (parents.length > 0) { return; } From ee2ebc7d148e3b247583bf35cb96f57f6098c25a Mon Sep 17 00:00:00 2001 From: leekelleher Date: Fri, 7 Jun 2019 15:31:00 +0100 Subject: [PATCH 057/776] ReadOnlyValueController - enabled filters Previously the code checks if the `config` is an array, but with how config options are set, this would typically be an object (dictionary). So the previous code would never allow a filter to be set. Now the check is on the object property. --- .../readonlyvalue/readonlyvalue.controller.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/readonlyvalue/readonlyvalue.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/readonlyvalue/readonlyvalue.controller.js index 48ec95ba0e..a49ffeadaa 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/readonlyvalue/readonlyvalue.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/readonlyvalue/readonlyvalue.controller.js @@ -14,13 +14,7 @@ function ReadOnlyValueController($rootScope, $scope, $filter) { function formatDisplayValue() { - - if ($scope.model.config && - angular.isArray($scope.model.config) && - $scope.model.config.length > 0 && - $scope.model.config[0] && - $scope.model.config.filter) { - + if ($scope.model.config && $scope.model.config.filter) { if ($scope.model.config.format) { $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value, $scope.model.config.format); } else { @@ -29,12 +23,11 @@ function ReadOnlyValueController($rootScope, $scope, $filter) { } else { $scope.displayvalue = $scope.model.value; } - } //format the display value on init: formatDisplayValue(); - + $scope.$watch("model.value", function (newVal, oldVal) { //cannot just check for !newVal because it might be an empty string which we //want to look for. @@ -45,4 +38,4 @@ function ReadOnlyValueController($rootScope, $scope, $filter) { }); } -angular.module('umbraco').controller("Umbraco.PropertyEditors.ReadOnlyValueController", ReadOnlyValueController); \ No newline at end of file +angular.module('umbraco').controller("Umbraco.PropertyEditors.ReadOnlyValueController", ReadOnlyValueController); From 84aa861a5bca9263eae6c89927f6948ecbb96b50 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 28 Jun 2019 13:03:36 +1000 Subject: [PATCH 058/776] Merge remote-tracking branch 'origin/v7/dev' into v8/dev - Iniital commit (broken) # Conflicts: # build/NuSpecs/tools/Web.config.install.xdt # src/Umbraco.Core/Constants-DataTypes.cs # src/Umbraco.Core/Models/DataTypeDefinition.cs # src/Umbraco.Core/Models/DataTypeExtensions.cs # src/Umbraco.Core/Models/IDataTypeDefinition.cs # src/Umbraco.Core/Models/UmbracoEntity.cs # src/Umbraco.Core/Models/UserExtensions.cs # src/Umbraco.Core/Persistence/Factories/DataTypeDefinitionFactory.cs # src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs # src/Umbraco.Core/Persistence/Migrations/Initial/BaseDataCreation.cs # src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs # src/Umbraco.Core/Services/ContentService.cs # src/Umbraco.Core/Services/DataTypeService.cs # src/Umbraco.Core/Services/EntityService.cs # src/Umbraco.Core/Services/IContentService.cs # src/Umbraco.Core/Services/IDataTypeService.cs # src/Umbraco.Core/Services/IEntityService.cs # src/Umbraco.Core/Services/IRelationService.cs # src/Umbraco.Core/Services/Implement/RelationService.cs # src/Umbraco.Tests/Models/UmbracoEntityTests.cs # src/Umbraco.Tests/Plugins/PluginManagerTests.cs # src/Umbraco.Tests/Services/EntityServiceTests.cs # src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js # src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js # src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js # src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js # src/Umbraco.Web.UI.Client/src/common/services/search.service.js # src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js # src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js # src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.html # src/Umbraco.Web.UI.Client/src/views/common/dialogs/mediapicker.controller.js # src/Umbraco.Web.UI.Client/src/views/common/dialogs/treepicker.controller.js # src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js # src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html # src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js # src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html # src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js # src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html # src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html # src/Umbraco.Web.UI/packages.config # src/Umbraco.Web.UI/web.Template.config # src/Umbraco.Web/Editors/ContentController.cs # src/Umbraco.Web/Editors/EntityController.cs # src/Umbraco.Web/Editors/MediaController.cs # src/Umbraco.Web/HtmlHelperRenderExtensions.cs # src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs # src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs # src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs # src/Umbraco.Web/Mvc/RenderRouteHandler.cs # src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs # src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs # src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs # src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs # src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs # src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs # src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs # src/Umbraco.Web/Search/UmbracoTreeSearcher.cs # src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs # src/Umbraco.Web/Trees/ContentTreeController.cs # src/Umbraco.Web/Trees/ContentTreeControllerBase.cs # src/Umbraco.Web/Trees/MediaTreeController.cs # src/Umbraco.Web/Trees/TreeControllerBase.cs # src/Umbraco.Web/Trees/TreeQueryStringParameters.cs # src/Umbraco.Web/UmbracoHelper.cs # src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs # src/Umbraco.Web/WebBootManager.cs # src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseMediaTree.cs # src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseTree.cs # src/umbraco.cms/businesslogic/web/Access.cs --- .../tools/Views.Web.config.install.xdt | 10 +- src/Umbraco.Core/Constants-DataTypes.cs | 292 ++++++- src/Umbraco.Core/Models/DataTypeExtensions.cs | 48 ++ src/Umbraco.Core/Models/UserExtensions.cs | 13 - src/Umbraco.Core/Services/IContentService.cs | 3 + .../Plugins/PluginManagerTests.cs | 2 +- .../tree/umbtreesearchbox.directive.js | 7 + .../src/common/resources/content.resource.js | 602 +++++++------- .../src/common/resources/entity.resource.js | 176 ++-- .../src/common/resources/media.resource.js | 752 +++++++++--------- .../common/services/mediahelper.service.js | 38 +- .../src/common/services/search.service.js | 20 +- .../src/common/services/tinymce.service.js | 32 - .../mediapicker/mediapicker.controller.js | 60 +- .../contentpicker/contentpicker.controller.js | 10 +- .../grid/editors/media.controller.js | 15 +- .../multiurlpicker.controller.js | 2 + .../propertyeditors/rte/rte.prevalues.html | 1 + src/Umbraco.Web/Editors/EntityController.cs | 204 ++++- src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 5 +- .../ContentEditing/ContentPropertyBasic.cs | 6 +- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 2 + src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 9 +- .../Trees/ContentTreeController.cs | 1 - .../Trees/ContentTreeControllerBase.cs | 50 +- src/Umbraco.Web/Trees/MediaTreeController.cs | 2 +- src/Umbraco.Web/Trees/TreeControllerBase.cs | 1 + .../Trees/TreeQueryStringParameters.cs | 1 + src/Umbraco.Web/UmbracoHelper.cs | 1 + 29 files changed, 1469 insertions(+), 896 deletions(-) diff --git a/build/NuSpecs/tools/Views.Web.config.install.xdt b/build/NuSpecs/tools/Views.Web.config.install.xdt index 4d660301a8..828bb8612f 100644 --- a/build/NuSpecs/tools/Views.Web.config.install.xdt +++ b/build/NuSpecs/tools/Views.Web.config.install.xdt @@ -8,7 +8,7 @@ - + @@ -18,13 +18,13 @@ - + diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs index f2b31be28f..1e9115cde9 100644 --- a/src/Umbraco.Core/Constants-DataTypes.cs +++ b/src/Umbraco.Core/Constants-DataTypes.cs @@ -1,4 +1,6 @@ -namespace Umbraco.Core +using System; + +namespace Umbraco.Core { public static partial class Constants { @@ -22,6 +24,294 @@ public const int DefaultMembersListView = -97; public const int Tags = 1041; + + /// + /// Defines the identifiers for Umbraco data types as constants for easy centralized access/management. + /// + internal static class BuiltInDataTypes + { + + public static class ReservedPreValueKeys + { + public const string IgnoreUserStartNodes = "ignoreUserStartNodes"; + } + + /// + /// Guid for Content Picker as string + /// + public const string ContentPicker = "FD1E0DA5-5606-4862-B679-5D0CF3A52A59"; + + /// + /// Guid for Content Picker + /// + public static readonly Guid ContentPickerGuid = new Guid(ContentPicker); + + + /// + /// Guid for Member Picker as string + /// + public const string MemberPicker = "1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"; + + /// + /// Guid for Member Picker + /// + public static readonly Guid MemberPickerGuid = new Guid(MemberPicker); + + + /// + /// Guid for Media Picker as string + /// + public const string MediaPicker = "135D60E0-64D9-49ED-AB08-893C9BA44AE5"; + + /// + /// Guid for Media Picker + /// + public static readonly Guid MediaPickerGuid = new Guid(MediaPicker); + + + /// + /// Guid for Multiple Media Picker as string + /// + public const string MultipleMediaPicker = "9DBBCBBB-2327-434A-B355-AF1B84E5010A"; + + /// + /// Guid for Multiple Media Picker + /// + public static readonly Guid MultipleMediaPickerGuid = new Guid(MultipleMediaPicker); + + + /// + /// Guid for Related Links as string + /// + public const string RelatedLinks = "B4E3535A-1753-47E2-8568-602CF8CFEE6F"; + + /// + /// Guid for Related Links + /// + public static readonly Guid RelatedLinksGuid = new Guid(RelatedLinks); + + + /// + /// Guid for Member as string + /// + public const string Member = "d59be02f-1df9-4228-aa1e-01917d806cda"; + + /// + /// Guid for Member + /// + public static readonly Guid MemberGuid = new Guid(Member); + + + /// + /// Guid for Image Cropper as string + /// + public const string ImageCropper = "1df9f033-e6d4-451f-b8d2-e0cbc50a836f"; + + /// + /// Guid for Image Cropper + /// + public static readonly Guid ImageCropperGuid = new Guid(ImageCropper); + + + /// + /// Guid for Tags as string + /// + public const string Tags = "b6b73142-b9c1-4bf8-a16d-e1c23320b549"; + + /// + /// Guid for Tags + /// + public static readonly Guid TagsGuid = new Guid(Tags); + + + /// + /// Guid for List View - Content as string + /// + public const string ListViewContent = "C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"; + + /// + /// Guid for List View - Content + /// + public static readonly Guid ListViewContentGuid = new Guid(ListViewContent); + + + /// + /// Guid for List View - Media as string + /// + public const string ListViewMedia = "3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"; + + /// + /// Guid for List View - Media + /// + public static readonly Guid ListViewMediaGuid = new Guid(ListViewMedia); + + + /// + /// Guid for List View - Members as string + /// + public const string ListViewMembers = "AA2C52A0-CE87-4E65-A47C-7DF09358585D"; + + /// + /// Guid for List View - Members + /// + public static readonly Guid ListViewMembersGuid = new Guid(ListViewMembers); + + + /// + /// Guid for Date Picker with time as string + /// + public const string DatePickerWithTime = "e4d66c0f-b935-4200-81f0-025f7256b89a"; + + /// + /// Guid for Date Picker with time + /// + public static readonly Guid DatePickerWithTimeGuid = new Guid(DatePickerWithTime); + + + /// + /// Guid for Approved Color as string + /// + public const string ApprovedColor = "0225af17-b302-49cb-9176-b9f35cab9c17"; + + /// + /// Guid for Approved Color + /// + public static readonly Guid ApprovedColorGuid = new Guid(ApprovedColor); + + + /// + /// Guid for Dropdown multiple as string + /// + public const string DropdownMultiple = "f38f0ac7-1d27-439c-9f3f-089cd8825a53"; + + /// + /// Guid for Dropdown multiple + /// + public static readonly Guid DropdownMultipleGuid = new Guid(DropdownMultiple); + + + /// + /// Guid for Radiobox as string + /// + public const string Radiobox = "bb5f57c9-ce2b-4bb9-b697-4caca783a805"; + + /// + /// Guid for Radiobox + /// + public static readonly Guid RadioboxGuid = new Guid(Radiobox); + + + /// + /// Guid for Date Picker as string + /// + public const string DatePicker = "5046194e-4237-453c-a547-15db3a07c4e1"; + + /// + /// Guid for Date Picker + /// + public static readonly Guid DatePickerGuid = new Guid(DatePicker); + + + /// + /// Guid for Dropdown as string + /// + public const string Dropdown = "0b6a45e7-44ba-430d-9da5-4e46060b9e03"; + + /// + /// Guid for Dropdown + /// + public static readonly Guid DropdownGuid = new Guid(Dropdown); + + + /// + /// Guid for Checkbox list as string + /// + public const string CheckboxList = "fbaf13a8-4036-41f2-93a3-974f678c312a"; + + /// + /// Guid for Checkbox list + /// + public static readonly Guid CheckboxListGuid = new Guid(CheckboxList); + + + /// + /// Guid for Checkbox as string + /// + public const string Checkbox = "92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"; + + /// + /// Guid for Checkbox + /// + public static readonly Guid CheckboxGuid = new Guid(Checkbox); + + + /// + /// Guid for Numeric as string + /// + public const string Numeric = "2e6d3631-066e-44b8-aec4-96f09099b2b5"; + + /// + /// Guid for Dropdown + /// + public static readonly Guid NumericGuid = new Guid(Numeric); + + + /// + /// Guid for Richtext editor as string + /// + public const string RichtextEditor = "ca90c950-0aff-4e72-b976-a30b1ac57dad"; + + /// + /// Guid for Richtext editor + /// + public static readonly Guid RichtextEditorGuid = new Guid(RichtextEditor); + + + /// + /// Guid for Textstring as string + /// + public const string Textstring = "0cc0eba1-9960-42c9-bf9b-60e150b429ae"; + + /// + /// Guid for Textstring + /// + public static readonly Guid TextstringGuid = new Guid(Textstring); + + + /// + /// Guid for Textarea as string + /// + public const string Textarea = "c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"; + + /// + /// Guid for Dropdown + /// + public static readonly Guid TextareaGuid = new Guid(Textarea); + + + /// + /// Guid for Upload as string + /// + public const string Upload = "84c6b441-31df-4ffe-b67e-67d5bc3ae65a"; + + /// + /// Guid for Upload + /// + public static readonly Guid UploadGuid = new Guid(Upload); + + + /// + /// Guid for Label as string + /// + public const string Label = "f0bc4bfb-b499-40d6-ba86-058885a5178c"; + + /// + /// Guid for Label + /// + public static readonly Guid LabelGuid = new Guid(Label); + + + } } } } diff --git a/src/Umbraco.Core/Models/DataTypeExtensions.cs b/src/Umbraco.Core/Models/DataTypeExtensions.cs index df8a3caea8..a2603ad3df 100644 --- a/src/Umbraco.Core/Models/DataTypeExtensions.cs +++ b/src/Umbraco.Core/Models/DataTypeExtensions.cs @@ -35,5 +35,53 @@ namespace Umbraco.Core.Models throw new InvalidCastException($"Cannot cast dataType configuration, of type {configuration.GetType().Name}, to {typeof(T).Name}."); } + + private static readonly ISet IdsOfBuildInDataTypes = new HashSet() + { + Constants.DataTypes.ContentPickerGuid, + Constants.DataTypes.MemberPickerGuid, + Constants.DataTypes.MediaPickerGuid, + Constants.DataTypes.MultipleMediaPickerGuid, + Constants.DataTypes.RelatedLinksGuid, + Constants.DataTypes.MemberGuid, + Constants.DataTypes.ImageCropperGuid, + Constants.DataTypes.TagsGuid, + Constants.DataTypes.ListViewContentGuid, + Constants.DataTypes.ListViewMediaGuid, + Constants.DataTypes.ListViewMembersGuid, + Constants.DataTypes.DatePickerWithTimeGuid, + Constants.DataTypes.ApprovedColorGuid, + Constants.DataTypes.DropdownMultipleGuid, + Constants.DataTypes.RadioboxGuid, + Constants.DataTypes.DatePickerGuid, + Constants.DataTypes.DropdownGuid, + Constants.DataTypes.CheckboxListGuid, + Constants.DataTypes.CheckboxGuid, + Constants.DataTypes.NumericGuid, + Constants.DataTypes.RichtextEditorGuid, + Constants.DataTypes.TextstringGuid, + Constants.DataTypes.TextareaGuid, + Constants.DataTypes.UploadGuid, + Constants.DataTypes.LabelGuid, + }; + + /// + /// Returns true if this date type is build-in/default. + /// + /// The data type definition. + /// + internal static bool IsBuildInDataType(this IDataTypeDefinition dataType) + { + return IsBuildInDataType(dataType.Key); + } + + /// + /// Returns true if this date type is build-in/default. + /// + internal static bool IsBuildInDataType(Guid key) + { + return IdsOfBuildInDataTypes.Contains(key); + } + } } diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 0f83cf78a4..cf7df4fb86 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -193,19 +193,6 @@ namespace Umbraco.Core.Models return ContentPermissionsHelper.HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService), Constants.System.RecycleBinMedia); } - internal static bool IsInBranchOfStartNode(this IUser user, IUmbracoEntity entity, IEntityService entityService, int recycleBinId, out bool hasPathAccess) - { - switch (recycleBinId) - { - case Constants.System.RecycleBinMedia: - return ContentPermissionsHelper.IsInBranchOfStartNode(entity.Path, user.CalculateMediaStartNodeIds(entityService), user.GetMediaStartNodePaths(entityService), out hasPathAccess); - case Constants.System.RecycleBinContent: - return ContentPermissionsHelper.IsInBranchOfStartNode(entity.Path, user.CalculateContentStartNodeIds(entityService), user.GetContentStartNodePaths(entityService), out hasPathAccess); - default: - throw new NotSupportedException("Path access is only determined on content or media"); - } - } - /// /// Determines whether this user has access to view sensitive data /// diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 48e577a8f0..164edca1e6 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -526,5 +526,8 @@ namespace Umbraco.Core.Services OperationResult Rollback(int id, int versionId, string culture = "*", int userId = Constants.Security.SuperUserId); #endregion + + IEnumerable GetAnchorValuesFromRTEs(int id); + IEnumerable GetAnchorValuesFromRTEContent(string rteContent); } } diff --git a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs index 809354bd74..44879eae2f 100644 --- a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs +++ b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs @@ -327,7 +327,7 @@ AnotherContentFinder public void Resolves_RestExtensions() { var types = _manager.ResolveRestExtensions(); - Assert.AreEqual(3, types.Count()); + Assert.AreEqual(2, types.Count()); } [Test] diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js index 4ba4cf96bb..3b6fb5f9cd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js @@ -12,6 +12,7 @@ function treeSearchBox(localizationService, searchService, $q) { searchFromName: "@", showSearch: "@", section: "@", + datatypeId: "@", hideSearchCallback: "=", searchCallback: "=" }, @@ -34,6 +35,7 @@ function treeSearchBox(localizationService, searchService, $q) { scope.showSearch = "false"; } + //used to cancel any request in progress if another one needs to take it's place var canceler = null; @@ -60,6 +62,11 @@ function treeSearchBox(localizationService, searchService, $q) { searchArgs["searchFrom"] = scope.searchFromId; } + //append dataTypeId value if there is one + if (scope.datatypeId) { + searchArgs["dataTypeId"] = scope.datatypeId; + } + searcher(searchArgs).then(function (data) { scope.searchCallback(data); //set back to null so it can be re-created 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 b807a4dc31..d714ea4938 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 @@ -1,27 +1,27 @@ /** - * @ngdoc service - * @name umbraco.resources.contentResource - * @description Handles all transactions of content data - * from the angular application to the Umbraco database, using the Content WebApi controller - * - * all methods returns a resource promise async, so all operations won't complete untill .then() is completed. - * - * @requires $q - * @requires $http - * @requires umbDataFormatter - * @requires umbRequestHelper - * - * ##usage - * To use, simply inject the contentResource into any controller or service that needs it, and make - * sure the umbraco.resources module is accesible - which it should be by default. - * - *
-  *    contentResource.getById(1234)
-  *          .then(function(data) {
-  *              $scope.content = data;
+ * @ngdoc service
+ * @name umbraco.resources.contentResource
+ * @description Handles all transactions of content data
+ * from the angular application to the Umbraco database, using the Content WebApi controller
+ *
+ * all methods returns a resource promise async, so all operations won't complete untill .then() is completed.
+ *
+ * @requires $q
+ * @requires $http
+ * @requires umbDataFormatter
+ * @requires umbRequestHelper
+ *
+ * ##usage
+ * To use, simply inject the contentResource into any controller or service that needs it, and make
+ * sure the umbraco.resources module is accesible - which it should be by default.
+ *
+ * 
+ *    contentResource.getById(1234)
+ *          .then(function(data) {
+ *              $scope.content = data;
   *          });
   * 
- **/ + **/ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { @@ -79,27 +79,27 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @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;
-          *    });
+         * @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. - * - */ + * @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"; @@ -121,28 +121,28 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @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){
+         * @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. - * - */ + * @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"; @@ -164,29 +164,29 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @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){
+         * @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. - * - */ + * @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"; @@ -205,26 +205,26 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method + * @ngdoc method * @name umbraco.resources.contentResource#unpublish - * @methodOf umbraco.resources.contentResource - * - * @description - * Unpublishes a content item with a given Id - * - * ##usage - *
+         * @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){
+         *    .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. - * - */ + * @param {Int} id the ID of the node to unpublish + * @returns {Promise} resourcePromise object. + * + */ unpublish: function (id, cultures) { if (!id) { throw "id cannot be null"; @@ -242,7 +242,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { 'Failed to publish content with id ' + id); }, /** - * @ngdoc method + * @ngdoc method * @name umbraco.resources.contentResource#getCultureAndDomains * @methodOf umbraco.resources.contentResource * @@ -281,23 +281,23 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** * @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!');
-          *    });
+         * @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. - * - */ + * @returns {Promise} resourcePromise object. + * + */ emptyRecycleBin: function () { return umbRequestHelper.resourcePromise( $http.post( @@ -308,25 +308,25 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @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!');
-          *    });
+         * @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. - * - */ + * @returns {Promise} resourcePromise object. + * + */ deleteById: function (id) { return umbRequestHelper.resourcePromise( $http.post( @@ -348,27 +348,27 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @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) {
+         * @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!');
-          *    });
+         *        alert('its here!');
+         *    });
           * 
* * @param {Int} id id of content item to return * @param {Int} culture optional culture to retrieve the item in - * @returns {Promise} resourcePromise object containing the content item. - * - */ + * @returns {Promise} resourcePromise object containing the content item. + * + */ getById: function (id) { return umbRequestHelper.resourcePromise( $http.get( @@ -419,26 +419,26 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @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) {
+         * @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!');
-          *    });
+         *        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. - * - */ + * @returns {Promise} resourcePromise object containing the content items array. + * + */ getByIds: function (ids) { var idQuery = ""; @@ -464,37 +464,37 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { /** - * @ngdoc method - * @name umbraco.resources.contentResource#getScaffold - * @methodOf umbraco.resources.contentResource + * @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. * - * @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 + * - 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. + * 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;
+         * ##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");
-          *            });
-          *    });
+         *
+         *        contentResource.publish(myDoc, true)
+         *            .then(function(content){
+         *                alert("Retrieved, updated and published again");
+         *            });
+         *    });
           * 
* - * @param {Int} parentId id of content item to return + * @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. - * - */ + * @returns {Promise} resourcePromise object containing the content scaffold. + * + */ getScaffold: function (parentId, alias) { return umbRequestHelper.resourcePromise( @@ -524,25 +524,25 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @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!');
-          *    });
+         * @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. - * - */ + * @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( @@ -554,33 +554,33 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @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) {
+         * @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!');
-          *    });
+         *        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` + * @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` * @param {String} options.cultureName if provided, the results will be for this specific culture/variant - * @returns {Promise} resourcePromise object containing an array of content items. - * - */ + * @returns {Promise} resourcePromise object containing an array of content items. + * + */ getChildren: function (parentId, options) { var defaults = { @@ -651,34 +651,34 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @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 + * @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");
-          *            });
-          *    });
+         * ##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 {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 * @param {Bool} showNotifications an option to disable/show notifications (default is true) - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ save: function (content, isNew, files, showNotifications) { var endpoint = umbRequestHelper.getApiUrl( "contentApiBaseUrl", @@ -694,34 +694,34 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @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 + * @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");
-          *            });
-          *    });
+         * ##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 {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 * @param {Bool} showNotifications an option to disable/show notifications (default is true) - * @returns {Promise} resourcePromise object containing the saved content item. - * - */ + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ publish: function (content, isNew, files, showNotifications) { var endpoint = umbRequestHelper.getApiUrl( "contentApiBaseUrl", @@ -743,31 +743,31 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#sendToPublish - * @methodOf umbraco.resources.contentResource + * @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 * - * @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");
-          *            });
-          *    });
+         * ##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 {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. - * - */ + * @returns {Promise} resourcePromise object containing the saved content item. + * + */ sendToPublish: function (content, isNew, files, showNotifications) { var endpoint = umbRequestHelper.getApiUrl( "contentApiBaseUrl", @@ -797,25 +797,25 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { }, /** - * @ngdoc method - * @name umbraco.resources.contentResource#publishByid - * @methodOf umbraco.resources.contentResource + * @ngdoc method + * @name umbraco.resources.contentResource#publishByid + * @methodOf umbraco.resources.contentResource + * + * @description + * Publishes a content item with a given ID * - * @description - * Publishes a content item with a given ID - * - * ##usage - *
-          * contentResource.publishById(1234)
-          *    .then(function(content) {
-          *        alert("published");
-          *    });
+         * ##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. - * - */ + * @param {Int} id The ID of the conten to publish + * @returns {Promise} resourcePromise object containing the published content item. + * + */ publishById: function (id) { if (!id) { diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 753d180880..95bfd1e49b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -2,10 +2,10 @@ * @ngdoc service * @name umbraco.resources.entityResource * @description Loads in basic data for all entities - * + * * ##What is an entity? * An entity is a basic **read-only** representation of an Umbraco node. It contains only the most - * basic properties used to display the item in trees, lists and navigation. + * basic properties used to display the item in trees, lists and navigation. * * ##What is the difference between entity and content/media/etc...? * the entity only contains the basic node data, name, id and guid, whereas content @@ -15,7 +15,7 @@ * * ##Entity object types? * You need to specify the type of object you want returned. - * + * * The core object types are: * * - Document @@ -35,7 +35,7 @@ function entityResource($q, $http, umbRequestHelper) { //the factory object returned return { - + getSafeAlias: function (value, camelCase) { if (!value) { @@ -64,10 +64,10 @@ function entityResource($q, $http, umbRequestHelper) { * .then(function(pathArray) { * alert('its here!'); * }); - *
- * + *
+ * * @param {Int} id Id of node to return the public url to - * @param {string} type Object type name + * @param {string} type Object type name * @returns {Promise} resourcePromise object containing the url. * */ @@ -100,8 +100,8 @@ function entityResource($q, $http, umbRequestHelper) { * .then(function(url) { * alert('its here!'); * }); - *
- * + *
+ * * @param {Int} id Id of node to return the public url to * @param {string} type Object type name * @returns {Promise} resourcePromise object containing the url. @@ -135,17 +135,17 @@ function entityResource($q, $http, umbRequestHelper) { * //get media by id * entityResource.getEntityById(0, "Media") * .then(function(ent) { - * var myDoc = ent; + * var myDoc = ent; * alert('its here!'); * }); - *
- * + *
+ * * @param {Int} id id of entity to return - * @param {string} type Object type name + * @param {string} type Object type name * @returns {Promise} resourcePromise object containing the entity. * */ - getById: function (id, type) { + getById: function (id, type) { if (id === -1 || id === "-1") { return null; @@ -160,6 +160,40 @@ function entityResource($q, $http, umbRequestHelper) { 'Failed to retrieve entity data for id ' + id); }, + + getUrlAndAnchors: function (id) { + + if (id === -1 || id === "-1") { + return null; + } + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "GetUrlAndAnchors", + { id: id })), + 'Failed to retrieve url and anchors data for id ' + id); + }, + + + getAnchors: function (rteContent) { + + if (!rteContent || rteContent.length === 0) { + return []; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + 'GetAnchors'), + { + rteContent: rteContent + }), + 'Failed to anchors data for rte content ' + rteContent); + }, + /** * @ngdoc method * @name umbraco.resources.entityResource#getByIds @@ -173,18 +207,18 @@ function entityResource($q, $http, umbRequestHelper) { * //Get templates for ids * entityResource.getEntitiesByIds( [1234,2526,28262], "Template") * .then(function(templateArray) { - * var myDoc = contentArray; + * var myDoc = contentArray; * alert('they are here!'); * }); - *
- * + *
+ * * @param {Array} ids ids of entities to return as an array - * @param {string} type type name + * @param {string} type type name * @returns {Promise} resourcePromise object containing the entity array. * */ getByIds: function (ids, type) { - + var query = "type=" + type; return umbRequestHelper.resourcePromise( @@ -212,14 +246,14 @@ function entityResource($q, $http, umbRequestHelper) { * //get content by xpath * entityResource.getByQuery("$current", -1, "Document") * .then(function(ent) { - * var myDoc = ent; + * var myDoc = ent; * alert('its here!'); * }); - *
- * + *
+ * * @param {string} query xpath to use in query * @param {Int} nodeContextId id id to start from - * @param {string} type Object type name + * @param {string} type Object type name * @returns {Promise} resourcePromise object containing the entity. * */ @@ -247,12 +281,12 @@ function entityResource($q, $http, umbRequestHelper) { * //Only return media * entityResource.getAll("Media") * .then(function(ent) { - * var myDoc = ent; + * var myDoc = ent; * alert('its here!'); * }); - *
- * - * @param {string} type Object type name + *
+ * + * @param {string} type Object type name * @param {string} postFilter optional filter expression which will execute a dynamic where clause on the server * @returns {Promise} resourcePromise object containing the entity. * @@ -277,24 +311,33 @@ function entityResource($q, $http, umbRequestHelper) { * * @description * Gets ancestor entities for a given item - * - * + * + * * @param {string} type Object type name * @param {string} culture Culture * @returns {Promise} resourcePromise object containing the entity. * */ - getAncestors: function (id, type, culture) { + getAncestors: function (id, type, culture, options) { if (culture === undefined) culture = ""; + var args = [ + { id: id }, + { type: type }, + { culture: culture} + ]; + if (options && options.dataTypeId) { + args.push({ dataTypeId: options.dataTypeId }); + } + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetAncestors", - [{ id: id }, { type: type }, { culture: culture }])), - 'Failed to retrieve ancestor data for id ' + id); + args)), + 'Failed to retrieve ancestor data for id ' + id); }, - + /** * @ngdoc method * @name umbraco.resources.entityResource#getChildren @@ -302,20 +345,25 @@ function entityResource($q, $http, umbRequestHelper) { * * @description * Gets children entities for a given item - * + * * @param {Int} parentid id of content item to return children of - * @param {string} type Object type name + * @param {string} type Object type name * @returns {Promise} resourcePromise object containing the entity. * */ - getChildren: function (id, type) { + getChildren: function (id, type, options) { + + var args = [{ id: id }, { type: type }]; + if (options && options.dataTypeId) { + args.push({ dataTypeId: options.dataTypeId }); + } return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetChildren", - [{ id: id }, { type: type }])), + args)), 'Failed to retrieve child data for id ' + id); }, @@ -331,11 +379,11 @@ function entityResource($q, $http, umbRequestHelper) { *
           * entityResource.getPagedChildren(1234, "Content", {pageSize: 10, pageNumber: 2})
           *    .then(function(contentArray) {
-          *        var children = contentArray; 
+          *        var children = contentArray;
           *        alert('they are here!');
           *    });
-          * 
- * + *
+ * * @param {Int} parentid id of content item to return children of * @param {string} type Object type name * @param {Object} options optional options object @@ -383,7 +431,8 @@ function entityResource($q, $http, umbRequestHelper) { pageSize: options.pageSize, orderBy: options.orderBy, orderDirection: options.orderDirection, - filter: encodeURIComponent(options.filter) + filter: encodeURIComponent(options.filter), + dataTypeId: options.dataTypeId } )), 'Failed to retrieve child data for id ' + parentId); @@ -401,11 +450,11 @@ function entityResource($q, $http, umbRequestHelper) { *
           * entityResource.getPagedDescendants(1234, "Document", {pageSize: 10, pageNumber: 2})
           *    .then(function(contentArray) {
-          *        var children = contentArray; 
+          *        var children = contentArray;
           *        alert('they are here!');
           *    });
-          * 
- * + *
+ * * @param {Int} parentid id of content item to return descendants of * @param {string} type Object type name * @param {Object} options optional options object @@ -424,7 +473,8 @@ function entityResource($q, $http, umbRequestHelper) { pageNumber: 1, filter: '', orderDirection: "Ascending", - orderBy: "SortOrder" + orderBy: "SortOrder", + dataTypeId: null }; if (options === undefined) { options = {}; @@ -453,12 +503,14 @@ function entityResource($q, $http, umbRequestHelper) { pageSize: options.pageSize, orderBy: options.orderBy, orderDirection: options.orderDirection, - filter: encodeURIComponent(options.filter) + filter: encodeURIComponent(options.filter), + dataTypeId: options.dataTypeId } )), 'Failed to retrieve child data for id ' + parentId); }, - + + /** * @ngdoc method * @name umbraco.resources.entityResource#search @@ -471,23 +523,27 @@ function entityResource($q, $http, umbRequestHelper) { *
          * entityResource.search("news", "Media")
          *    .then(function(mediaArray) {
-         *        var myDoc = mediaArray; 
+         *        var myDoc = mediaArray;
          *        alert('they are here!');
          *    });
-         * 
- * - * @param {String} Query search query - * @param {String} Type type of conten to search + *
+ * + * @param {String} Query search query + * @param {String} Type type of conten to search * @returns {Promise} resourcePromise object containing the entity array. * */ - search: function (query, type, searchFrom, canceler) { + search: function (query, type, searchFrom, canceler, dataTypeId) { var args = [{ query: query }, { type: type }]; if (searchFrom) { args.push({ searchFrom: searchFrom }); } + if (dataTypeId) { + args.push({ dataTypeId: dataTypeId }); + } + var httpConfig = {}; if (canceler) { httpConfig["timeout"] = canceler; @@ -502,7 +558,7 @@ function entityResource($q, $http, umbRequestHelper) { httpConfig), 'Failed to retrieve entity data for query ' + query); }, - + /** * @ngdoc method @@ -516,12 +572,12 @@ function entityResource($q, $http, umbRequestHelper) { *
          * entityResource.searchAll("bob")
          *    .then(function(array) {
-         *        var myDoc = array; 
+         *        var myDoc = array;
          *        alert('they are here!');
          *    });
-         * 
- * - * @param {String} Query search query + *
+ * + * @param {String} Query search query * @returns {Promise} resourcePromise object containing the entity array. * */ @@ -541,7 +597,9 @@ function entityResource($q, $http, umbRequestHelper) { httpConfig), 'Failed to retrieve entity data for query ' + query); } - + + + }; } 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 1d6d5171a1..ca7700c188 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 @@ -1,16 +1,16 @@ /** - * @ngdoc service - * @name umbraco.resources.mediaResource - * @description Loads in data for media - **/ + * @ngdoc service + * @name umbraco.resources.mediaResource + * @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"), + "mediaApiBaseUrl", + "PostSave"), content: content, action: action, files: files, @@ -24,35 +24,35 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { getRecycleBin: function () { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRecycleBin")), - 'Failed to retrieve data for media recycle bin'); + $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. - * - */ + * @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"; @@ -65,37 +65,37 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"), - { - parentId: args.parentId, - idSortOrder: args.sortedIds - }), - 'Failed to sort media'); + $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. - * - */ + * @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"; @@ -108,121 +108,121 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { } return umbRequestHelper.resourcePromise( - $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), - { - parentId: args.parentId, - id: args.id + $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id }, {responseType: 'text'}), - { - error: function(data){ - var errorMsg = 'Failed to move media'; - if (data.id !== undefined && data.parentId !== undefined) { - if (data.id === data.parentId) { - errorMsg = 'Media can\'t be moved into itself'; - } - } - else if (data.notifications !== undefined) { - if (data.notifications.length > 0) { - if (data.notifications[0].header.length > 0) { - errorMsg = data.notifications[0].header; - } - if (data.notifications[0].message.length > 0) { - errorMsg = errorMsg + ": " + data.notifications[0].message; - } + { + error: function(data){ + var errorMsg = 'Failed to move media'; + if (data.id !== undefined && data.parentId !== undefined) { + if (data.id === data.parentId) { + errorMsg = 'Media can\'t be moved into itself'; + } + } + else if (data.notifications !== undefined) { + if (data.notifications.length > 0) { + if (data.notifications[0].header.length > 0) { + errorMsg = data.notifications[0].header; + } + if (data.notifications[0].message.length > 0) { + errorMsg = errorMsg + ": " + data.notifications[0].message; } } + } - return { - errorMsg: errorMsg - }; - } - }); + return { + errorMsg: errorMsg + }; + } + }); }, /** - * @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. - * - */ + * @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); + $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. - * - */ + * @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); + $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. - * - */ + * @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 = ""; @@ -231,96 +231,96 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { }); return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetByIds", - idQuery)), - 'Failed to retrieve data for media ids ' + ids); + $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. - * - */ + * @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); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetEmpty", + [{ contentTypeAlias: alias }, { parentId: parentId }])), + 'Failed to retrieve data for empty media item type ' + alias); }, rootMedia: function () { return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetRootMedia")), - 'Failed to retrieve data for root media'); + $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. - * - */ + * @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 = { @@ -361,111 +361,111 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { } 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); + $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#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. - * - */ + * @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. - * - */ + * @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'); + $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, - * + * @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, + * * NOTE: This will return a page of max 500 folders, if more is required it needs to be paged * and then folders are in the .items property of the returned promise data - * - * ##usage - *
-          * mediaResource.getChildFolders(1234)
+         *
+         * ##usage
+         * 
+         * mediaResource.getChildFolders(1234)
           *    .then(function(page) {
-          *        alert('folders');
-          *    });
-          * 
- * - * @param {int} parentId Id of the media item to query for child folders - * @returns {Promise} resourcePromise object. - * - */ + * 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; @@ -473,69 +473,69 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { //NOTE: This will return a max of 500 folders, if more is required it needs to be paged return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "GetChildFolders", - { + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "GetChildFolders", + { id: parentId, pageNumber: 1, pageSize: 500 - })), + })), '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. - * - */ + * @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'); + $http.post( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "EmptyRecycleBin")), + 'Failed to empty the recycle bin'); }, /** - * @ngdoc method - * @name umbraco.resources.mediaResource#search - * @methodOf umbraco.resources.mediaResource - * - * @description - * Paginated search for media items starting on the supplied nodeId - * - * ##usage - *
-          * mediaResource.search("my search", 1, 100, -1)
-          *    .then(function(searchResult) {
-          *        alert('it's here!');
-          *    });
-          * 
- * - * @param {string} query The search query - * @param {int} pageNumber The page number - * @param {int} pageSize The number of media items on a page - * @param {int} searchFrom NodeId to search from (-1 for root) - * @returns {Promise} resourcePromise object. - * - */ + * @ngdoc method + * @name umbraco.resources.mediaResource#search + * @methodOf umbraco.resources.mediaResource + * + * @description + * Paginated search for media items starting on the supplied nodeId + * + * ##usage + *
+         * mediaResource.search("my search", 1, 100, -1)
+         *    .then(function(searchResult) {
+         *        alert('it's here!');
+         *    });
+         * 
+ * + * @param {string} query The search query + * @param {int} pageNumber The page number + * @param {int} pageSize The number of media items on a page + * @param {int} searchFrom NodeId to search from (-1 for root) + * @returns {Promise} resourcePromise object. + * + */ search: function (query, pageNumber, pageSize, searchFrom) { var args = [ @@ -546,12 +546,12 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { ]; return umbRequestHelper.resourcePromise( - $http.get( - umbRequestHelper.getApiUrl( - "mediaApiBaseUrl", - "Search", - args)), - 'Failed to retrieve media items for search: ' + query); + $http.get( + umbRequestHelper.getApiUrl( + "mediaApiBaseUrl", + "Search", + args)), + 'Failed to retrieve media items for search: ' + query); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js index d85a59d836..9350af1c47 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js @@ -3,7 +3,7 @@ * @name umbraco.services.mediaHelper * @description A helper object used for dealing with media items **/ -function mediaHelper(umbRequestHelper) { +function mediaHelper(umbRequestHelper, $log) { //container of fileresolvers var _mediaFileResolvers = {}; @@ -13,11 +13,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#getImagePropertyValue * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Returns the file path associated with the media property if there is one - * + * * @param {object} options Options object * @param {object} options.mediaModel The media object to retrieve the image path from * @param {object} options.imageOnly Optional, if true then will only return a path if the media item is an image @@ -80,11 +80,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#getImagePropertyValue * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Returns the actual image path associated with the image property if there is one - * + * * @param {object} options Options object * @param {object} options.imageModel The media object to retrieve the image path from */ @@ -104,11 +104,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#getThumbnail * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * formats the display model used to display the content to the model used to save the content - * + * * @param {object} options Options object * @param {object} options.imageModel The media object to retrieve the image path from */ @@ -133,18 +133,20 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#resolveFileFromEntity * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Gets the media file url for a media entity returned with the entityResource - * + * * @param {object} mediaEntity A media Entity returned from the entityResource * @param {boolean} thumbnail Whether to return the thumbnail url or normal url */ resolveFileFromEntity: function (mediaEntity, thumbnail) { if (!angular.isObject(mediaEntity.metaData) || !mediaEntity.metaData.MediaPath) { - throw "Cannot resolve the file url from the mediaEntity, it does not contain the required metaData"; + //don't throw since this image legitimately might not contain a media path, but output a warning + $log.warn("Cannot resolve the file url from the mediaEntity, it does not contain the required metaData"); + return null; } if (thumbnail) { @@ -164,11 +166,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#resolveFile * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Gets the media file url for a media object returned with the mediaResource - * + * * @param {object} mediaEntity A media Entity returned from the entityResource * @param {boolean} thumbnail Whether to return the thumbnail url or normal url */ @@ -240,11 +242,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#scaleToMaxSize * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Finds the corrct max width and max height, given maximum dimensions and keeping aspect ratios - * + * * @param {number} maxSize Maximum width & height * @param {number} width Current width * @param {number} height Current height @@ -283,11 +285,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#getThumbnailFromPath * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Returns the path to the thumbnail version of a given media library image path - * + * * @param {string} imagePath Image path, ex: /media/1234/my-image.jpg */ getThumbnailFromPath: function (imagePath) { @@ -310,11 +312,11 @@ function mediaHelper(umbRequestHelper) { * @ngdoc function * @name umbraco.services.mediaHelper#detectIfImageByExtension * @methodOf umbraco.services.mediaHelper - * @function + * @function * * @description * Returns true/false, indicating if the given path has an allowed image extension - * + * * @param {string} imagePath Image path, ex: /media/1234/my-image.jpg */ detectIfImageByExtension: function (imagePath) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js index 04c431767c..cdba3647a3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js @@ -2,7 +2,7 @@ * @ngdoc service * @name umbraco.services.searchService * - * + * * @description * Service for handling the main application search, can currently search content, media and members * @@ -15,10 +15,10 @@ * angular.forEach(results, function(result){ * //returns: * {name: "name", id: 1234, menuUrl: "url", editorPath: "url", metaData: {}, subtitle: "/path/etc" } - * }) - * var result = - * }) - * + * }) + * var result = + * }) + * */ angular.module('umbraco.services') .factory('searchService', function ($q, $log, entityResource, contentResource, umbRequestHelper, $injector, searchResultFormatter) { @@ -67,7 +67,7 @@ angular.module('umbraco.services') throw "args.term is required"; } - return entityResource.search(args.term, "Document", args.searchFrom, args.canceler).then(function (data) { + return entityResource.search(args.term, "Document", args.searchFrom, args.canceler, args.dataTypeId).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureContentResult(item); }); @@ -92,7 +92,7 @@ angular.module('umbraco.services') throw "args.term is required"; } - return entityResource.search(args.term, "Media", args.searchFrom).then(function (data) { + return entityResource.search(args.term, "Media", args.searchFrom, args.canceler, args.dataTypeId).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureMediaResult(item); }); @@ -122,7 +122,7 @@ angular.module('umbraco.services') _.each(data, function (resultByType) { //we need to format the search result data to include things like the subtitle, urls, etc... - // this is done with registered angular services as part of the SearchableTreeAttribute, if that + // this is done with registered angular services as part of the SearchableTreeAttribute, if that // is not found, than we format with the default formatter var formatterMethod = searchResultFormatter.configureDefaultResult; //check if a custom formatter is specified... @@ -143,7 +143,7 @@ angular.module('umbraco.services') _.each(resultByType.results, function (item) { formatterMethod.apply(this, [item, resultByType.treeAlias, resultByType.appAlias]); }); - + }); return data; @@ -157,4 +157,4 @@ angular.module('umbraco.services') var currentSection = sectionAlias; } }; - }); \ No newline at end of file + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index f72c447627..01eef916b1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -895,38 +895,6 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s }, - /** - * @ngdoc method - * @name umbraco.services.tinyMceService#getAnchorNames - * @methodOf umbraco.services.tinyMceService - * - * @description - * From the given string, generates a string array where each item is the id attribute value from a named anchor - * 'some string
with a named anchor' returns ['anchor'] - * - * @param {string} input the string to parse - */ - getAnchorNames: function (input) { - var anchors = []; - if (!input) { - return anchors; - } - - var anchorPattern = //gi; - var matches = input.match(anchorPattern); - - - if (matches) { - anchors = matches.map(function (v) { - return v.substring(v.indexOf('"') + 1, v.lastIndexOf('\\')); - }); - } - - return anchors.filter(function(val, i, self) { - return self.indexOf(val) === i; - }); - }, - insertLinkInEditor: function (editor, target, anchorElm) { var href = target.url; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index 3a0deb812e..23d9cef9a1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -1,7 +1,7 @@ //used for the media picker dialog angular.module("umbraco") .controller("Umbraco.Editors.MediaPickerController", - function($scope, mediaResource, entityResource, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService, editorService) { + function($scope, mediaResource, entityResource, userService, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService, editorService) { if (!$scope.model.title) { localizationService.localizeMany(["defaultdialogs_selectMedia", "general_includeFromsubFolders"]) @@ -25,6 +25,8 @@ angular.module("umbraco") $scope.lockedFolder = true; $scope.allowMediaEdit = dialogOptions.allowMediaEdit ? dialogOptions.allowMediaEdit : false; + var userStartNodes = []; + var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings; var allowedUploadFiles = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles); if ($scope.onlyImages) { @@ -54,7 +56,8 @@ angular.module("umbraco") pageSize: 100, totalItems: 0, totalPages: 0, - filter: '' + filter: '', + dataTypeId: $scope.model.dataTypeId }; //preload selected item @@ -64,15 +67,19 @@ angular.module("umbraco") } function onInit() { - if ($scope.startNodeId !== -1) { - entityResource.getById($scope.startNodeId, "media") - .then(function (ent) { - $scope.startNodeId = ent.id; - run(); - }); - } else { - run(); - } + userService.getCurrentUser().then(function (userData) { + userStartNodes = userData.startMediaIds; + + if ($scope.startNodeId !== -1) { + entityResource.getById($scope.startNodeId, "media") + .then(function (ent) { + $scope.startNodeId = ent.id; + run(); + }); + } else { + run(); + } + }); } function run() { @@ -89,7 +96,7 @@ angular.module("umbraco") //media object so we need to look it up var id = $scope.target.udi ? $scope.target.udi : $scope.target.id; var altText = $scope.target.altText; - mediaResource.getById(id) + entityResource.getById(id, "Media") .then(function (node) { $scope.target = node; if (ensureWithinStartNode(node)) { @@ -153,7 +160,7 @@ angular.module("umbraco") } if (folder.id > 0) { - entityResource.getAncestors(folder.id, "media") + entityResource.getAncestors(folder.id, "media", { dataTypeId: $scope.model.dataTypeId }) .then(function(anc) { $scope.path = _.filter(anc, function(f) { @@ -169,7 +176,7 @@ angular.module("umbraco") $scope.path = []; } - $scope.lockedFolder = folder.id === -1 && $scope.model.startNodeIsVirtual; + $scope.lockedFolder = (folder.id === -1 && $scope.model.startNodeIsVirtual) || hasFolderAccess(folder) === false; $scope.currentFolder = folder; localStorageService.set("umbLastOpenedMediaNodeId", folder.id); @@ -265,6 +272,17 @@ angular.module("umbraco") } } + function hasFolderAccess(node) { + var nodePath = node.path ? node.path.split(',') : [node.id]; + + for (var i = 0; i < nodePath.length; i++) { + if (userStartNodes.indexOf(parseInt(nodePath[i])) !== -1) + return true; + } + + return false; + } + function gotoStartNode(err) { $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); } @@ -299,7 +317,8 @@ angular.module("umbraco") pageSize: 100, totalItems: 0, totalPages: 0, - filter: '' + filter: '', + dataTypeId: $scope.model.dataTypeId }; getChildren($scope.currentFolder.id); } @@ -369,10 +388,17 @@ angular.module("umbraco") function getChildren(id) { $scope.loading = true; - return mediaResource.getChildren(id) + return entityResource.getChildren(id, "Media", $scope.searchOptions) .then(function(data) { + + for (i=0;i= 0; @@ -210,6 +211,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper //dialog $scope.openCurrentPicker = function () { $scope.currentPicker = dialogOptions; + $scope.contentPickerOverlay.dataTypeId = $scope.model.dataTypeId; $scope.currentPicker.submit = function (model) { if (angular.isArray(model.selection)) { @@ -401,7 +403,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper // get url for content and media items if (entityType !== "Member") { entityResource.getUrl(entity.id, entityType).then(function (data) { - // update url + // update url angular.forEach($scope.renderModel, function (item) { if (item.id === entity.id) { if (entity.trashed) { @@ -447,7 +449,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper "url": item.url, "trashed": item.trashed, "published": (item.metaData && item.metaData.IsPublished === false && entityType === "Document") ? false : true - // only content supports published/unpublished content so we set everything else to published so the UI looks correct + // only content supports published/unpublished content so we set everything else to published so the UI looks correct }); setEntityUrl(item); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index eb1032a9c7..2d8e15ea57 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -7,15 +7,22 @@ angular.module("umbraco") if (!$scope.model.config.startNodeId) { - userService.getCurrentUser().then(function (userData) { - $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; - }); + if ($scope.model.config.ignoreUserStartNodes === "1" ) { + $scope.model.config.startNodeId = -1; + $scope.model.config.startNodeIsVirtual = true; + + } else { + userService.getCurrentUser().then(function (userData) { + $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; + }); + } } $scope.setImage = function(){ var startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined; var startNodeIsVirtual = startNodeId ? $scope.model.config.startNodeIsVirtual : undefined; + $scope.mediaPickerOverlay.dataTypeId = $scope.model.dataTypeId; var mediaPicker = { startNodeId: startNodeId, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js index 734b06536d..dccf9887ec 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js @@ -71,6 +71,8 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en var linkPicker = { currentTarget: target, + dataTypeId: $scope.model.dataTypeId, + ignoreUserStartNodes : $scope.model.config.ignoreUserStartNodes, submit: function (model) { if (model.target.url || model.target.anchor) { // if an anchor exists, check that it is appropriately prefixed diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html index 4aa424396d..ade75a1cd6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html @@ -47,4 +47,5 @@ + diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index dbd17205bb..d83ade5a13 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -36,7 +36,12 @@ namespace Umbraco.Web.Editors /// The API controller used for getting entity objects, basic name, icon, id representation of umbraco objects that are based on CMSNode ///
/// - /// Some objects such as macros are not based on CMSNode + /// + /// This controller allows resolving basic entity data for various entities without placing the hard restrictions on users that may not have access + /// to the sections these entities entities exist in. This is to allow pickers, etc... of data to work for all users. In some cases such as accessing + /// Members, more explicit security checks are done. + /// + /// Some objects such as macros are not based on CMSNode /// [EntityControllerConfiguration] [PluginController("UmbracoApi")] @@ -98,19 +103,20 @@ namespace Umbraco.Web.Editors /// /// A starting point for the search, generally a node id, but for members this is a member type alias /// + /// If set used to look up whether user and group start node permissions will be ignored. /// [HttpGet] - public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null) + public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null, Guid? dataTypeId = null) { - // TODO: Should we restrict search results based on what app the user has access to? - // - Theoretically you shouldn't be able to see member data if you don't have access to members right? + // NOTE: Theoretically you shouldn't be able to see member data if you don't have access to members right? ... but there is a member picker, so can't really do that if (string.IsNullOrEmpty(query)) return Enumerable.Empty(); //TODO: This uses the internal UmbracoTreeSearcher, this instead should delgate to the ISearchableTree implementation for the type - return ExamineSearch(query, type, searchFrom); + var ignoreUserStartNodes = dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); + return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes); } /// @@ -204,6 +210,9 @@ namespace Umbraco.Web.Editors /// Int id of the entity to fetch URL for /// The type of entity such as Document, Media, Member /// The URL or path to the item + /// + /// We are not restricting this with security because there is no sensitive data + /// public HttpResponseMessage GetUrl(int id, UmbracoEntityTypes type) { var returnUrl = string.Empty; @@ -277,6 +286,29 @@ namespace Umbraco.Web.Editors publishedContentExists: i => Umbraco.Content(i) != null); } + + [HttpGet] + public UrlAndAnchors GetUrlAndAnchors([FromUri]int id) + { + var url = Umbraco.Url(id); + var anchorValues = Services.ContentService.GetAnchorValuesFromRTEs(id); + return new UrlAndAnchors(url, anchorValues); + } + + public class AnchorsModel + { + public string RteContent { get; set; } + } + + [HttpGet] + [HttpPost] + public IEnumerable GetAnchors(AnchorsModel model) + { + var anchorValues = Services.ContentService.GetAnchorValuesFromRTEContent(model.RteContent); + return anchorValues; + } + + #region GetById /// @@ -397,9 +429,52 @@ namespace Umbraco.Web.Editors } #endregion - public IEnumerable GetChildren(int id, UmbracoEntityTypes type) + public IEnumerable GetChildren(int id, UmbracoEntityTypes type, Guid? dataTypeId = null) { - return GetResultForChildren(id, type); + var objectType = ConvertToObjectType(type); + if (objectType.HasValue) + { + //TODO: Need to check for Object types that support hierarchy here, some might not. + + int[] startNodes = null; + switch (type) + { + case UmbracoEntityTypes.Document: + startNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + startNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + } + + var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); + + // root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes + if (id == Constants.System.Root && startNodes.Length > 0 && startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes) + { + var nodes = Services.EntityService.GetAll(objectType.Value, startNodes).ToArray(); + if (nodes.Length == 0) + return Enumerable.Empty(); + var pr = new List(nodes.Select(Mapper.Map)); + return pr; + } + + // else proceed as usual + + return Services.EntityService.GetChildren(id, objectType.Value) + .WhereNotNull() + .Select(Mapper.Map); + } + //now we need to convert the unknown ones + switch (type) + { + case UmbracoEntityTypes.Domain: + case UmbracoEntityTypes.Language: + case UmbracoEntityTypes.User: + case UmbracoEntityTypes.Macro: + default: + throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type); + } } /// @@ -420,7 +495,8 @@ namespace Umbraco.Web.Editors int pageSize, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, - string filter = "") + string filter = "", + Guid? dataTypeId = null) { if (int.TryParse(id, out var intId)) { @@ -445,7 +521,7 @@ namespace Umbraco.Web.Editors //the EntityService can search paged members from the root intId = -1; - return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter); + return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter, dataTypeId); } //the EntityService cannot search members of a certain type, this is currently not supported and would require @@ -480,7 +556,8 @@ namespace Umbraco.Web.Editors int pageSize, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, - string filter = "") + string filter = "", + Guid? dataTypeId = null) { if (pageNumber <= 0) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -490,12 +567,47 @@ namespace Umbraco.Web.Editors var objectType = ConvertToObjectType(type); if (objectType.HasValue) { - var entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out var totalRecords, + IEnumerable entities; + long totalRecords; + + int[] startNodes = null; + switch (type) + { + case UmbracoEntityTypes.Document: + startNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + startNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + } + + var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); + + // root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes + if (id == Constants.System.Root && startNodes.Length > 0 && startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes) + { + if (pageNumber > 0) + return new PagedResult(0, 0, 0); + var nodes = Services.EntityService.GetAll(objectType.Value, startNodes).ToArray(); + if (nodes.Length == 0) + return new PagedResult(0, 0, 0); + if (pageSize < nodes.Length) pageSize = nodes.Length; // bah + var pr = new PagedResult(nodes.Length, pageNumber, pageSize) + { + Items = nodes.Select(Mapper.Map) + }; + return pr; + } + + // else proceed as usual + //entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out var totalRecords, filter.IsNullOrWhiteSpace() ? null : SqlContext.Query().Where(x => x.Name.Contains(filter)), Ordering.By(orderBy, orderDirection)); + if (totalRecords == 0) { return new PagedResult(0, 0, 0); @@ -532,6 +644,7 @@ namespace Umbraco.Web.Editors } } + public PagedResult GetPagedDescendants( int id, UmbracoEntityTypes type, @@ -539,7 +652,8 @@ namespace Umbraco.Web.Editors int pageSize, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, - string filter = "") + string filter = "", + Guid? dataTypeId = null) { if (pageNumber <= 0) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -567,7 +681,8 @@ namespace Umbraco.Web.Editors break; } - entities = aids == null || aids.Contains(Constants.System.Root) + var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); + entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes ? Services.EntityService.GetPagedDescendants(objectType.Value, pageNumber - 1, pageSize, out totalRecords, SqlContext.Query().Where(x => x.Name.Contains(filter)), Ordering.By(orderBy, orderDirection), includeTrashed: false) @@ -609,6 +724,8 @@ namespace Umbraco.Web.Editors } } + private bool IsDataTypeIgnoringUserStartNodes(Guid? dataTypeId) => dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); + public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) { return GetResultForAncestors(id, type, queryStrings); @@ -620,10 +737,11 @@ namespace Umbraco.Web.Editors /// /// /// + /// If set to true, user and group start node permissions will be ignored. /// - private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null) + private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false) { - return _treeSearcher.ExamineSearch(query, entityType, 200, 0, out _, searchFrom); + return _treeSearcher.ExamineSearch(query, entityType, 200, 0, out _, searchFrom, ignoreUserStartNodes); } private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes entityType) @@ -658,35 +776,39 @@ namespace Umbraco.Web.Editors var ids = Services.EntityService.Get(id).Path.Split(',').Select(int.Parse).Distinct().ToArray(); - int[] aids = null; - switch (entityType) + var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); + if (ignoreUserStartNodes == false) { - case UmbracoEntityTypes.Document: - aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); - break; - case UmbracoEntityTypes.Media: - aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); - break; - } - - if (aids != null) - { - var lids = new List(); - var ok = false; - foreach (var i in ids) + int[] aids = null; + switch (entityType) { - if (ok) - { - lids.Add(i); - continue; - } - if (aids.Contains(i)) - { - lids.Add(i); - ok = true; - } + case UmbracoEntityTypes.Document: + aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + } + + if (aids != null) + { + var lids = new List(); + var ok = false; + foreach (var i in ids) + { + if (ok) + { + lids.Add(i); + continue; + } + if (aids.Contains(i)) + { + lids.Add(i); + ok = true; + } + } + ids = lids.ToArray(); } - ids = lids.ToArray(); } var culture = queryStrings?.GetValue("culture"); diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 274cc06dd2..e9b731348d 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -230,8 +230,10 @@ namespace Umbraco.Web //that each token is unique to the controller/action/area instead of the default ASP.Net implementation which is that the token is unique //per user. _viewContext.HttpContext.Items["ufprt"] = _encryptedString; + } + private readonly ViewContext _viewContext; private readonly FormMethod _method; private bool _disposed; @@ -243,7 +245,6 @@ namespace Umbraco.Web if (this._disposed) return; this._disposed = true; - //Detect if the call is targeting UmbRegisterController/UmbProfileController/UmbLoginStatusController/UmbLoginController and if it is we automatically output a AntiForgeryToken() // We have a controllerName and area so we can match if (_controllerName == "UmbRegister" @@ -251,7 +252,7 @@ namespace Umbraco.Web || _controllerName == "UmbLoginStatus" || _controllerName == "UmbLogin") { - _viewContext.Writer.Write(AntiForgery.GetHtml().ToString()); + _viewContext.Writer.Write(AntiForgery.GetHtml().ToString()); } //write out the hidden surface form routes diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs index d9d730956a..7d6d066e35 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs @@ -1,4 +1,5 @@ -using System.ComponentModel; +using System; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Core.PropertyEditors; @@ -21,6 +22,9 @@ namespace Umbraco.Web.Models.ContentEditing [Required] public int Id { get; set; } + [DataMember(Name = "dataTypeId", IsRequired = false)] + public Guid? DataTypeId { get; set; } + [DataMember(Name = "value")] public object Value { get; set; } diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index aa2e5f18a7..284c1344b6 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -155,6 +155,8 @@ namespace Umbraco.Web.Mvc }; } + + /// /// Handles a posted form to an Umbraco Url and ensures the correct controller is routed to and that /// the right DataTokens are set. diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index 43db9ff0ba..19c0d0ed03 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -49,6 +49,7 @@ namespace Umbraco.Web.Search /// /// /// + /// If set to true, user and group start node permissions will be ignored. /// public IEnumerable ExamineSearch( string query, @@ -85,12 +86,12 @@ namespace Umbraco.Web.Search case UmbracoEntityTypes.Media: type = "media"; var allMediaStartNodes = _umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(_entityService); - AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, _entityService); + AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, _entityService); break; case UmbracoEntityTypes.Document: type = "content"; var allContentStartNodes = _umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(_entityService); - AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, _entityService); + AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, ignoreUserStartNodes, _entityService); break; default: throw new NotSupportedException("The " + typeof(UmbracoTreeSearcher) + " currently does not support searching against object type " + entityType); @@ -288,7 +289,7 @@ namespace Umbraco.Web.Search } } - private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, int[] startNodeIds, string searchFrom, IEntityService entityService) + private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, int[] startNodeIds, string searchFrom, bool ignoreUserStartNodes, IEntityService entityService) { if (sb == null) throw new ArgumentNullException(nameof(sb)); if (entityService == null) throw new ArgumentNullException(nameof(entityService)); @@ -311,7 +312,7 @@ namespace Umbraco.Web.Search // make sure we don't find anything sb.Append("+__Path:none "); } - else if (startNodeIds.Contains(-1) == false) // -1 = no restriction + else if (startNodeIds.Contains(-1) == false && ignoreUserStartNodes == false) // -1 = no restriction { var entityPaths = entityService.GetAllPaths(objectType, startNodeIds); diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 9e481fc4c9..970191e510 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -217,7 +217,6 @@ namespace Umbraco.Web.Trees return result; } - /// /// Returns a collection of all menu items that can be on a content node /// diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 1b8f3b1434..20d7389da1 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -69,7 +69,7 @@ namespace Umbraco.Web.Trees { var node = base.CreateRootNode(queryStrings); - if (IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false) + if (IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false && IgnoreUserStartNodes(queryStrings) == false) { node.AdditionalData["noAccess"] = true; } @@ -87,7 +87,8 @@ namespace Umbraco.Web.Trees /// /// /// - internal TreeNode GetSingleTreeNodeWithAccessCheck(IEntitySlim e, string parentId, FormDataCollection queryStrings) + internal TreeNode GetSingleTreeNodeWithAccessCheck(IEntitySlim e, string parentId, FormDataCollection queryStrings, + int[] startNodeIds, string[] startNodePaths, bool ignoreUserStartNodes) { var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out var hasPathAccess); if (entityIsAncestorOfStartNodes == false) @@ -101,6 +102,23 @@ namespace Umbraco.Web.Trees return treeNode; } + private void GetUserStartNodes(out int[] startNodeIds, out string[] startNodePaths) + { + switch (RecycleBinId) + { + case Constants.System.RecycleBinMedia: + startNodeIds = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + startNodePaths = Security.CurrentUser.GetMediaStartNodePaths(Services.EntityService); + break; + case Constants.System.RecycleBinContent: + startNodeIds = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + startNodePaths = Security.CurrentUser.GetContentStartNodePaths(Services.EntityService); + break; + default: + throw new NotSupportedException("Path access is only determined on content or media"); + } + } + /// /// Returns the /// @@ -127,6 +145,8 @@ namespace Umbraco.Web.Trees ? queryStrings.GetValue(TreeQueryStringParameters.StartNodeId) : string.Empty; + var ignoreUserStartNodes = IgnoreUserStartNodes(queryStrings); + if (string.IsNullOrEmpty(startNodeId) == false && startNodeId != "undefined" && startNodeId != rootIdString) { // request has been made to render from a specific, non-root, start node @@ -134,7 +154,7 @@ namespace Umbraco.Web.Trees // ensure that the user has access to that node, otherwise return the empty tree nodes collection // TODO: in the future we could return a validation statement so we can have some UI to notify the user they don't have access - if (HasPathAccess(id, queryStrings) == false) + if (ignoreUserStartNodes == false && HasPathAccess(id, queryStrings) == false) { Logger.Warn("User {Username} does not have access to node with id {Id}", Security.CurrentUser.Username, id); return nodes; @@ -152,7 +172,11 @@ namespace Umbraco.Web.Trees // get child entities - if id is root, but user's start nodes do not contain the // root node, this returns the start nodes instead of root's children var entities = GetChildEntities(id, queryStrings).ToList(); - nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings)).Where(x => x != null)); + + //get the current user start node/paths + GetUserStartNodes(out var userStartNodes, out var userStartNodePaths); + + nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths, ignoreUserStartNodes)).Where(x => x != null)); // if the user does not have access to the root node, what we have is the start nodes, // but to provide some context we also need to add their topmost nodes when they are not @@ -163,7 +187,7 @@ namespace Umbraco.Web.Trees if (topNodeIds.Length > 0) { var topNodes = Services.EntityService.GetAll(UmbracoObjectType, topNodeIds.ToArray()); - nodes.AddRange(topNodes.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings)).Where(x => x != null)); + nodes.AddRange(topNodes.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths, ignoreUserStartNodes)).Where(x => x != null)); } } @@ -504,5 +528,21 @@ namespace Umbraco.Web.Trees } private readonly ConcurrentDictionary _entityCache = new ConcurrentDictionary(); + + /// + /// If the request should allows a user to choose nodes that they normally don't have access to + /// + /// + /// + internal bool IgnoreUserStartNodes(FormDataCollection queryStrings) + { + var dataTypeId = queryStrings.GetValue(TreeQueryStringParameters.DataTypeId); + if (dataTypeId.HasValue) + { + return Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); + } + + return false; + } } } diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index 22ad4ed355..f4f373f9a4 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -166,6 +166,6 @@ namespace Umbraco.Web.Trees { return _treeSearcher.ExamineSearch(query, UmbracoEntityTypes.Media, pageSize, pageIndex, out totalFound, searchFrom); } - + } } diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index 4acf807b77..0f9b61469e 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Services; using Umbraco.Web.Models.Trees; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; +using Umbraco.Core.Services; namespace Umbraco.Web.Trees { diff --git a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs index 466aff5a1f..9d012cb25c 100644 --- a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs +++ b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs @@ -8,6 +8,7 @@ public const string Use = "use"; public const string Application = "application"; public const string StartNodeId = "startNodeId"; + public const string DataTypeId = "dataTypeId"; //public const string OnNodeClick = "OnNodeClick"; //public const string RenderParent = "RenderParent"; } diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index a330f66c43..bf017c73cc 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -5,6 +5,7 @@ using System.Web; using System.Xml.XPath; using Umbraco.Core; using Umbraco.Core.Dictionary; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Xml; From 568835f7e2a51fa8ca85e5f61fcd566956e63e94 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 28 Jun 2019 13:24:13 +1000 Subject: [PATCH 059/776] manual merging/fixing, project builds now, but need to manually merge the remaining --- src/Umbraco.Core/Constants-DataTypes.cs | 18 ++++--- src/Umbraco.Core/Models/DataTypeExtensions.cs | 52 +++++++++---------- src/Umbraco.Core/Services/IContentService.cs | 4 +- src/Umbraco.Core/Services/IDataTypeService.cs | 1 + .../Services/Implement/ContentService.cs | 35 +++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 2 + src/Umbraco.Web/Editors/EntityController.cs | 11 ++-- .../Models/ContentEditing/UrlAndAnchors.cs | 21 ++++++++ .../MultiUrlPickerConfiguration.cs | 8 ++- src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 2 +- .../Trees/ContentTreeControllerBase.cs | 14 +++-- src/Umbraco.Web/Umbraco.Web.csproj | 1 + 12 files changed, 123 insertions(+), 46 deletions(-) create mode 100644 src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs index 1e9115cde9..d33138f398 100644 --- a/src/Umbraco.Core/Constants-DataTypes.cs +++ b/src/Umbraco.Core/Constants-DataTypes.cs @@ -6,6 +6,10 @@ namespace Umbraco.Core { public static class DataTypes { + //NOTE: unfortunately due to backwards compat we can't move/rename these, with the addition of the GUID + //constants, it would make more sense to have these suffixed with "ID" or in a Subclass called "INT", for + //now all we can do is make a subclass called Guids to put the GUID IDs. + public const int LabelString = -92; public const int LabelInt = -91; public const int LabelBigint = -93; @@ -25,16 +29,16 @@ namespace Umbraco.Core public const int Tags = 1041; + public static class ReservedPreValueKeys + { + public const string IgnoreUserStartNodes = "ignoreUserStartNodes"; + } + /// /// Defines the identifiers for Umbraco data types as constants for easy centralized access/management. /// - internal static class BuiltInDataTypes - { - - public static class ReservedPreValueKeys - { - public const string IgnoreUserStartNodes = "ignoreUserStartNodes"; - } + public static class Guids + { /// /// Guid for Content Picker as string diff --git a/src/Umbraco.Core/Models/DataTypeExtensions.cs b/src/Umbraco.Core/Models/DataTypeExtensions.cs index a2603ad3df..922b6a6a39 100644 --- a/src/Umbraco.Core/Models/DataTypeExtensions.cs +++ b/src/Umbraco.Core/Models/DataTypeExtensions.cs @@ -38,31 +38,31 @@ namespace Umbraco.Core.Models private static readonly ISet IdsOfBuildInDataTypes = new HashSet() { - Constants.DataTypes.ContentPickerGuid, - Constants.DataTypes.MemberPickerGuid, - Constants.DataTypes.MediaPickerGuid, - Constants.DataTypes.MultipleMediaPickerGuid, - Constants.DataTypes.RelatedLinksGuid, - Constants.DataTypes.MemberGuid, - Constants.DataTypes.ImageCropperGuid, - Constants.DataTypes.TagsGuid, - Constants.DataTypes.ListViewContentGuid, - Constants.DataTypes.ListViewMediaGuid, - Constants.DataTypes.ListViewMembersGuid, - Constants.DataTypes.DatePickerWithTimeGuid, - Constants.DataTypes.ApprovedColorGuid, - Constants.DataTypes.DropdownMultipleGuid, - Constants.DataTypes.RadioboxGuid, - Constants.DataTypes.DatePickerGuid, - Constants.DataTypes.DropdownGuid, - Constants.DataTypes.CheckboxListGuid, - Constants.DataTypes.CheckboxGuid, - Constants.DataTypes.NumericGuid, - Constants.DataTypes.RichtextEditorGuid, - Constants.DataTypes.TextstringGuid, - Constants.DataTypes.TextareaGuid, - Constants.DataTypes.UploadGuid, - Constants.DataTypes.LabelGuid, + Constants.DataTypes.Guids.ContentPickerGuid, + Constants.DataTypes.Guids.MemberPickerGuid, + Constants.DataTypes.Guids.MediaPickerGuid, + Constants.DataTypes.Guids.MultipleMediaPickerGuid, + Constants.DataTypes.Guids.RelatedLinksGuid, + Constants.DataTypes.Guids.MemberGuid, + Constants.DataTypes.Guids.ImageCropperGuid, + Constants.DataTypes.Guids.TagsGuid, + Constants.DataTypes.Guids.ListViewContentGuid, + Constants.DataTypes.Guids.ListViewMediaGuid, + Constants.DataTypes.Guids.ListViewMembersGuid, + Constants.DataTypes.Guids.DatePickerWithTimeGuid, + Constants.DataTypes.Guids.ApprovedColorGuid, + Constants.DataTypes.Guids.DropdownMultipleGuid, + Constants.DataTypes.Guids.RadioboxGuid, + Constants.DataTypes.Guids.DatePickerGuid, + Constants.DataTypes.Guids.DropdownGuid, + Constants.DataTypes.Guids.CheckboxListGuid, + Constants.DataTypes.Guids.CheckboxGuid, + Constants.DataTypes.Guids.NumericGuid, + Constants.DataTypes.Guids.RichtextEditorGuid, + Constants.DataTypes.Guids.TextstringGuid, + Constants.DataTypes.Guids.TextareaGuid, + Constants.DataTypes.Guids.UploadGuid, + Constants.DataTypes.Guids.LabelGuid, }; /// @@ -70,7 +70,7 @@ namespace Umbraco.Core.Models /// /// The data type definition. /// - internal static bool IsBuildInDataType(this IDataTypeDefinition dataType) + internal static bool IsBuildInDataType(this IDataType dataType) { return IsBuildInDataType(dataType.Key); } diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 164edca1e6..a9226fbae2 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -527,7 +527,7 @@ namespace Umbraco.Core.Services #endregion - IEnumerable GetAnchorValuesFromRTEs(int id); - IEnumerable GetAnchorValuesFromRTEContent(string rteContent); + IEnumerable GetAnchorValuesFromRTEs(int id, string culture = "*"); + IEnumerable GetAnchorValuesFromRTEContent(string rteContent, string culture = "*"); } } diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index 3dc530e250..bb56e110cd 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -4,6 +4,7 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Services { + /// /// Defines the DataType Service, which is an easy access to operations involving /// diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index c79e7aa869..90aaf6fc5d 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.Linq; +using System.Text.RegularExpressions; using Umbraco.Core.Events; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; @@ -31,6 +32,7 @@ namespace Umbraco.Core.Services.Implement private IQuery _queryNotTrashed; //TODO: The non-lazy object should be injected private readonly Lazy _propertyValidationService = new Lazy(() => new PropertyValidationService()); + private static readonly Regex AnchorRegex = new Regex("", RegexOptions.Compiled); #region Constructors @@ -3024,5 +3026,38 @@ namespace Umbraco.Core.Services.Implement } #endregion + + + #region RTE Anchor values + public IEnumerable GetAnchorValuesFromRTEs(int id, string culture = "*") + { + var result = new List(); + var content = GetById(id); + foreach (var contentProperty in content.Properties) + { + if (contentProperty.PropertyType.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.TinyMce)) + { + var value = contentProperty.GetValue(culture)?.ToString(); + if (!string.IsNullOrEmpty(value)) + { + result.AddRange(GetAnchorValuesFromRTEContent(value)); + } + } + } + return result; + } + + + public IEnumerable GetAnchorValuesFromRTEContent(string rteContent, string culture = "*") + { + var result = new List(); + var matches = AnchorRegex.Matches(rteContent); + foreach (Match match in matches) + { + result.Add(match.Value.Split('\"')[1]); + } + return result; + } + #endregion } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 7ac1e9d06e..86e4cb2f97 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -255,6 +255,7 @@ + @@ -264,6 +265,7 @@ + diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index d83ade5a13..4184182a1d 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -287,10 +287,11 @@ namespace Umbraco.Web.Editors } + //fixme - culture? [HttpGet] public UrlAndAnchors GetUrlAndAnchors([FromUri]int id) { - var url = Umbraco.Url(id); + var url = UmbracoContext.UrlProvider.GetUrl(id); var anchorValues = Services.ContentService.GetAnchorValuesFromRTEs(id); return new UrlAndAnchors(url, anchorValues); } @@ -300,6 +301,7 @@ namespace Umbraco.Web.Editors public string RteContent { get; set; } } + //fixme - culture? [HttpGet] [HttpPost] public IEnumerable GetAnchors(AnchorsModel model) @@ -567,7 +569,7 @@ namespace Umbraco.Web.Editors var objectType = ConvertToObjectType(type); if (objectType.HasValue) { - IEnumerable entities; + IEnumerable entities; long totalRecords; int[] startNodes = null; @@ -600,8 +602,7 @@ namespace Umbraco.Web.Editors } // else proceed as usual - //entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); - entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out var totalRecords, + entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, filter.IsNullOrWhiteSpace() ? null : SqlContext.Query().Where(x => x.Name.Contains(filter)), @@ -776,7 +777,7 @@ namespace Umbraco.Web.Editors var ids = Services.EntityService.Get(id).Path.Split(',').Select(int.Parse).Distinct().ToArray(); - var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); + var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(queryStrings?.GetValue("dataTypeId")); if (ignoreUserStartNodes == false) { int[] aids = null; diff --git a/src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs b/src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs new file mode 100644 index 0000000000..86642a8d65 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/UrlAndAnchors.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "urlAndAnchors", Namespace = "")] + public class UrlAndAnchors + { + public UrlAndAnchors(string url, IEnumerable anchorValues) + { + Url = url; + AnchorValues = anchorValues; + } + + [DataMember(Name = "url")] + public string Url { get; } + + [DataMember(Name = "anchorValues")] + public IEnumerable AnchorValues { get; } + } +} diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs index 515512eff8..8b6a4c15d2 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs @@ -2,12 +2,18 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - public class MultiUrlPickerConfiguration + + public class MultiUrlPickerConfiguration : IIgnoreUserStartNodesConfig { [ConfigurationField("minNumber", "Minimum number of items", "number")] public int MinNumber { get; set; } [ConfigurationField("maxNumber", "Maximum number of items", "number")] public int MaxNumber { get; set; } + + [ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "Selecting this option allows a user to choose nodes that they normally don't have access to.", "boolean")] + public bool IgnoreUserStartNodes { get; set; } + + } } diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index 19c0d0ed03..3a0f5eb361 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -55,7 +55,7 @@ namespace Umbraco.Web.Search string query, UmbracoEntityTypes entityType, int pageSize, - long pageIndex, out long totalFound, string searchFrom = null) + long pageIndex, out long totalFound, string searchFrom = null, bool ignoreUserStartNodes = false) { var sb = new StringBuilder(); diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 20d7389da1..2f90c7ac29 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -17,7 +17,7 @@ using Umbraco.Core.Models.Entities; using System.Web.Http.ModelBinding; using Umbraco.Web.Actions; using Umbraco.Web.Composing; - +using Umbraco.Core.Security; namespace Umbraco.Web.Trees { @@ -90,12 +90,18 @@ namespace Umbraco.Web.Trees internal TreeNode GetSingleTreeNodeWithAccessCheck(IEntitySlim e, string parentId, FormDataCollection queryStrings, int[] startNodeIds, string[] startNodePaths, bool ignoreUserStartNodes) { - var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out var hasPathAccess); - if (entityIsAncestorOfStartNodes == false) + var entityIsAncestorOfStartNodes = ContentPermissionsHelper.IsInBranchOfStartNode(e.Path, startNodeIds, startNodePaths, out var hasPathAccess); + if (ignoreUserStartNodes == false && entityIsAncestorOfStartNodes == false) return null; var treeNode = GetSingleTreeNode(e, parentId, queryStrings); - if (hasPathAccess == false) + if (treeNode == null) + { + //this means that the user has NO access to this node via permissions! They at least need to have browse permissions to see + //the node so we need to return null; + return null; + } + if (ignoreUserStartNodes == false && hasPathAccess == false) { treeNode.AdditionalData["noAccess"] = true; } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 8d1b106ba4..03a61fc33e 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -206,6 +206,7 @@ + From 4a33ca99bab197b48bb21160929fa3531d1cd874 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 28 Jun 2019 15:03:52 +1000 Subject: [PATCH 060/776] More manual merging --- src/Umbraco.Core/Constants-DataTypes.cs | 56 ++++++++++++++++++- .../Migrations/Install/DatabaseDataCreator.cs | 44 +++++++-------- src/Umbraco.Core/Models/DataTypeExtensions.cs | 7 ++- .../IIgnoreUserStartNodesConfig.cs | 10 ++++ .../Services/DateTypeServiceExtensions.cs | 21 +++++++ .../Mapping/ContentPropertyBasicMapper.cs | 9 ++- .../Models/Mapping/DataTypeMapDefinition.cs | 17 ++++-- .../ContentPickerConfiguration.cs | 5 +- .../PropertyEditors/GridConfiguration.cs | 5 +- .../MediaPickerConfiguration.cs | 5 +- .../MultiNodePickerConfiguration.cs | 5 +- .../PropertyEditors/RichTextConfiguration.cs | 5 +- .../Trees/ContentTreeControllerBase.cs | 2 +- 13 files changed, 153 insertions(+), 38 deletions(-) create mode 100644 src/Umbraco.Core/PropertyEditors/IIgnoreUserStartNodesConfig.cs create mode 100644 src/Umbraco.Core/Services/DateTypeServiceExtensions.cs diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs index d33138f398..021e508b6a 100644 --- a/src/Umbraco.Core/Constants-DataTypes.cs +++ b/src/Umbraco.Core/Constants-DataTypes.cs @@ -307,14 +307,64 @@ namespace Umbraco.Core /// /// Guid for Label as string /// - public const string Label = "f0bc4bfb-b499-40d6-ba86-058885a5178c"; + public const string LabelString = "f0bc4bfb-b499-40d6-ba86-058885a5178c"; /// - /// Guid for Label + /// Guid for Label string /// - public static readonly Guid LabelGuid = new Guid(Label); + public static readonly Guid LabelStringGuid = new Guid(LabelString); + /// + /// Guid for Label as int + /// + public const string LabelInt = "8e7f995c-bd81-4627-9932-c40e568ec788"; + /// + /// Guid for Label int + /// + public static readonly Guid LabelIntGuid = new Guid(LabelInt); + + /// + /// Guid for Label as big int + /// + public const string LabelBigInt = "930861bf-e262-4ead-a704-f99453565708"; + + /// + /// Guid for Label big int + /// + public static readonly Guid LabelBigIntGuid = new Guid(LabelBigInt); + + /// + /// Guid for Label as date time + /// + public const string LabelDateTime = "0e9794eb-f9b5-4f20-a788-93acd233a7e4"; + + /// + /// Guid for Label date time + /// + public static readonly Guid LabelDateTimeGuid = new Guid(LabelDateTime); + + /// + /// Guid for Label as time + /// + public const string LabelTime = "a97cec69-9b71-4c30-8b12-ec398860d7e8"; + + /// + /// Guid for Label time + /// + public static readonly Guid LabelTimeGuid = new Guid(LabelTime); + + /// + /// Guid for Label as decimal + /// + public const string LabelDecimal = "8f1ef1e1-9de4-40d3-a072-6673f631ca64"; + + /// + /// Guid for Label decimal + /// + public static readonly Guid LabelDecimalGuid = new Guid(LabelDecimal); + + } } } diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 1de983636b..d8aced7a8c 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -101,28 +101,28 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -1, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1", SortOrder = 0, UniqueId = new Guid("916724a5-173d-4619-b97e-b9de133dd6f5"), Text = "SYSTEM DATA: umbraco master root", NodeObjectType = Constants.ObjectTypes.SystemRoot, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -20, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,-20", SortOrder = 0, UniqueId = new Guid("0F582A79-1E41-4CF0-BFA0-76340651891A"), Text = "Recycle Bin", NodeObjectType = Constants.ObjectTypes.ContentRecycleBin, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -21, Trashed = false, ParentId = -1, UserId = -1, Level = 0, Path = "-1,-21", SortOrder = 0, UniqueId = new Guid("BF7C7CBC-952F-4518-97A2-69E9C7B33842"), Text = "Recycle Bin", NodeObjectType = Constants.ObjectTypes.MediaRecycleBin, CreateDate = DateTime.Now }); - InsertDataTypeNodeDto(Constants.DataTypes.LabelString, 35, "f0bc4bfb-b499-40d6-ba86-058885a5178c", "Label"); - InsertDataTypeNodeDto(Constants.DataTypes.LabelInt, 36, "8e7f995c-bd81-4627-9932-c40e568ec788", "Label (integer)"); - InsertDataTypeNodeDto(Constants.DataTypes.LabelBigint, 36, "930861bf-e262-4ead-a704-f99453565708", "Label (bigint)"); - InsertDataTypeNodeDto(Constants.DataTypes.LabelDateTime, 37, "0e9794eb-f9b5-4f20-a788-93acd233a7e4", "Label (datetime)"); - InsertDataTypeNodeDto(Constants.DataTypes.LabelTime, 38, "a97cec69-9b71-4c30-8b12-ec398860d7e8", "Label (time)"); - InsertDataTypeNodeDto(Constants.DataTypes.LabelDecimal, 39, "8f1ef1e1-9de4-40d3-a072-6673f631ca64", "Label (decimal)"); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -90, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-90", SortOrder = 34, UniqueId = new Guid("84c6b441-31df-4ffe-b67e-67d5bc3ae65a"), Text = "Upload", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -89, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-89", SortOrder = 33, UniqueId = new Guid("c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"), Text = "Textarea", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -88, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-88", SortOrder = 32, UniqueId = new Guid("0cc0eba1-9960-42c9-bf9b-60e150b429ae"), Text = "Textstring", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -87, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-87", SortOrder = 4, UniqueId = new Guid("ca90c950-0aff-4e72-b976-a30b1ac57dad"), Text = "Richtext editor", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = new Guid("2e6d3631-066e-44b8-aec4-96f09099b2b5"), Text = "Numeric", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -49, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-49", SortOrder = 2, UniqueId = new Guid("92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"), Text = "True/false", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -43, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-43", SortOrder = 2, UniqueId = new Guid("fbaf13a8-4036-41f2-93a3-974f678c312a"), Text = "Checkbox list", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DropDownSingle, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DropDownSingle}", SortOrder = 2, UniqueId = new Guid("0b6a45e7-44ba-430d-9da5-4e46060b9e03"), Text = "Dropdown", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -41, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-41", SortOrder = 2, UniqueId = new Guid("5046194e-4237-453c-a547-15db3a07c4e1"), Text = "Date Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -40, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-40", SortOrder = 2, UniqueId = new Guid("bb5f57c9-ce2b-4bb9-b697-4caca783a805"), Text = "Radiobox", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DropDownMultiple, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DropDownMultiple}", SortOrder = 2, UniqueId = new Guid("f38f0ac7-1d27-439c-9f3f-089cd8825a53"), Text = "Dropdown multiple", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = new Guid("0225af17-b302-49cb-9176-b9f35cab9c17"), Text = "Approved Color", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -36, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-36", SortOrder = 2, UniqueId = new Guid("e4d66c0f-b935-4200-81f0-025f7256b89a"), Text = "Date Picker with time", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultContentListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultContentListView}", SortOrder = 2, UniqueId = new Guid("C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMediaListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultMediaListView}", SortOrder = 2, UniqueId = new Guid("3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMembersListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultMembersListView}", SortOrder = 2, UniqueId = new Guid("AA2C52A0-CE87-4E65-A47C-7DF09358585D"), Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + InsertDataTypeNodeDto(Constants.DataTypes.LabelString, 35, Constants.DataTypes.Guids.LabelString, "Label"); + InsertDataTypeNodeDto(Constants.DataTypes.LabelInt, 36, Constants.DataTypes.Guids.LabelInt, "Label (integer)"); + InsertDataTypeNodeDto(Constants.DataTypes.LabelBigint, 36, Constants.DataTypes.Guids.LabelBigInt, "Label (bigint)"); + InsertDataTypeNodeDto(Constants.DataTypes.LabelDateTime, 37, Constants.DataTypes.Guids.LabelDateTime, "Label (datetime)"); + InsertDataTypeNodeDto(Constants.DataTypes.LabelTime, 38, Constants.DataTypes.Guids.LabelTime, "Label (time)"); + InsertDataTypeNodeDto(Constants.DataTypes.LabelDecimal, 39, Constants.DataTypes.Guids.LabelDecimal, "Label (decimal)"); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -90, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-90", SortOrder = 34, UniqueId = Constants.DataTypes.Guids.UploadGuid, Text = "Upload", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -89, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-89", SortOrder = 33, UniqueId = Constants.DataTypes.Guids.TextareaGuid, Text = "Textarea", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -88, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-88", SortOrder = 32, UniqueId = Constants.DataTypes.Guids.TextstringGuid, Text = "Textstring", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -87, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-87", SortOrder = 4, UniqueId = Constants.DataTypes.Guids.RichtextEditorGuid, Text = "Richtext editor", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.NumericGuid, Text = "Numeric", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -49, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-49", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.CheckboxGuid, Text = "True/false", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -43, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-43", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.CheckboxListGuid, Text = "Checkbox list", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DropDownSingle, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DropDownSingle}", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.DropdownGuid, Text = "Dropdown", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -41, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-41", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.DatePickerGuid, Text = "Date Picker", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -40, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-40", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.RadioboxGuid, Text = "Radiobox", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DropDownMultiple, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DropDownMultiple}", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.DropdownMultipleGuid, Text = "Dropdown multiple", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -37, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-37", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.ApprovedColorGuid, Text = "Approved Color", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -36, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-36", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.DatePickerWithTimeGuid, Text = "Date Picker with time", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultContentListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultContentListView}", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.ListViewContentGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Content", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMediaListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultMediaListView}", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.ListViewMediaGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Media", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.DefaultMembersListView, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.DefaultMembersListView}", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.ListViewMembersGuid, Text = Constants.Conventions.DataTypes.ListViewPrefix + "Members", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1031, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1031", SortOrder = 2, UniqueId = new Guid("f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d"), Text = Constants.Conventions.MediaTypes.Folder, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1032, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1032", SortOrder = 2, UniqueId = new Guid("cc07b313-0843-4aa8-bbda-871c8da728c8"), Text = Constants.Conventions.MediaTypes.Image, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = 1033, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,1033", SortOrder = 2, UniqueId = new Guid("4c52d8ab-54e6-40cd-999c-7a5f24903e4d"), Text = Constants.Conventions.MediaTypes.File, NodeObjectType = Constants.ObjectTypes.MediaType, CreateDate = DateTime.Now }); diff --git a/src/Umbraco.Core/Models/DataTypeExtensions.cs b/src/Umbraco.Core/Models/DataTypeExtensions.cs index 922b6a6a39..f460edbde7 100644 --- a/src/Umbraco.Core/Models/DataTypeExtensions.cs +++ b/src/Umbraco.Core/Models/DataTypeExtensions.cs @@ -62,7 +62,12 @@ namespace Umbraco.Core.Models Constants.DataTypes.Guids.TextstringGuid, Constants.DataTypes.Guids.TextareaGuid, Constants.DataTypes.Guids.UploadGuid, - Constants.DataTypes.Guids.LabelGuid, + Constants.DataTypes.Guids.LabelStringGuid, + Constants.DataTypes.Guids.LabelDecimalGuid, + Constants.DataTypes.Guids.LabelDateTimeGuid, + Constants.DataTypes.Guids.LabelBigIntGuid, + Constants.DataTypes.Guids.LabelTimeGuid, + Constants.DataTypes.Guids.LabelDateTimeGuid, }; /// diff --git a/src/Umbraco.Core/PropertyEditors/IIgnoreUserStartNodesConfig.cs b/src/Umbraco.Core/PropertyEditors/IIgnoreUserStartNodesConfig.cs new file mode 100644 index 0000000000..bef3f42f46 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/IIgnoreUserStartNodesConfig.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Marker interface for any editor configuration that supports Ignoring user start nodes + /// + internal interface IIgnoreUserStartNodesConfig + { + bool IgnoreUserStartNodes { get; set; } + } +} diff --git a/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs new file mode 100644 index 0000000000..3b72a6f258 --- /dev/null +++ b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs @@ -0,0 +1,21 @@ +using System; +using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Services +{ + internal static class DateTypeServiceExtensions + { + public static bool IsDataTypeIgnoringUserStartNodes(this IDataTypeService dataTypeService, Guid key) + { + if (DataTypeExtensions.IsBuildInDataType(key)) return false; //built in ones can never be ignoring start nodes + + var dataType = dataTypeService.GetDataType(key); + + if (dataType != null && dataType.Configuration is IIgnoreUserStartNodesConfig ignoreStartNodesConfig) + return ignoreStartNodesConfig.IgnoreUserStartNodes; + + return false; + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs index e7c53f5728..8340d24032 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs @@ -16,15 +16,17 @@ namespace Umbraco.Web.Models.Mapping internal class ContentPropertyBasicMapper where TDestination : ContentPropertyBasic, new() { + private readonly IEntityService _entityService; private readonly ILogger _logger; private readonly PropertyEditorCollection _propertyEditors; protected IDataTypeService DataTypeService { get; } - public ContentPropertyBasicMapper(IDataTypeService dataTypeService, ILogger logger, PropertyEditorCollection propertyEditors) + public ContentPropertyBasicMapper(IDataTypeService dataTypeService, IEntityService entityService, ILogger logger, PropertyEditorCollection propertyEditors) { _logger = logger; _propertyEditors = propertyEditors; DataTypeService = dataTypeService; + _entityService = entityService; } /// @@ -49,6 +51,11 @@ namespace Umbraco.Web.Models.Mapping dest.PropertyEditor = editor; dest.Editor = editor.Alias; + var dataTypeKey = _entityService.GetKey(property.PropertyType.DataTypeId, UmbracoObjectTypes.DataType); + if (!dataTypeKey.Success) + throw new InvalidOperationException("Can't get the unique key from the id: " + property.PropertyType.DataTypeId); + dest.DataTypeId = dataTypeKey.Result; + // if there's a set of property aliases specified, we will check if the current property's value should be mapped. // if it isn't one of the ones specified in 'includeProperties', we will just return the result without mapping the Value. var includedProperties = context.GetIncludedProperties(); diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/DataTypeMapDefinition.cs index 4c4a03939d..6b5797e05b 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeMapDefinition.cs @@ -146,19 +146,26 @@ namespace Umbraco.Web.Models.Mapping var fields = context.MapEnumerable(configurationEditor.Fields); var configurationDictionary = configurationEditor.ToConfigurationEditor(dataType.Configuration); - MapConfigurationFields(fields, configurationDictionary); + MapConfigurationFields(dataType, fields, configurationDictionary); return fields; } - - private void MapConfigurationFields(List fields, IDictionary configuration) + + private void MapConfigurationFields(IDataType dataType, List fields, IDictionary configuration) { if (fields == null) throw new ArgumentNullException(nameof(fields)); if (configuration == null) throw new ArgumentNullException(nameof(configuration)); // now we need to wire up the pre-values values with the actual fields defined - foreach (var field in fields) + foreach (var field in fields.ToList()) { + //filter out the not-supported pre-values for built-in data types + if (dataType != null && dataType.IsBuildInDataType() && field.Key.InvariantEquals(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes)) + { + fields.Remove(field); + continue; + } + if (configuration.TryGetValue(field.Key, out var value)) { field.Value = value; @@ -194,7 +201,7 @@ namespace Umbraco.Web.Models.Mapping var defaultConfiguration = configurationEditor.DefaultConfiguration; if (defaultConfiguration != null) - MapConfigurationFields(fields, defaultConfiguration); + MapConfigurationFields(null, fields, defaultConfiguration); return fields; } diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs index 7879e2b42b..d7f7ca9551 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs @@ -3,12 +3,15 @@ using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors { - public class ContentPickerConfiguration + public class ContentPickerConfiguration : IIgnoreUserStartNodesConfig { [ConfigurationField("showOpenButton", "Show open button (this feature is in beta!)", "boolean", Description = "Opens the node in a dialog")] public bool ShowOpenButton { get; set; } [ConfigurationField("startNodeId", "Start node", "treepicker")] // + config in configuration editor ctor public Udi StartNodeId { get; set; } + + [ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "Selecting this option allows a user to choose nodes that they normally don't have access to.", "boolean")] + public bool IgnoreUserStartNodes { get; set; } } } diff --git a/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs b/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs index e2b46b360d..80f09d3f8f 100644 --- a/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs @@ -6,7 +6,7 @@ namespace Umbraco.Web.PropertyEditors /// /// Represents the configuration for the grid value editor. /// - public class GridConfiguration + public class GridConfiguration : IIgnoreUserStartNodesConfig { // TODO: Make these strongly typed, for now this works though [ConfigurationField("items", "Grid", "views/propertyeditors/grid/grid.prevalues.html", Description = "Grid configuration")] @@ -15,5 +15,8 @@ namespace Umbraco.Web.PropertyEditors // TODO: Make these strongly typed, for now this works though [ConfigurationField("rte", "Rich text editor", "views/propertyeditors/rte/rte.prevalues.html", Description = "Rich text editor configuration")] public JObject Rte { get; set; } + + [ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "Selecting this option allows a user to choose nodes that they normally don't have access to.", "boolean")] + public bool IgnoreUserStartNodes { get; set; } } } diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs index 4844e2f822..3a9a9e25ac 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs @@ -6,7 +6,7 @@ namespace Umbraco.Web.PropertyEditors /// /// Represents the configuration for the media picker value editor. /// - public class MediaPickerConfiguration + public class MediaPickerConfiguration : IIgnoreUserStartNodesConfig { [ConfigurationField("multiPicker", "Pick multiple items", "boolean")] public bool Multiple { get; set; } @@ -19,5 +19,8 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("startNodeId", "Start node", "mediapicker")] public Udi StartNodeId { get; set; } + + [ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "Selecting this option allows a user to choose nodes that they normally don't have access to.", "boolean")] + public bool IgnoreUserStartNodes { get; set; } } } diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs index 279872e2d2..310211ab2b 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs @@ -6,7 +6,7 @@ namespace Umbraco.Web.PropertyEditors /// /// Represents the configuration for the multinode picker value editor. /// - public class MultiNodePickerConfiguration + public class MultiNodePickerConfiguration : IIgnoreUserStartNodesConfig { [ConfigurationField("startNode", "Node type", "treesource")] public MultiNodePickerConfigurationTreeSource TreeSource { get; set; } @@ -22,5 +22,8 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("showOpenButton", "Show open button (this feature is in preview!)", "boolean", Description = "Opens the node in a dialog")] public bool ShowOpen { get; set; } + + [ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "Selecting this option allows a user to choose nodes that they normally don't have access to.", "boolean")] + public bool IgnoreUserStartNodes { get; set; } } } diff --git a/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs b/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs index 13bf269bcd..30d3ee2113 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs @@ -6,7 +6,7 @@ namespace Umbraco.Web.PropertyEditors /// /// Represents the configuration for the rich text value editor. /// - public class RichTextConfiguration + public class RichTextConfiguration : IIgnoreUserStartNodesConfig { // TODO: Make these strongly typed, for now this works though [ConfigurationField("editor", "Editor", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] @@ -14,5 +14,8 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("hideLabel", "Hide Label", "boolean")] public bool HideLabel { get; set; } + + [ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "Selecting this option allows a user to choose nodes that they normally don't have access to.", "boolean")] + public bool IgnoreUserStartNodes { get; set; } } } diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 2f90c7ac29..091c3a8f8d 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -244,7 +244,7 @@ namespace Umbraco.Web.Trees return result; } - internal virtual IEnumerable GetChildrenFromEntityService(int entityId) + private IEnumerable GetChildrenFromEntityService(int entityId) => Services.EntityService.GetChildren(entityId, UmbracoObjectType).ToList(); /// From 958c3b147b12d19309f7d7c43d2f55820d8c3489 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 28 Jun 2019 15:42:23 +1000 Subject: [PATCH 061/776] more manual merging --- .../linkpicker/linkpicker.controller.js | 25 +++++++++---------- .../linkpicker/linkpicker.html | 4 ++- .../mediapicker/mediapicker.controller.js | 14 +++++++++-- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js index 6568cfb567..0286fb2384 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js @@ -1,6 +1,6 @@ //used for the media picker dialog angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", - function ($scope, eventsService, entityResource, contentResource, mediaResource, mediaHelper, udiParser, userService, localizationService, tinyMceService, editorService, contentEditingHelper) { + function ($scope, eventsService, entityResource, mediaResource, mediaHelper, udiParser, userService, localizationService, tinyMceService, editorService, contentEditingHelper) { var vm = this; var dialogOptions = $scope.model; @@ -20,13 +20,14 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", $scope.model.title = value; }); } - + $scope.customTreeParams = dialogOptions.dataTypeId ? "dataTypeId=" + dialogOptions.dataTypeId : ""; $scope.dialogTreeApi = {}; $scope.model.target = {}; $scope.searchInfo = { searchFromId: null, searchFromName: null, showSearch: false, + dataTypeId: dialogOptions.dataTypeId, results: [], selectedSearchResults: [] }; @@ -86,10 +87,11 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", oneTimeTreeSync.sync(); }); - // get the content properties to build the anchor name list - contentResource.getById(id).then(function (resp) { - handleContentTarget(resp); + entityResource.getUrlAndAnchors(id).then(function (resp) { + $scope.anchorValues = resp.anchorValues; + $scope.model.target.url = resp.url; }); + } } else if ($scope.model.target.url.length) { // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs @@ -136,10 +138,11 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", if (args.node.id < 0) { $scope.model.target.url = "/"; - } else { - contentResource.getById(args.node.id).then(function (resp) { - handleContentTarget(resp); - + } + else { + entityResource.getUrlAndAnchors(args.node.id).then(function (resp) { + $scope.anchorValues = resp.anchorValues; + $scope.model.target.url = resp.url; }); } @@ -148,10 +151,6 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", } } - function handleContentTarget(content) { - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(contentEditingHelper.getAllProps(content.variants[0]))); - $scope.model.target.url = content.urls.filter(item => item.culture === $scope.currentNode.metaData.culture)[0].text; - } function nodeExpandedHandler(args) { // open mini list view for list views diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html index 0bb91d8da6..e1880d5ac5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html @@ -65,6 +65,7 @@ search-callback="onSearchResults" search-from-id="{{searchInfo.searchFromId}}" search-from-name="{{searchInfo.searchFromName}}" + datatype-id="{{searchInfo.dataTypeId}}" show-search="{{searchInfo.showSearch}}" section="{{section}}"> @@ -86,7 +87,8 @@ on-init="onTreeInit()" enablelistviewexpand="true" isdialog="true" - enablecheckboxes="true"> + enablecheckboxes="true" + customtreeparams="{{customTreeParams}}"> 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 d7cac59348..c32730a2ac 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 @@ -7,6 +7,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false; var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false; var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false; + $scope.allowEditMedia = false; $scope.allowAddMedia = false; @@ -107,10 +108,18 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl function init() { userService.getCurrentUser().then(function (userData) { + if (!$scope.model.config.startNodeId) { - $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; + if ($scope.model.config.ignoreUserStartNodes === "1") { + $scope.model.config.startNodeId = -1; + $scope.model.config.startNodeIsVirtual = true; + } + else { + $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; + } } + // only allow users to add and edit media if they have access to the media section var hasAccessToMedia = userData.allowedSections.indexOf("media") !== -1; $scope.allowEditMedia = hasAccessToMedia; @@ -168,6 +177,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl var mediaPicker = { startNodeId: $scope.model.config.startNodeId, startNodeIsVirtual: $scope.model.config.startNodeIsVirtual, + dataTypeId: $scope.model.dataTypeId, multiPicker: multiPicker, onlyImages: onlyImages, disableFolderSelect: disableFolderSelect, From e5956e4955474694c12ed6da7a4d5600acd0af8c Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 28 Jun 2019 09:19:11 +0200 Subject: [PATCH 062/776] V8: Don't cast IContent to Content in DocumentRepository (#4611) --- .../Models/Entities/EntityBase.cs | 32 +--- .../Models/Entities/EntityExtensions.cs | 46 +++++ src/Umbraco.Core/Models/Member.cs | 13 -- .../Implement/AuditEntryRepository.cs | 2 +- .../Implement/ConsentRepository.cs | 4 +- .../Implement/ContentTypeRepository.cs | 5 +- .../Implement/DataTypeRepository.cs | 5 +- .../Implement/DictionaryRepository.cs | 2 +- .../Implement/DocumentRepository.cs | 163 +++++++++--------- .../Implement/DomainRepository.cs | 5 +- .../Implement/ExternalLoginRepository.cs | 4 +- .../Implement/LanguageRepository.cs | 4 +- .../Repositories/Implement/MacroRepository.cs | 4 +- .../Repositories/Implement/MediaRepository.cs | 3 +- .../Implement/MediaTypeRepository.cs | 5 +- .../Implement/MemberGroupRepository.cs | 3 +- .../Implement/MemberRepository.cs | 8 +- .../Implement/MemberTypeRepository.cs | 5 +- .../Implement/PublicAccessRepository.cs | 1 + .../Implement/RelationRepository.cs | 4 +- .../Implement/RelationTypeRepository.cs | 4 +- .../Implement/ServerRegistrationRepository.cs | 5 +- .../Repositories/Implement/TagRepository.cs | 4 +- .../Implement/UserGroupRepository.cs | 4 +- .../Repositories/Implement/UserRepository.cs | 4 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + 26 files changed, 177 insertions(+), 163 deletions(-) create mode 100644 src/Umbraco.Core/Models/Entities/EntityExtensions.cs diff --git a/src/Umbraco.Core/Models/Entities/EntityBase.cs b/src/Umbraco.Core/Models/Entities/EntityBase.cs index 43837fa776..cdb3ecebc5 100644 --- a/src/Umbraco.Core/Models/Entities/EntityBase.cs +++ b/src/Umbraco.Core/Models/Entities/EntityBase.cs @@ -81,37 +81,7 @@ namespace Umbraco.Core.Models.Entities _key = Guid.Empty; _hasIdentity = false; } - - /// - /// Updates the entity when it is being saved for the first time. - /// - internal virtual void AddingEntity() - { - var now = DateTime.Now; - - // set the create and update dates, if not already set - if (IsPropertyDirty("CreateDate") == false || _createDate == default) - CreateDate = now; - if (IsPropertyDirty("UpdateDate") == false || _updateDate == default) - UpdateDate = now; - } - - /// - /// Updates the entity when it is being saved. - /// - internal virtual void UpdatingEntity() - { - var now = DateTime.Now; - - // just in case - if (_createDate == default) - CreateDate = now; - - // set the update date if not already set - if (IsPropertyDirty("UpdateDate") == false || _updateDate == default) - UpdateDate = now; - } - + public virtual bool Equals(EntityBase other) { return other != null && (ReferenceEquals(this, other) || SameIdentityAs(other)); diff --git a/src/Umbraco.Core/Models/Entities/EntityExtensions.cs b/src/Umbraco.Core/Models/Entities/EntityExtensions.cs new file mode 100644 index 0000000000..2ee6a2d5ed --- /dev/null +++ b/src/Umbraco.Core/Models/Entities/EntityExtensions.cs @@ -0,0 +1,46 @@ +using System; + +namespace Umbraco.Core.Models.Entities +{ + internal static class EntityExtensions + { + /// + /// Updates the entity when it is being saved. + /// + internal static void UpdatingEntity(this IEntity entity) + { + var now = DateTime.Now; + + // just in case + if (entity.CreateDate == default) + { + entity.CreateDate = now; + } + + // set the update date if not already set + if (entity.UpdateDate == default || (entity is ICanBeDirty canBeDirty && canBeDirty.IsPropertyDirty("UpdateDate") == false)) + { + entity.UpdateDate = now; + } + } + + /// + /// Updates the entity when it is being saved for the first time. + /// + internal static void AddingEntity(this IEntity entity) + { + var now = DateTime.Now; + var canBeDirty = entity as ICanBeDirty; + + // set the create and update dates, if not already set + if (entity.CreateDate == default || canBeDirty?.IsPropertyDirty("CreateDate") == false) + { + entity.CreateDate = now; + } + if (entity.UpdateDate == default || canBeDirty?.IsPropertyDirty("UpdateDate") == false) + { + entity.UpdateDate = now; + } + } + } +} diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 0e91065d56..b473a154f1 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -478,19 +478,6 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _providerUserKey, nameof(ProviderUserKey)); } - - /// - /// Method to call when Entity is being saved - /// - /// Created date is set and a Unique key is assigned - internal override void AddingEntity() - { - base.AddingEntity(); - - if (ProviderUserKey == null) - ProviderUserKey = Key; - } - /* Internal experiment - only used for mapping queries. * Adding these to have first level properties instead of the Properties collection. */ diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs index 1486935e2a..c3d34cc3e9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs @@ -100,7 +100,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// protected override void PersistNewItem(IAuditEntry entity) { - ((EntityBase) entity).AddingEntity(); + entity.AddingEntity(); var dto = AuditEntryFactory.BuildDto(entity); Database.Insert(dto); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ConsentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ConsentRepository.cs index 8df9bf686d..57d5dfa864 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ConsentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ConsentRepository.cs @@ -69,7 +69,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// protected override void PersistNewItem(IConsent entity) { - ((EntityBase) entity).AddingEntity(); + entity.AddingEntity(); var dto = ConsentFactory.BuildDto(entity); Database.Insert(dto); @@ -80,7 +80,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// protected override void PersistUpdatedItem(IConsent entity) { - ((EntityBase) entity).UpdatingEntity(); + entity.UpdatingEntity(); var dto = ConsentFactory.BuildDto(entity); Database.Update(dto); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs index 98ddcdcb17..9d77eb0990 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs @@ -5,6 +5,7 @@ using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; @@ -230,7 +231,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement throw ex; } - ((ContentType)entity).AddingEntity(); + entity.AddingEntity(); PersistNewBaseContentType(entity); PersistTemplates(entity, false); @@ -270,7 +271,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement ValidateAlias(entity); //Updates Modified date - ((ContentType)entity).UpdatingEntity(); + entity.UpdatingEntity(); //Look up parent to get and set the correct Path if ParentId has changed if (entity.IsPropertyDirty("ParentId")) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs index 02ba4d1c13..dac8fda5ec 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Events; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; @@ -106,7 +107,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IDataType entity) { - ((DataType)entity).AddingEntity(); + entity.AddingEntity(); //ensure a datatype has a unique name before creating it entity.Name = EnsureUniqueNodeName(entity.Name); @@ -174,7 +175,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } //Updates Modified date - ((DataType)entity).UpdatingEntity(); + entity.UpdatingEntity(); //Look up parent to get and set the correct Path if ParentId has changed if (entity.IsPropertyDirty("ParentId")) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs index be1e28fcc1..0b58663952 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs @@ -148,7 +148,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IDictionaryItem entity) { - ((EntityBase)entity).UpdatingEntity(); + entity.UpdatingEntity(); foreach (var translation in entity.Translations) translation.Value = translation.Value.ToValidXmlString(); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 7e867c924c..30a2927cc8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; @@ -263,21 +264,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IContent entity) { - // TODO: https://github.com/umbraco/Umbraco-CMS/issues/4234 - sort out IContent vs Content - // however, it's not just so we have access to AddingEntity - // there are tons of things at the end of the methods, that can only work with a true Content - // and basically, the repository requires a Content, not an IContent - var content = (Content)entity; + entity.AddingEntity(); - content.AddingEntity(); - var publishing = content.PublishedState == PublishedState.Publishing; + var publishing = entity.PublishedState == PublishedState.Publishing; // ensure that the default template is assigned if (entity.TemplateId.HasValue == false) entity.TemplateId = entity.ContentType.DefaultTemplate?.Id; // sanitize names - SanitizeNames(content, publishing); + SanitizeNames(entity, publishing); // ensure that strings don't contain characters that are invalid in xml // TODO: do we really want to keep doing this here? @@ -327,11 +323,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement contentVersionDto.NodeId = nodeDto.NodeId; contentVersionDto.Current = !publishing; Database.Insert(contentVersionDto); - content.VersionId = contentVersionDto.Id; + entity.VersionId = contentVersionDto.Id; // persist the document version dto var documentVersionDto = dto.DocumentVersionDto; - documentVersionDto.Id = content.VersionId; + documentVersionDto.Id = entity.VersionId; if (publishing) documentVersionDto.Published = true; Database.Insert(documentVersionDto); @@ -339,62 +335,62 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // and again in case we're publishing immediately if (publishing) { - content.PublishedVersionId = content.VersionId; + entity.PublishedVersionId = entity.VersionId; contentVersionDto.Id = 0; contentVersionDto.Current = true; - contentVersionDto.Text = content.Name; + contentVersionDto.Text = entity.Name; Database.Insert(contentVersionDto); - content.VersionId = contentVersionDto.Id; + entity.VersionId = contentVersionDto.Id; - documentVersionDto.Id = content.VersionId; + documentVersionDto.Id = entity.VersionId; documentVersionDto.Published = false; Database.Insert(documentVersionDto); } // persist the property data - var propertyDataDtos = PropertyFactory.BuildDtos(content.ContentType.Variations, content.VersionId, content.PublishedVersionId, entity.Properties, LanguageRepository, out var edited, out var editedCultures); + var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, entity.PublishedVersionId, entity.Properties, LanguageRepository, out var edited, out var editedCultures); foreach (var propertyDataDto in propertyDataDtos) Database.Insert(propertyDataDto); // if !publishing, we may have a new name != current publish name, // also impacts 'edited' - if (!publishing && content.PublishName != content.Name) + if (!publishing && entity.PublishName != entity.Name) edited = true; // persist the document dto // at that point, when publishing, the entity still has its old Published value // so we need to explicitly update the dto to persist the correct value - if (content.PublishedState == PublishedState.Publishing) + if (entity.PublishedState == PublishedState.Publishing) dto.Published = true; dto.NodeId = nodeDto.NodeId; - content.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited + entity.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited Database.Insert(dto); //insert the schedule - PersistContentSchedule(content, false); + PersistContentSchedule(entity, false); // persist the variations - if (content.ContentType.VariesByCulture()) + if (entity.ContentType.VariesByCulture()) { // bump dates to align cultures to version if (publishing) - content.AdjustDates(contentVersionDto.VersionDate); + entity.AdjustDates(contentVersionDto.VersionDate); // names also impact 'edited' // ReSharper disable once UseDeconstruction - foreach (var cultureInfo in content.CultureInfos) - if (cultureInfo.Name != content.GetPublishName(cultureInfo.Culture)) + foreach (var cultureInfo in entity.CultureInfos) + if (cultureInfo.Name != entity.GetPublishName(cultureInfo.Culture)) (editedCultures ?? (editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase))).Add(cultureInfo.Culture); // insert content variations - Database.BulkInsertRecords(GetContentVariationDtos(content, publishing)); + Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing)); // insert document variations - Database.BulkInsertRecords(GetDocumentVariationDtos(content, publishing, editedCultures)); + Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures)); } // refresh content - content.SetCultureEdited(editedCultures); + entity.SetCultureEdited(editedCultures); // trigger here, before we reset Published etc OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity)); @@ -402,23 +398,23 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // flip the entity's published property // this also flips its published state // note: what depends on variations (eg PublishNames) is managed directly by the content - if (content.PublishedState == PublishedState.Publishing) + if (entity.PublishedState == PublishedState.Publishing) { - content.Published = true; - content.PublishTemplateId = content.TemplateId; - content.PublisherId = content.WriterId; - content.PublishName = content.Name; - content.PublishDate = content.UpdateDate; + entity.Published = true; + entity.PublishTemplateId = entity.TemplateId; + entity.PublisherId = entity.WriterId; + entity.PublishName = entity.Name; + entity.PublishDate = entity.UpdateDate; SetEntityTags(entity, _tagRepository); } - else if (content.PublishedState == PublishedState.Unpublishing) + else if (entity.PublishedState == PublishedState.Unpublishing) { - content.Published = false; - content.PublishTemplateId = null; - content.PublisherId = null; - content.PublishName = null; - content.PublishDate = null; + entity.Published = false; + entity.PublishTemplateId = null; + entity.PublisherId = null; + entity.PublishName = null; + entity.PublishDate = null; ClearEntityTags(entity, _tagRepository); } @@ -440,34 +436,33 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IContent entity) { - // however, it's not just so we have access to AddingEntity - // there are tons of things at the end of the methods, that can only work with a true Content - // and basically, the repository requires a Content, not an IContent - var content = (Content)entity; + var entityBase = entity as EntityBase; + var isEntityDirty = entityBase != null && entityBase.IsDirty(); // check if we need to make any database changes at all - if ((content.PublishedState == PublishedState.Published || content.PublishedState == PublishedState.Unpublished) - && !content.IsEntityDirty() && !content.IsAnyUserPropertyDirty()) + if ((entity.PublishedState == PublishedState.Published || entity.PublishedState == PublishedState.Unpublished) + && !isEntityDirty && !entity.IsAnyUserPropertyDirty()) return; // no change to save, do nothing, don't even update dates // whatever we do, we must check that we are saving the current version - var version = Database.Fetch(SqlContext.Sql().Select().From().Where(x => x.Id == content.VersionId)).FirstOrDefault(); + var version = Database.Fetch(SqlContext.Sql().Select().From().Where(x => x.Id == entity.VersionId)).FirstOrDefault(); if (version == null || !version.Current) throw new InvalidOperationException("Cannot save a non-current version."); // update - content.UpdatingEntity(); - var publishing = content.PublishedState == PublishedState.Publishing; + entity.UpdatingEntity(); + + var publishing = entity.PublishedState == PublishedState.Publishing; // check if we need to create a new version - if (publishing && content.PublishedVersionId > 0) + if (publishing && entity.PublishedVersionId > 0) { // published version is not published anymore - Database.Execute(Sql().Update(u => u.Set(x => x.Published, false)).Where(x => x.Id == content.PublishedVersionId)); + Database.Execute(Sql().Update(u => u.Set(x => x.Published, false)).Where(x => x.Id == entity.PublishedVersionId)); } // sanitize names - SanitizeNames(content, publishing); + SanitizeNames(entity, publishing); // ensure that strings don't contain characters that are invalid in xml // TODO: do we really want to keep doing this here? @@ -507,13 +502,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // and, if publishing, insert new content & document version dtos if (publishing) { - content.PublishedVersionId = content.VersionId; + entity.PublishedVersionId = entity.VersionId; contentVersionDto.Id = 0; // want a new id contentVersionDto.Current = true; // current version - contentVersionDto.Text = content.Name; + contentVersionDto.Text = entity.Name; Database.Insert(contentVersionDto); - content.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id + entity.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id documentVersionDto.Published = false; // non-published version Database.Insert(documentVersionDto); @@ -521,31 +516,31 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // replace the property data (rather than updating) // only need to delete for the version that existed, the new version (if any) has no property data yet - var versionToDelete = publishing ? content.PublishedVersionId : content.VersionId; + var versionToDelete = publishing ? entity.PublishedVersionId : entity.VersionId; var deletePropertyDataSql = Sql().Delete().Where(x => x.VersionId == versionToDelete); Database.Execute(deletePropertyDataSql); // insert property data - var propertyDataDtos = PropertyFactory.BuildDtos(content.ContentType.Variations, content.VersionId, publishing ? content.PublishedVersionId : 0, + var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, publishing ? entity.PublishedVersionId : 0, entity.Properties, LanguageRepository, out var edited, out var editedCultures); foreach (var propertyDataDto in propertyDataDtos) Database.Insert(propertyDataDto); // if !publishing, we may have a new name != current publish name, // also impacts 'edited' - if (!publishing && content.PublishName != content.Name) + if (!publishing && entity.PublishName != entity.Name) edited = true; - if (content.ContentType.VariesByCulture()) + if (entity.ContentType.VariesByCulture()) { // bump dates to align cultures to version if (publishing) - content.AdjustDates(contentVersionDto.VersionDate); + entity.AdjustDates(contentVersionDto.VersionDate); // names also impact 'edited' // ReSharper disable once UseDeconstruction - foreach (var cultureInfo in content.CultureInfos) - if (cultureInfo.Name != content.GetPublishName(cultureInfo.Culture)) + foreach (var cultureInfo in entity.CultureInfos) + if (cultureInfo.Name != entity.GetPublishName(cultureInfo.Culture)) { edited = true; (editedCultures ?? (editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase))).Add(cultureInfo.Culture); @@ -563,7 +558,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Execute(deleteContentVariations); // replace the document version variations (rather than updating) - var deleteDocumentVariations = Sql().Delete().Where(x => x.NodeId == content.Id); + var deleteDocumentVariations = Sql().Delete().Where(x => x.NodeId == entity.Id); Database.Execute(deleteDocumentVariations); // TODO: NPoco InsertBulk issue? @@ -573,32 +568,32 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // (same in PersistNewItem above) // insert content variations - Database.BulkInsertRecords(GetContentVariationDtos(content, publishing)); + Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing)); // insert document variations - Database.BulkInsertRecords(GetDocumentVariationDtos(content, publishing, editedCultures)); + Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures)); } // refresh content - content.SetCultureEdited(editedCultures); + entity.SetCultureEdited(editedCultures); // update the document dto // at that point, when un/publishing, the entity still has its old Published value // so we need to explicitly update the dto to persist the correct value - if (content.PublishedState == PublishedState.Publishing) + if (entity.PublishedState == PublishedState.Publishing) dto.Published = true; - else if (content.PublishedState == PublishedState.Unpublishing) + else if (entity.PublishedState == PublishedState.Unpublishing) dto.Published = false; - content.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited + entity.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited Database.Update(dto); //update the schedule - if (content.IsPropertyDirty("ContentSchedule")) - PersistContentSchedule(content, true); + if (entity.IsPropertyDirty("ContentSchedule")) + PersistContentSchedule(entity, true); // if entity is publishing, update tags, else leave tags there // means that implicitly unpublished, or trashed, entities *still* have tags in db - if (content.PublishedState == PublishedState.Publishing) + if (entity.PublishedState == PublishedState.Publishing) SetEntityTags(entity, _tagRepository); // trigger here, before we reset Published etc @@ -606,23 +601,23 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // flip the entity's published property // this also flips its published state - if (content.PublishedState == PublishedState.Publishing) + if (entity.PublishedState == PublishedState.Publishing) { - content.Published = true; - content.PublishTemplateId = content.TemplateId; - content.PublisherId = content.WriterId; - content.PublishName = content.Name; - content.PublishDate = content.UpdateDate; + entity.Published = true; + entity.PublishTemplateId = entity.TemplateId; + entity.PublisherId = entity.WriterId; + entity.PublishName = entity.Name; + entity.PublishDate = entity.UpdateDate; SetEntityTags(entity, _tagRepository); } - else if (content.PublishedState == PublishedState.Unpublishing) + else if (entity.PublishedState == PublishedState.Unpublishing) { - content.Published = false; - content.PublishTemplateId = null; - content.PublisherId = null; - content.PublishName = null; - content.PublishDate = null; + entity.Published = false; + entity.PublishTemplateId = null; + entity.PublisherId = null; + entity.PublishName = null; + entity.PublishDate = null; ClearEntityTags(entity, _tagRepository); } @@ -1338,7 +1333,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #region Utilities - private void SanitizeNames(Content content, bool publishing) + private void SanitizeNames(IContent content, bool publishing) { // a content item *must* have an invariant name, and invariant published name // else we just cannot write the invariant rows (node, content version...) to the database @@ -1403,7 +1398,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement x.NodeId != SqlTemplate.Arg("id")) .OrderBy(x => x.LanguageId)); - private void EnsureVariantNamesAreUnique(Content content, bool publishing) + private void EnsureVariantNamesAreUnique(IContent content, bool publishing) { if (!EnsureUniqueNaming || !content.ContentType.VariesByCulture() || content.CultureInfos.Count == 0) return; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs index 69523a860a..9aa28fb18a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DomainRepository.cs @@ -6,6 +6,7 @@ using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; @@ -101,7 +102,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (languageExists == 0) throw new NullReferenceException("No language exists with id " + entity.LanguageId.Value); } - ((UmbracoDomain)entity).AddingEntity(); + entity.AddingEntity(); var factory = new DomainModelFactory(); var dto = factory.BuildDto(entity); @@ -120,7 +121,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IDomain entity) { - ((UmbracoDomain)entity).UpdatingEntity(); + entity.UpdatingEntity(); var exists = Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoDomain WHERE domainName = @domainName AND umbracoDomain.id <> @id", new { domainName = entity.DomainName, id = entity.Id }); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs index 0fa48e5521..f708590ea8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ExternalLoginRepository.cs @@ -139,7 +139,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IIdentityUserLogin entity) { - ((EntityBase)entity).AddingEntity(); + entity.AddingEntity(); var dto = ExternalLoginFactory.BuildDto(entity); @@ -151,7 +151,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IIdentityUserLogin entity) { - ((EntityBase)entity).UpdatingEntity(); + entity.UpdatingEntity(); var dto = ExternalLoginFactory.BuildDto(entity); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs index 5a62c25df7..8429532b01 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs @@ -129,7 +129,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (entity.IsoCode.IsNullOrWhiteSpace() || entity.CultureName.IsNullOrWhiteSpace()) throw new InvalidOperationException("Cannot save a language without an ISO code and a culture name."); - ((EntityBase) entity).AddingEntity(); + entity.AddingEntity(); // deal with entity becoming the new default entity if (entity.IsDefault) @@ -156,7 +156,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (entity.IsoCode.IsNullOrWhiteSpace() || entity.CultureName.IsNullOrWhiteSpace()) throw new InvalidOperationException("Cannot save a language without an ISO code and a culture name."); - ((EntityBase) entity).UpdatingEntity(); + entity.UpdatingEntity(); if (entity.IsDefault) { diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs index 565917e078..f0044e225d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs @@ -132,7 +132,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IMacro entity) { - ((EntityBase)entity).AddingEntity(); + entity.AddingEntity(); var dto = MacroFactory.BuildDto(entity); @@ -152,7 +152,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IMacro entity) { - ((EntityBase)entity).UpdatingEntity(); + entity.UpdatingEntity(); ; var dto = MacroFactory.BuildDto(entity); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index 65043c4c67..25828b8126 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; @@ -217,7 +218,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IMedia entity) { var media = (Models.Media) entity; - media.AddingEntity(); + entity.AddingEntity(); // ensure unique name on the same level entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs index 281255e755..1abc75cf3a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs @@ -5,6 +5,7 @@ using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; @@ -102,7 +103,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IMediaType entity) { - ((MediaType)entity).AddingEntity(); + entity.AddingEntity(); PersistNewBaseContentType(entity); @@ -114,7 +115,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement ValidateAlias(entity); //Updates Modified date - ((MediaType)entity).UpdatingEntity(); + entity.UpdatingEntity(); //Look up parent to get and set the correct Path if ParentId has changed if (entity.IsPropertyDirty("ParentId")) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs index ff7a79f98e..c138550de5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberGroupRepository.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; @@ -91,8 +92,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IMemberGroup entity) { //Save to db + entity.AddingEntity(); var group = (MemberGroup)entity; - group.AddingEntity(); var dto = MemberGroupFactory.BuildDto(group); 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 diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index 808f61305a..1fc3568fc0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -6,6 +6,7 @@ using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; @@ -232,8 +233,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IMember entity) { + if (entity.ProviderUserKey == null) + { + entity.ProviderUserKey = entity.Key; + } + entity.AddingEntity(); + var member = (Member) entity; - member.AddingEntity(); // ensure that strings don't contain characters that are invalid in xml // TODO: do we really want to keep doing this here? diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs index ecc0b73ed8..61981a42e3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs @@ -5,6 +5,7 @@ using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; @@ -131,7 +132,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { ValidateAlias(entity); - ((MemberType)entity).AddingEntity(); + entity.AddingEntity(); //set a default icon if one is not specified if (entity.Icon.IsNullOrWhiteSpace()) @@ -165,7 +166,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement ValidateAlias(entity); //Updates Modified date - ((MemberType)entity).UpdatingEntity(); + entity.UpdatingEntity(); //Look up parent to get and set the correct Path if ParentId has changed if (entity.IsPropertyDirty("ParentId")) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/PublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/PublicAccessRepository.cs index bd2580b38f..1dc7aa478d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/PublicAccessRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/PublicAccessRepository.cs @@ -5,6 +5,7 @@ using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index c5ba24f385..4b4af505b8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -134,7 +134,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IRelation entity) { - ((EntityBase)entity).AddingEntity(); + entity.AddingEntity(); var factory = new RelationFactory(entity.RelationType); var dto = factory.BuildDto(entity); @@ -147,7 +147,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IRelation entity) { - ((EntityBase)entity).UpdatingEntity(); + entity.UpdatingEntity(); var factory = new RelationFactory(entity.RelationType); var dto = factory.BuildDto(entity); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs index 4faf78bd0a..075d4aa769 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs @@ -133,7 +133,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IRelationType entity) { - ((EntityBase)entity).AddingEntity(); + entity.AddingEntity(); var dto = RelationTypeFactory.BuildDto(entity); @@ -145,7 +145,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IRelationType entity) { - ((EntityBase)entity).UpdatingEntity(); + entity.UpdatingEntity(); var dto = RelationTypeFactory.BuildDto(entity); Database.Update(dto); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs index 6b2dfddaeb..1497c2857c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ServerRegistrationRepository.cs @@ -5,6 +5,7 @@ using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; @@ -96,7 +97,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IServerRegistration entity) { - ((ServerRegistration)entity).AddingEntity(); + entity.AddingEntity(); var dto = ServerRegistrationFactory.BuildDto(entity); @@ -108,7 +109,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IServerRegistration entity) { - ((ServerRegistration)entity).UpdatingEntity(); + entity.UpdatingEntity(); var dto = ServerRegistrationFactory.BuildDto(entity); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs index f26fcca81b..279e03b19e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/TagRepository.cs @@ -85,7 +85,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// protected override void PersistNewItem(ITag entity) { - ((EntityBase)entity).AddingEntity(); + entity.AddingEntity(); var dto = TagFactory.BuildDto(entity); var id = Convert.ToInt32(Database.Insert(dto)); @@ -97,7 +97,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// protected override void PersistUpdatedItem(ITag entity) { - ((EntityBase)entity).UpdatingEntity(); + entity.UpdatingEntity(); var dto = TagFactory.BuildDto(entity); Database.Update(dto); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs index 3b247950e4..0701a0996e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserGroupRepository.cs @@ -290,7 +290,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IUserGroup entity) { - ((UserGroup) entity).AddingEntity(); + entity.AddingEntity(); var userGroupDto = UserGroupFactory.BuildDto(entity); @@ -304,7 +304,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IUserGroup entity) { - ((UserGroup) entity).UpdatingEntity(); + entity.UpdatingEntity(); var userGroupDto = UserGroupFactory.BuildDto(entity); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index 91a20c5bdd..96abc37662 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -445,7 +445,7 @@ ORDER BY colName"; return; } - ((User) entity).AddingEntity(); + entity.AddingEntity(); // ensure security stamp if missing if (entity.SecurityStamp.IsNullOrWhiteSpace()) @@ -495,7 +495,7 @@ ORDER BY colName"; protected override void PersistUpdatedItem(IUser entity) { // updates Modified date - ((User) entity).UpdatingEntity(); + entity.UpdatingEntity(); // ensure security stamp if missing if (entity.SecurityStamp.IsNullOrWhiteSpace()) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 7ac1e9d06e..343043ea36 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -234,6 +234,7 @@ + From 01c89bd3382c9927eb152f95d04cb505232c4971 Mon Sep 17 00:00:00 2001 From: Rasmus John Pedersen Date: Fri, 28 Jun 2019 10:11:20 +0200 Subject: [PATCH 063/776] Make fields set by constructor non static --- src/Umbraco.Core/Composing/TypeLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index af9277fce9..fe7a561eca 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -42,8 +42,8 @@ namespace Umbraco.Core.Composing private string _currentAssembliesHash; private IEnumerable _assemblies; private bool _reportedChange; - private static string _localTempPath; - private static string _fileBasePath; + private readonly string _localTempPath; + private string _fileBasePath; /// /// Initializes a new instance of the class. From fabacd1689af72fb0d3d1af6f83cb2a5aed51f15 Mon Sep 17 00:00:00 2001 From: Adam Nelson Date: Mon, 1 Jul 2019 00:26:35 +1000 Subject: [PATCH 064/776] V8: Fixes case issue on metaData.hasChildren in mini list view as well as .toLowerCase() issue on undefined scope.entityTypeFilter.filter (#5744) --- .../common/directives/components/umbminilistview.directive.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js index 196a28c753..9c140d572e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbminilistview.directive.js @@ -71,7 +71,7 @@ } // set published state for content if (c.metaData) { - c.hasChildren = c.metaData.HasChildren; + c.hasChildren = c.metaData.hasChildren; if(scope.entityType === "Document") { c.published = c.metaData.IsPublished; } @@ -79,7 +79,7 @@ // filter items if there is a filter and it's not advanced // ** ignores advanced filter at the moment - if (scope.entityTypeFilter && !scope.entityTypeFilter.filterAdvanced) { + if (scope.entityTypeFilter && scope.entityTypeFilter.filter && !scope.entityTypeFilter.filterAdvanced) { var a = scope.entityTypeFilter.filter.toLowerCase().replace(/\s/g, '').split(','); var found = a.indexOf(c.metaData.ContentTypeAlias.toLowerCase()) >= 0; From 80e6880bb7fccb89f7a00122fc7b887008a4976c Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 1 Jul 2019 16:22:54 +1000 Subject: [PATCH 065/776] more manual merging - getting anchors back in tinymce --- .../DataTypes/PreValueMigratorComposer.cs | 34 ++++++++-------- .../src/common/services/tinymce.service.js | 40 ++++++++++++++++++- .../linkpicker/linkpicker.controller.js | 2 +- .../Mapping/ContentPropertyDisplayMapper.cs | 4 +- .../Mapping/ContentPropertyDtoMapper.cs | 4 +- .../Mapping/ContentPropertyMapDefinition.cs | 8 ++-- 6 files changed, 64 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs index 9e2dec9b70..4c3f8534e7 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs @@ -2,24 +2,24 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes { - [RuntimeLevel(MinLevel = RuntimeLevel.Upgrade, MaxLevel = RuntimeLevel.Upgrade)] // only on upgrades - public class PreValueMigratorComposer : ICoreComposer +[RuntimeLevel(MinLevel = RuntimeLevel.Upgrade, MaxLevel = RuntimeLevel.Upgrade)] // only on upgrades +public class PreValueMigratorComposer : ICoreComposer +{ + public void Compose(Composition composition) { - public void Compose(Composition composition) - { - // do NOT add DefaultPreValueMigrator to this list! - // it will be automatically used if nothing matches + // do NOT add DefaultPreValueMigrator to this list! + // it will be automatically used if nothing matches - composition.WithCollectionBuilder() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append(); - } + composition.WithCollectionBuilder() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); } } +} diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 01eef916b1..603e723bec 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -6,7 +6,7 @@ * @description * A service containing all logic for all of the Umbraco TinyMCE plugins */ -function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, stylesheetResource, macroResource, macroService, $routeParams, umbRequestHelper, angularHelper, userService, editorService, editorState) { +function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, stylesheetResource, macroResource, macroService, $routeParams, umbRequestHelper, angularHelper, userService, editorService, editorState, contentEditingHelper) { //These are absolutely required in order for the macros to render inline //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce @@ -1077,6 +1077,42 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s startWatch(); } + /** + * Internal method ... to retrieve the anchor named properties from the serialized string of a content item's properties + * + * From the given string, generates a string array where each item is the id attribute value from a named anchor + * 'some string with a named anchor' returns ['anchor'] + */ + function getCurrentAnchorNames() { + + if (!editorState.current || !editorState.current.variants) { + return null; + } + + //fixme - this only takes into account the first variant , not the 'current' one. + var jsonProperties = JSON.stringify(contentEditingHelper.getAllProps(editorState.current.variants[0])); + + if (!jsonProperties) { + return null; + } + + var anchors = []; + + var anchorPattern = //gi; + var matches = jsonProperties.match(anchorPattern); + + + if (matches) { + anchors = matches.map(function (v) { + return v.substring(v.indexOf('"') + 1, v.lastIndexOf('\\')); + }); + } + + return anchors.filter(function (val, i, self) { + return self.indexOf(val) === i; + }); + } + args.editor.on('init', function (e) { if (args.model.value) { @@ -1118,7 +1154,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s self.createLinkPicker(args.editor, function (currentTarget, anchorElement) { var linkPicker = { currentTarget: currentTarget, - anchors: editorState.current ? self.getAnchorNames(JSON.stringify(editorState.current.properties)) : [], + anchors: getCurrentAnchorNames(), submit: function (model) { self.insertLinkInEditor(args.editor, model.target, anchorElement); editorService.close(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js index 0286fb2384..d1743baf10 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js @@ -1,6 +1,6 @@ //used for the media picker dialog angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", - function ($scope, eventsService, entityResource, mediaResource, mediaHelper, udiParser, userService, localizationService, tinyMceService, editorService, contentEditingHelper) { + function ($scope, eventsService, entityResource, mediaResource, mediaHelper, udiParser, userService, localizationService, editorService) { var vm = this; var dialogOptions = $scope.model; diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs index 8a45548e9c..f68c5d8b44 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs @@ -14,8 +14,8 @@ namespace Umbraco.Web.Models.Mapping { private readonly ILocalizedTextService _textService; - public ContentPropertyDisplayMapper(IDataTypeService dataTypeService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors) - : base(dataTypeService, logger, propertyEditors) + public ContentPropertyDisplayMapper(IDataTypeService dataTypeService, IEntityService entityService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors) + : base(dataTypeService, entityService, logger, propertyEditors) { _textService = textService; } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs index f192cd32ce..72107c6201 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs @@ -12,8 +12,8 @@ namespace Umbraco.Web.Models.Mapping /// internal class ContentPropertyDtoMapper : ContentPropertyBasicMapper { - public ContentPropertyDtoMapper(IDataTypeService dataTypeService, ILogger logger, PropertyEditorCollection propertyEditors) - : base(dataTypeService, logger, propertyEditors) + public ContentPropertyDtoMapper(IDataTypeService dataTypeService, IEntityService entityService, ILogger logger, PropertyEditorCollection propertyEditors) + : base(dataTypeService, entityService, logger, propertyEditors) { } public override void Map(Property property, ContentPropertyDto dest, MapperContext context) diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs index 226560c516..e6290cc19e 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyMapDefinition.cs @@ -17,11 +17,11 @@ namespace Umbraco.Web.Models.Mapping private readonly ContentPropertyDtoMapper _contentPropertyDtoConverter; private readonly ContentPropertyDisplayMapper _contentPropertyDisplayMapper; - public ContentPropertyMapDefinition(IDataTypeService dataTypeService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors) + public ContentPropertyMapDefinition(IDataTypeService dataTypeService, IEntityService entityService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors) { - _contentPropertyBasicConverter = new ContentPropertyBasicMapper(dataTypeService, logger, propertyEditors); - _contentPropertyDtoConverter = new ContentPropertyDtoMapper(dataTypeService, logger, propertyEditors); - _contentPropertyDisplayMapper = new ContentPropertyDisplayMapper(dataTypeService, textService, logger, propertyEditors); + _contentPropertyBasicConverter = new ContentPropertyBasicMapper(dataTypeService, entityService, logger, propertyEditors); + _contentPropertyDtoConverter = new ContentPropertyDtoMapper(dataTypeService, entityService, logger, propertyEditors); + _contentPropertyDisplayMapper = new ContentPropertyDisplayMapper(dataTypeService, entityService, textService, logger, propertyEditors); } public void DefineMaps(UmbracoMapper mapper) From 1885767ef118d17b7d72ec9fcb97b491272b1a3e Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 1 Jul 2019 16:33:09 +1000 Subject: [PATCH 066/776] Internalizes the new Anchor logic and removes it from the IContentService interface into extensions. --- .../Services/ContentServiceExtensions.cs | 37 +++++++++++++++++++ src/Umbraco.Core/Services/IContentService.cs | 4 +- .../Services/Implement/ContentService.cs | 34 +---------------- .../src/common/resources/entity.resource.js | 18 --------- src/Umbraco.Web/Editors/EntityController.cs | 20 +--------- 5 files changed, 42 insertions(+), 71 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentServiceExtensions.cs b/src/Umbraco.Core/Services/ContentServiceExtensions.cs index 1175df81dc..dfe02ba690 100644 --- a/src/Umbraco.Core/Services/ContentServiceExtensions.cs +++ b/src/Umbraco.Core/Services/ContentServiceExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -11,6 +12,42 @@ namespace Umbraco.Core.Services /// public static class ContentServiceExtensions { + #region RTE Anchor values + + private static readonly Regex AnchorRegex = new Regex("", RegexOptions.Compiled); + + internal static IEnumerable GetAnchorValuesFromRTEs(this IContentService contentService, int id, string culture = "*") + { + var result = new List(); + var content = contentService.GetById(id); + + foreach (var contentProperty in content.Properties) + { + if (contentProperty.PropertyType.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.TinyMce)) + { + var value = contentProperty.GetValue(culture)?.ToString(); + if (!string.IsNullOrEmpty(value)) + { + result.AddRange(contentService.GetAnchorValuesFromRTEContent(value)); + } + } + } + return result; + } + + + internal static IEnumerable GetAnchorValuesFromRTEContent(this IContentService contentService, string rteContent) + { + var result = new List(); + var matches = AnchorRegex.Matches(rteContent); + foreach (Match match in matches) + { + result.Add(match.Value.Split('\"')[1]); + } + return result; + } + #endregion + public static IEnumerable GetByIds(this IContentService contentService, IEnumerable ids) { var guids = new List(); diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index a9226fbae2..6f9ca58821 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -526,8 +526,6 @@ namespace Umbraco.Core.Services OperationResult Rollback(int id, int versionId, string culture = "*", int userId = Constants.Security.SuperUserId); #endregion - - IEnumerable GetAnchorValuesFromRTEs(int id, string culture = "*"); - IEnumerable GetAnchorValuesFromRTEContent(string rteContent, string culture = "*"); + } } diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 90aaf6fc5d..e49dcf4a12 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -32,7 +32,7 @@ namespace Umbraco.Core.Services.Implement private IQuery _queryNotTrashed; //TODO: The non-lazy object should be injected private readonly Lazy _propertyValidationService = new Lazy(() => new PropertyValidationService()); - private static readonly Regex AnchorRegex = new Regex("", RegexOptions.Compiled); + #region Constructors @@ -3028,36 +3028,6 @@ namespace Umbraco.Core.Services.Implement #endregion - #region RTE Anchor values - public IEnumerable GetAnchorValuesFromRTEs(int id, string culture = "*") - { - var result = new List(); - var content = GetById(id); - foreach (var contentProperty in content.Properties) - { - if (contentProperty.PropertyType.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.TinyMce)) - { - var value = contentProperty.GetValue(culture)?.ToString(); - if (!string.IsNullOrEmpty(value)) - { - result.AddRange(GetAnchorValuesFromRTEContent(value)); - } - } - } - return result; - } - - - public IEnumerable GetAnchorValuesFromRTEContent(string rteContent, string culture = "*") - { - var result = new List(); - var matches = AnchorRegex.Matches(rteContent); - foreach (Match match in matches) - { - result.Add(match.Value.Split('\"')[1]); - } - return result; - } - #endregion + } } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 95bfd1e49b..eee877a60c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -176,24 +176,6 @@ function entityResource($q, $http, umbRequestHelper) { 'Failed to retrieve url and anchors data for id ' + id); }, - - getAnchors: function (rteContent) { - - if (!rteContent || rteContent.length === 0) { - return []; - } - - return umbRequestHelper.resourcePromise( - $http.post( - umbRequestHelper.getApiUrl( - "entityApiBaseUrl", - 'GetAnchors'), - { - rteContent: rteContent - }), - 'Failed to anchors data for rte content ' + rteContent); - }, - /** * @ngdoc method * @name umbraco.resources.entityResource#getByIds diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 4184182a1d..b0868bd2a6 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -286,30 +286,14 @@ namespace Umbraco.Web.Editors publishedContentExists: i => Umbraco.Content(i) != null); } - - //fixme - culture? [HttpGet] - public UrlAndAnchors GetUrlAndAnchors([FromUri]int id) + public UrlAndAnchors GetUrlAndAnchors([FromUri]int id, [FromUri]string culture = "*") { var url = UmbracoContext.UrlProvider.GetUrl(id); - var anchorValues = Services.ContentService.GetAnchorValuesFromRTEs(id); + var anchorValues = Services.ContentService.GetAnchorValuesFromRTEs(id, culture); return new UrlAndAnchors(url, anchorValues); } - public class AnchorsModel - { - public string RteContent { get; set; } - } - - //fixme - culture? - [HttpGet] - [HttpPost] - public IEnumerable GetAnchors(AnchorsModel model) - { - var anchorValues = Services.ContentService.GetAnchorValuesFromRTEContent(model.RteContent); - return anchorValues; - } - #region GetById From fa4acb255b6ebf81b7e285fa7486384d7e26e5c1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 1 Jul 2019 17:09:11 +1000 Subject: [PATCH 067/776] fixes missing 'var' --- .../mediaPicker/mediapicker.controller.js | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 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 96354d7696..bb34ba379c 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 @@ -1,7 +1,7 @@ //used for the media picker dialog angular.module("umbraco") .controller("Umbraco.Overlays.MediaPickerController", - function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, userService, $cookies, localStorageService, localizationService) { + function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, userService, $cookies, localStorageService, localizationService) { if (!$scope.model.title) { $scope.model.title = localizationService.localize("defaultdialogs_selectMedia"); @@ -40,7 +40,7 @@ angular.module("umbraco") $scope.acceptedMediatypes = []; mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) - .then(function(types) { + .then(function (types) { $scope.acceptedMediatypes = types; }); @@ -107,24 +107,24 @@ angular.module("umbraco") } } - $scope.upload = function(v) { + $scope.upload = function (v) { angular.element(".umb-file-dropzone-directive .file-select").click(); }; - $scope.dragLeave = function(el, event) { + $scope.dragLeave = function (el, event) { $scope.activeDrag = false; }; - $scope.dragEnter = function(el, event) { + $scope.dragEnter = function (el, event) { $scope.activeDrag = true; }; - $scope.submitFolder = function() { + $scope.submitFolder = function () { if ($scope.newFolderName) { $scope.creatingFolder = true; mediaResource .addFolder($scope.newFolderName, $scope.currentFolder.id) - .then(function(data) { + .then(function (data) { //we've added a new folder so lets clear the tree cache for that specific item treeService.clearCache({ cacheKey: "__media", //this is the main media tree cache key @@ -140,14 +140,14 @@ angular.module("umbraco") } }; - $scope.enterSubmitFolder = function(event) { + $scope.enterSubmitFolder = function (event) { if (event.keyCode === 13) { $scope.submitFolder(); event.stopPropagation(); } }; - $scope.gotoFolder = function(folder) { + $scope.gotoFolder = function (folder) { if (!$scope.multiPicker) { deselectAllImages($scope.model.selectedImages); } @@ -158,9 +158,9 @@ angular.module("umbraco") if (folder.id > 0) { entityResource.getAncestors(folder.id, "media", { dataTypeId: $scope.model.dataTypeId }) - .then(function(anc) { + .then(function (anc) { $scope.path = _.filter(anc, - function(f) { + function (f) { return f.path.indexOf($scope.startNodeId) !== -1; }); }); @@ -181,7 +181,7 @@ angular.module("umbraco") return getChildren(folder.id); }; - $scope.clickHandler = function(image, event, index) { + $scope.clickHandler = function (image, event, index) { if (image.isFolder) { if ($scope.disableFolderSelect) { $scope.gotoFolder(image); @@ -209,7 +209,7 @@ angular.module("umbraco") } }; - $scope.clickItemName = function(item) { + $scope.clickItemName = function (item) { if (item.isFolder) { $scope.gotoFolder(item); } @@ -241,8 +241,8 @@ angular.module("umbraco") images.length = 0; } - $scope.onUploadComplete = function(files) { - $scope.gotoFolder($scope.currentFolder).then(function() { + $scope.onUploadComplete = function (files) { + $scope.gotoFolder($scope.currentFolder).then(function () { if (files.length === 1 && $scope.model.selectedImages.length === 0) { var image = $scope.images[$scope.images.length - 1]; $scope.target = image; @@ -252,7 +252,7 @@ angular.module("umbraco") }); }; - $scope.onFilesQueue = function() { + $scope.onFilesQueue = function () { $scope.activeDrag = false; }; @@ -285,12 +285,12 @@ angular.module("umbraco") $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); } - $scope.openDetailsDialog = function() { + $scope.openDetailsDialog = function () { $scope.mediaPickerDetailsOverlay = {}; $scope.mediaPickerDetailsOverlay.show = true; - $scope.mediaPickerDetailsOverlay.submit = function(model) { + $scope.mediaPickerDetailsOverlay.submit = function (model) { $scope.model.selectedImages.push($scope.target); $scope.model.submit($scope.model); @@ -298,42 +298,42 @@ angular.module("umbraco") $scope.mediaPickerDetailsOverlay = null; }; - $scope.mediaPickerDetailsOverlay.close = function(oldModel) { + $scope.mediaPickerDetailsOverlay.close = function (oldModel) { $scope.mediaPickerDetailsOverlay.show = false; $scope.mediaPickerDetailsOverlay = null; }; }; - var debounceSearchMedia = _.debounce(function() { - $scope.$apply(function() { - if ($scope.searchOptions.filter) { - searchMedia(); - } else { - // reset pagination - $scope.searchOptions = { - pageNumber: 1, - pageSize: 100, - totalItems: 0, - totalPages: 0, - filter: '', - dataTypeId: $scope.model.dataTypeId - }; - getChildren($scope.currentFolder.id); - } - }); - }, 500); + var debounceSearchMedia = _.debounce(function () { + $scope.$apply(function () { + if ($scope.searchOptions.filter) { + searchMedia(); + } else { + // reset pagination + $scope.searchOptions = { + pageNumber: 1, + pageSize: 100, + totalItems: 0, + totalPages: 0, + filter: '', + dataTypeId: $scope.model.dataTypeId + }; + getChildren($scope.currentFolder.id); + } + }); + }, 500); - $scope.changeSearch = function() { + $scope.changeSearch = function () { $scope.loading = true; debounceSearchMedia(); }; - $scope.toggle = function() { + $scope.toggle = function () { // Make sure to activate the changeSearch function everytime the toggle is clicked $scope.changeSearch(); } - $scope.changePagination = function(pageNumber) { + $scope.changePagination = function (pageNumber) { $scope.loading = true; $scope.searchOptions.pageNumber = pageNumber; searchMedia(); @@ -342,10 +342,10 @@ angular.module("umbraco") function searchMedia() { $scope.loading = true; entityResource.getPagedDescendants($scope.currentFolder.id, "Media", $scope.searchOptions) - .then(function(data) { + .then(function (data) { // update image data to work with image grid angular.forEach(data.items, - function(mediaItem) { + function (mediaItem) { // set thumbnail and src mediaItem.thumbnail = mediaHelper.resolveFileFromEntity(mediaItem, true); mediaItem.image = mediaHelper.resolveFileFromEntity(mediaItem, false); @@ -389,10 +389,10 @@ angular.module("umbraco") function getChildren(id) { $scope.loading = true; return entityResource.getChildren(id, "Media", $scope.searchOptions) - .then(function(data) { + .then(function (data) { - for (i=0;i Date: Mon, 1 Jul 2019 18:23:36 +1000 Subject: [PATCH 068/776] gets content picker ignore start nodes working, fixes descriptions --- .../mediapicker/mediapicker.controller.js | 115 +++++++++--------- .../treepicker/treepicker.controller.js | 6 + .../treepicker/treepicker.html | 1 + .../contentpicker/contentpicker.controller.js | 5 +- src/Umbraco.Web/Editors/EntityController.cs | 1 + .../ContentPickerConfiguration.cs | 6 +- .../PropertyEditors/GridConfiguration.cs | 4 +- .../MediaPickerConfiguration.cs | 4 +- .../MultiNodePickerConfiguration.cs | 4 +- .../MultiUrlPickerConfiguration.cs | 4 +- .../PropertyEditors/RichTextConfiguration.cs | 4 +- .../Trees/ContentTreeControllerBase.cs | 28 +++-- 12 files changed, 103 insertions(+), 79 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index 23d9cef9a1..015a452e67 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -1,7 +1,7 @@ //used for the media picker dialog angular.module("umbraco") .controller("Umbraco.Editors.MediaPickerController", - function($scope, mediaResource, entityResource, userService, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService, editorService) { + function ($scope, mediaResource, entityResource, userService, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService, editorService) { if (!$scope.model.title) { localizationService.localizeMany(["defaultdialogs_selectMedia", "general_includeFromsubFolders"]) @@ -47,7 +47,7 @@ angular.module("umbraco") $scope.acceptedMediatypes = []; mediaTypeHelper.getAllowedImagetypes($scope.startNodeId) - .then(function(types) { + .then(function (types) { $scope.acceptedMediatypes = types; }); @@ -96,7 +96,7 @@ angular.module("umbraco") //media object so we need to look it up var id = $scope.target.udi ? $scope.target.udi : $scope.target.id; var altText = $scope.target.altText; - entityResource.getById(id, "Media") + entityResource.getById(id, "Media") .then(function (node) { $scope.target = node; if (ensureWithinStartNode(node)) { @@ -106,28 +106,28 @@ angular.module("umbraco") $scope.openDetailsDialog(); } }, - gotoStartNode); + gotoStartNode); } } - $scope.upload = function(v) { + $scope.upload = function (v) { angular.element(".umb-file-dropzone .file-select").trigger("click"); }; - $scope.dragLeave = function(el, event) { + $scope.dragLeave = function (el, event) { $scope.activeDrag = false; }; - $scope.dragEnter = function(el, event) { + $scope.dragEnter = function (el, event) { $scope.activeDrag = true; }; - $scope.submitFolder = function() { + $scope.submitFolder = function () { if ($scope.model.newFolderName) { $scope.model.creatingFolder = true; mediaResource .addFolder($scope.model.newFolderName, $scope.currentFolder.id) - .then(function(data) { + .then(function (data) { //we've added a new folder so lets clear the tree cache for that specific item treeService.clearCache({ cacheKey: "__media", //this is the main media tree cache key @@ -143,14 +143,14 @@ angular.module("umbraco") } }; - $scope.enterSubmitFolder = function(event) { + $scope.enterSubmitFolder = function (event) { if (event.keyCode === 13) { $scope.submitFolder(); event.stopPropagation(); } }; - $scope.gotoFolder = function(folder) { + $scope.gotoFolder = function (folder) { if (!$scope.multiPicker) { deselectAllImages($scope.model.selection); } @@ -161,15 +161,15 @@ angular.module("umbraco") if (folder.id > 0) { entityResource.getAncestors(folder.id, "media", { dataTypeId: $scope.model.dataTypeId }) - .then(function(anc) { + .then(function (anc) { $scope.path = _.filter(anc, - function(f) { + function (f) { return f.path.indexOf($scope.startNodeId) !== -1; }); }); mediaTypeHelper.getAllowedImagetypes(folder.id) - .then(function(types) { + .then(function (types) { $scope.acceptedMediatypes = types; }); } else { @@ -183,7 +183,7 @@ angular.module("umbraco") return getChildren(folder.id); }; - $scope.clickHandler = function(image, event, index) { + $scope.clickHandler = function (image, event, index) { if (image.isFolder) { if ($scope.disableFolderSelect) { $scope.gotoFolder(image); @@ -211,7 +211,7 @@ angular.module("umbraco") } }; - $scope.clickItemName = function(item) { + $scope.clickItemName = function (item) { if (item.isFolder) { $scope.gotoFolder(item); } @@ -243,8 +243,8 @@ angular.module("umbraco") images.length = 0; } - $scope.onUploadComplete = function(files) { - $scope.gotoFolder($scope.currentFolder).then(function() { + $scope.onUploadComplete = function (files) { + $scope.gotoFolder($scope.currentFolder).then(function () { if (files.length === 1 && $scope.model.selection.length === 0) { var image = $scope.images[$scope.images.length - 1]; $scope.target = image; @@ -254,7 +254,7 @@ angular.module("umbraco") }); }; - $scope.onFilesQueue = function() { + $scope.onFilesQueue = function () { $scope.activeDrag = false; }; @@ -287,12 +287,12 @@ angular.module("umbraco") $scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); } - $scope.openDetailsDialog = function() { + $scope.openDetailsDialog = function () { $scope.mediaPickerDetailsOverlay = {}; $scope.mediaPickerDetailsOverlay.show = true; - $scope.mediaPickerDetailsOverlay.submit = function(model) { + $scope.mediaPickerDetailsOverlay.submit = function (model) { $scope.model.selection.push($scope.target); $scope.model.submit($scope.model); @@ -300,42 +300,42 @@ angular.module("umbraco") $scope.mediaPickerDetailsOverlay = null; }; - $scope.mediaPickerDetailsOverlay.close = function(oldModel) { + $scope.mediaPickerDetailsOverlay.close = function (oldModel) { $scope.mediaPickerDetailsOverlay.show = false; $scope.mediaPickerDetailsOverlay = null; }; }; - var debounceSearchMedia = _.debounce(function() { - $scope.$apply(function() { - if ($scope.searchOptions.filter) { - searchMedia(); - } else { - // reset pagination - $scope.searchOptions = { - pageNumber: 1, - pageSize: 100, - totalItems: 0, - totalPages: 0, - filter: '', - dataTypeId: $scope.model.dataTypeId - }; - getChildren($scope.currentFolder.id); - } - }); - }, 500); + var debounceSearchMedia = _.debounce(function () { + $scope.$apply(function () { + if ($scope.searchOptions.filter) { + searchMedia(); + } else { + // reset pagination + $scope.searchOptions = { + pageNumber: 1, + pageSize: 100, + totalItems: 0, + totalPages: 0, + filter: '', + dataTypeId: $scope.model.dataTypeId + }; + getChildren($scope.currentFolder.id); + } + }); + }, 500); - $scope.changeSearch = function() { + $scope.changeSearch = function () { $scope.loading = true; debounceSearchMedia(); }; - $scope.toggle = function() { + $scope.toggle = function () { // Make sure to activate the changeSearch function everytime the toggle is clicked $scope.changeSearch(); } - $scope.changePagination = function(pageNumber) { + $scope.changePagination = function (pageNumber) { $scope.loading = true; $scope.searchOptions.pageNumber = pageNumber; searchMedia(); @@ -344,9 +344,9 @@ angular.module("umbraco") function searchMedia() { $scope.loading = true; entityResource.getPagedDescendants($scope.currentFolder.id, "Media", $scope.searchOptions) - .then(function(data) { + .then(function (data) { // update image data to work with image grid - angular.forEach(data.items, function(mediaItem) { + angular.forEach(data.items, function (mediaItem) { setMediaMetaData(mediaItem); }); // update images @@ -389,10 +389,9 @@ angular.module("umbraco") function getChildren(id) { $scope.loading = true; return entityResource.getChildren(id, "Media", $scope.searchOptions) - .then(function(data) { - - for (i=0;i diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 8fbed8cb9c..f8ecb898ae 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -133,7 +133,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper multiPicker: $scope.model.config.multiPicker, entityType: entityType, filterCssClass: "not-allowed not-published", - startNodeId: null, + startNodeId: null, currentNode: editorState ? editorState.current : null, callback: function (data) { if (angular.isArray(data)) { @@ -155,6 +155,8 @@ function contentPickerController($scope, entityResource, editorState, iconHelper // pre-value config on to the dialog options angular.extend(dialogOptions, $scope.model.config); + dialogOptions.dataTypeId = $scope.model.dataTypeId; + // if we can't pick more than one item, explicitly disable multiPicker in the dialog options if ($scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) === 1) { dialogOptions.multiPicker = false; @@ -211,7 +213,6 @@ function contentPickerController($scope, entityResource, editorState, iconHelper //dialog $scope.openCurrentPicker = function () { $scope.currentPicker = dialogOptions; - $scope.contentPickerOverlay.dataTypeId = $scope.model.dataTypeId; $scope.currentPicker.submit = function (model) { if (angular.isArray(model.selection)) { diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index b0868bd2a6..573bb2b872 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -607,6 +607,7 @@ namespace Umbraco.Web.Editors { context.SetCulture(culture); }); + //TODO: Why is this here and not in the mapping? target.AdditionalData["hasChildren"] = source.HasChildren; return target; }) diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs index d7f7ca9551..021d416781 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs @@ -5,13 +5,15 @@ namespace Umbraco.Web.PropertyEditors { public class ContentPickerConfiguration : IIgnoreUserStartNodesConfig { - [ConfigurationField("showOpenButton", "Show open button (this feature is in beta!)", "boolean", Description = "Opens the node in a dialog")] + [ConfigurationField("showOpenButton", "Show open button", "boolean", Description = "Opens the node in a dialog")] public bool ShowOpenButton { get; set; } [ConfigurationField("startNodeId", "Start node", "treepicker")] // + config in configuration editor ctor public Udi StartNodeId { get; set; } - [ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "Selecting this option allows a user to choose nodes that they normally don't have access to.", "boolean")] + [ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + "Ignore User Start Nodes", "boolean", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } } } diff --git a/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs b/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs index 80f09d3f8f..b7a76ac960 100644 --- a/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs @@ -16,7 +16,9 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("rte", "Rich text editor", "views/propertyeditors/rte/rte.prevalues.html", Description = "Rich text editor configuration")] public JObject Rte { get; set; } - [ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "Selecting this option allows a user to choose nodes that they normally don't have access to.", "boolean")] + [ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + "Ignore User Start Nodes", "boolean", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } } } diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs index 3a9a9e25ac..b8b9476184 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs @@ -20,7 +20,9 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("startNodeId", "Start node", "mediapicker")] public Udi StartNodeId { get; set; } - [ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "Selecting this option allows a user to choose nodes that they normally don't have access to.", "boolean")] + [ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + "Ignore User Start Nodes", "boolean", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } } } diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs index 310211ab2b..b099573b9f 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs @@ -23,7 +23,9 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("showOpenButton", "Show open button (this feature is in preview!)", "boolean", Description = "Opens the node in a dialog")] public bool ShowOpen { get; set; } - [ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "Selecting this option allows a user to choose nodes that they normally don't have access to.", "boolean")] + [ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + "Ignore User Start Nodes", "boolean", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } } } diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs index 8b6a4c15d2..16aff6e0bf 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs @@ -11,7 +11,9 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("maxNumber", "Maximum number of items", "number")] public int MaxNumber { get; set; } - [ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "Selecting this option allows a user to choose nodes that they normally don't have access to.", "boolean")] + [ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + "Ignore User Start Nodes", "boolean", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } diff --git a/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs b/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs index 30d3ee2113..bd153c8e2f 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs @@ -15,7 +15,9 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("hideLabel", "Hide Label", "boolean")] public bool HideLabel { get; set; } - [ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "Selecting this option allows a user to choose nodes that they normally don't have access to.", "boolean")] + [ConfigurationField(Core.Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + "Ignore User Start Nodes", "boolean", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } } } diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 091c3a8f8d..2ee3d07c83 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -88,9 +88,10 @@ namespace Umbraco.Web.Trees /// /// internal TreeNode GetSingleTreeNodeWithAccessCheck(IEntitySlim e, string parentId, FormDataCollection queryStrings, - int[] startNodeIds, string[] startNodePaths, bool ignoreUserStartNodes) + int[] startNodeIds, string[] startNodePaths) { var entityIsAncestorOfStartNodes = ContentPermissionsHelper.IsInBranchOfStartNode(e.Path, startNodeIds, startNodePaths, out var hasPathAccess); + var ignoreUserStartNodes = IgnoreUserStartNodes(queryStrings); if (ignoreUserStartNodes == false && entityIsAncestorOfStartNodes == false) return null; @@ -101,7 +102,7 @@ namespace Umbraco.Web.Trees //the node so we need to return null; return null; } - if (ignoreUserStartNodes == false && hasPathAccess == false) + if (!ignoreUserStartNodes && !hasPathAccess) { treeNode.AdditionalData["noAccess"] = true; } @@ -182,7 +183,7 @@ namespace Umbraco.Web.Trees //get the current user start node/paths GetUserStartNodes(out var userStartNodes, out var userStartNodePaths); - nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths, ignoreUserStartNodes)).Where(x => x != null)); + nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths)).Where(x => x != null)); // if the user does not have access to the root node, what we have is the start nodes, // but to provide some context we also need to add their topmost nodes when they are not @@ -193,7 +194,7 @@ namespace Umbraco.Web.Trees if (topNodeIds.Length > 0) { var topNodes = Services.EntityService.GetAll(UmbracoObjectType, topNodeIds.ToArray()); - nodes.AddRange(topNodes.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths, ignoreUserStartNodes)).Where(x => x != null)); + nodes.AddRange(topNodes.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings, userStartNodes, userStartNodePaths)).Where(x => x != null)); } } @@ -217,7 +218,7 @@ namespace Umbraco.Web.Trees { // try to parse id as an integer else use GetEntityFromId // which will grok Guids, Udis, etc and let use obtain the id - if (int.TryParse(id, out var entityId) == false) + if (!int.TryParse(id, out var entityId)) { var entity = GetEntityFromId(id); if (entity == null) @@ -226,11 +227,13 @@ namespace Umbraco.Web.Trees entityId = entity.Id; } + var ignoreUserStartNodes = IgnoreUserStartNodes(queryStrings); + IEntitySlim[] result; // if a request is made for the root node but user has no access to // root node, return start nodes instead - if (entityId == Constants.System.Root && UserStartNodes.Contains(Constants.System.Root) == false) + if (!ignoreUserStartNodes && entityId == Constants.System.Root && UserStartNodes.Contains(Constants.System.Root) == false) { result = UserStartNodes.Length > 0 ? Services.EntityService.GetAll(UmbracoObjectType, UserStartNodes).ToArray() @@ -535,6 +538,8 @@ namespace Umbraco.Web.Trees private readonly ConcurrentDictionary _entityCache = new ConcurrentDictionary(); + private bool? _ignoreUserStartNodes; + /// /// If the request should allows a user to choose nodes that they normally don't have access to /// @@ -542,13 +547,12 @@ namespace Umbraco.Web.Trees /// internal bool IgnoreUserStartNodes(FormDataCollection queryStrings) { - var dataTypeId = queryStrings.GetValue(TreeQueryStringParameters.DataTypeId); - if (dataTypeId.HasValue) - { - return Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); - } + if (_ignoreUserStartNodes.HasValue) return _ignoreUserStartNodes.Value; - return false; + var dataTypeId = queryStrings.GetValue(TreeQueryStringParameters.DataTypeId); + _ignoreUserStartNodes = dataTypeId.HasValue ? Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value) : false; + + return _ignoreUserStartNodes.Value; } } } From 1c5ed58074c228bfbd41762d192ee9152e1ae635 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 1 Jul 2019 18:54:39 +1000 Subject: [PATCH 069/776] gets media picker working with ignore user start nodes --- .../src/common/resources/entity.resource.js | 5 ++++- .../infiniteeditors/mediapicker/mediapicker.controller.js | 2 +- .../views/propertyeditors/grid/editors/media.controller.js | 2 +- .../propertyeditors/mediapicker/mediapicker.controller.js | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index eee877a60c..189732bf3a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -301,7 +301,10 @@ function entityResource($q, $http, umbRequestHelper) { * */ getAncestors: function (id, type, culture, options) { - if (culture === undefined) culture = ""; + if (!culture) { + culture = ""; + } + var args = [ { id: id }, { type: type }, diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index 015a452e67..f95a8ec974 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -160,7 +160,7 @@ angular.module("umbraco") } if (folder.id > 0) { - entityResource.getAncestors(folder.id, "media", { dataTypeId: $scope.model.dataTypeId }) + entityResource.getAncestors(folder.id, "media", null, { dataTypeId: $scope.model.dataTypeId }) .then(function (anc) { $scope.path = _.filter(anc, function (f) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index 2d8e15ea57..7defe51d29 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -7,7 +7,7 @@ angular.module("umbraco") if (!$scope.model.config.startNodeId) { - if ($scope.model.config.ignoreUserStartNodes === "1" ) { + if ($scope.model.config.ignoreUserStartNodes === true) { $scope.model.config.startNodeId = -1; $scope.model.config.startNodeIsVirtual = true; 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 c32730a2ac..8fd50387c0 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 @@ -110,7 +110,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl userService.getCurrentUser().then(function (userData) { if (!$scope.model.config.startNodeId) { - if ($scope.model.config.ignoreUserStartNodes === "1") { + if ($scope.model.config.ignoreUserStartNodes === true) { $scope.model.config.startNodeId = -1; $scope.model.config.startNodeIsVirtual = true; } From f01be090750acfad253ab145e79ccb050cf16ec2 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 1 Jul 2019 10:13:31 +0100 Subject: [PATCH 070/776] Update sourcelink & set from full to portable --- src/Umbraco.Core/Umbraco.Core.csproj | 4 ++-- src/Umbraco.Examine/Umbraco.Examine.csproj | 4 ++-- src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- src/Umbraco.Web/Umbraco.Web.csproj | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 3daeb37392..f2ae7b8d99 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -13,7 +13,7 @@ true - full + portable false bin\Debug\ TRACE;DEBUG @@ -56,7 +56,7 @@ - 1.0.0-beta2-19270-01 + 1.0.0-beta2-19324-01 runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index 14a57c3216..e4116154ae 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -13,7 +13,7 @@ true - full + portable false bin\Debug\ DEBUG;TRACE @@ -50,7 +50,7 @@ - 1.0.0-beta2-19270-01 + 1.0.0-beta2-19324-01 runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index b9a5890d57..39913991f2 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -14,7 +14,7 @@ true - full + portable false bin\Debug\ DEBUG;TRACE diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index ee19c27ad0..025aea0fb1 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -34,7 +34,7 @@ true - full + portable false bin\ TRACE;DEBUG @@ -102,7 +102,7 @@ - 1.0.0-beta2-19270-01 + 1.0.0-beta2-19324-01 runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 9bcc1da13d..2c8e8bc915 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -16,7 +16,7 @@ true - full + portable false bin\Debug\ DEBUG;TRACE @@ -81,7 +81,7 @@ - 1.0.0-beta2-19270-01 + 1.0.0-beta2-19324-01 runtime; build; native; contentfiles; analyzers; buildtransitive all From 0b64e34f05382ecb4c59f8e2ebe6753ca87c6262 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 1 Jul 2019 10:19:09 +0100 Subject: [PATCH 071/776] Add in SourceLink Nuget to Umbraco.ModelsBuilder csproj --- src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index 39913991f2..7c3af3312d 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -103,6 +103,11 @@ 2.8.0 + + 1.0.0-beta2-19324-01 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + From 760f6a1647df7e15ec4426ca7d31c84798a2a440 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 1 Jul 2019 10:54:29 +0100 Subject: [PATCH 072/776] Umbraco.Web.UI revert change as no real CS code to F11 into this project --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 025aea0fb1..b27f6aa335 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -34,7 +34,7 @@ true - portable + full false bin\ TRACE;DEBUG From b64f30353766470b6ffb05cc42645de99e3e943f Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 1 Jul 2019 11:20:07 +0100 Subject: [PATCH 073/776] Mark the release PDB as portable too - the Powershell build uses Release not Debug --- src/Umbraco.Core/Umbraco.Core.csproj | 2 +- src/Umbraco.Examine/Umbraco.Examine.csproj | 2 +- src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index f2ae7b8d99..facb7b0346 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -23,7 +23,7 @@ latest - pdbonly + portable true bin\Release\ TRACE diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index e4116154ae..95690c17e4 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -23,7 +23,7 @@ latest - pdbonly + portable true bin\Release\ TRACE diff --git a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj index 7c3af3312d..60ef944a8c 100644 --- a/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj +++ b/src/Umbraco.ModelsBuilder/Umbraco.ModelsBuilder.csproj @@ -22,7 +22,7 @@ 4 - pdbonly + portable true bin\Release\ TRACE diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 2c8e8bc915..5f076e60c3 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -26,7 +26,7 @@ latest - pdbonly + portable true bin\Release\ TRACE From 3effc66b2c6472334ad19731de0def60ed0b399e Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 1 Jul 2019 11:53:22 +0100 Subject: [PATCH 074/776] Need to update NuSpec to ensure the PDB are getting coppied into the bin when Nuget pacakeg installed into a project --- build/NuSpecs/UmbracoCms.Core.nuspec | 4 ++-- build/NuSpecs/UmbracoCms.Web.nuspec | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index d93cd4e241..fce15eb487 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -45,7 +45,7 @@ - + @@ -56,6 +56,6 @@ - + diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 4937c85466..4aa354eba2 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -44,7 +44,7 @@ - + @@ -59,7 +59,7 @@ - - + + From 503183331f56b1dbac04ab9256454f8364e7aa79 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 1 Jul 2019 20:59:41 +1000 Subject: [PATCH 075/776] gets media picker with ignore start nodes working in rte --- .../src/common/services/tinymce.service.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 603e723bec..d0d2d06479 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -1154,6 +1154,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s self.createLinkPicker(args.editor, function (currentTarget, anchorElement) { var linkPicker = { currentTarget: currentTarget, + dataTypeId: args.model.dataTypeId, anchors: getCurrentAnchorNames(), submit: function (model) { self.insertLinkInEditor(args.editor, model.target, anchorElement); @@ -1168,13 +1169,26 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s //Create the insert media plugin self.createMediaPicker(args.editor, function (currentTarget, userData) { + + if (!args.model.config.startNodeId) { + if (args.model.config.ignoreUserStartNodes === true) { + args.model.config.startNodeId = -1; + args.model.config.startNodeIsVirtual = true; + } + else { + args.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + args.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; + } + } + var mediaPicker = { currentTarget: currentTarget, onlyImages: true, showDetails: true, disableFolderSelect: true, - startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], - startNodeIsVirtual: userData.startMediaIds.length !== 1, + startNodeId: args.model.config.startNodeId, + startNodeIsVirtual: args.model.config.startNodeIsVirtual, + dataTypeId: args.model.dataTypeId, submit: function (model) { self.insertMediaInEditor(args.editor, model.selection[0]); editorService.close(); From 0a6237af042b0dd353da25c07ab55fed3c8a9319 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 1 Jul 2019 21:13:11 +1000 Subject: [PATCH 076/776] Gets the multi url picker working with ignore start nodes --- .../src/common/services/tinymce.service.js | 13 +++++++------ .../linkpicker/linkpicker.controller.js | 16 ++++++++++++++-- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index d0d2d06479..3355b15570 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -1170,14 +1170,15 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s //Create the insert media plugin self.createMediaPicker(args.editor, function (currentTarget, userData) { + var startNodeId, startNodeIsVirtual; if (!args.model.config.startNodeId) { if (args.model.config.ignoreUserStartNodes === true) { - args.model.config.startNodeId = -1; - args.model.config.startNodeIsVirtual = true; + startNodeId = -1; + startNodeIsVirtual = true; } else { - args.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; - args.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1; + startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + startNodeIsVirtual = userData.startMediaIds.length !== 1; } } @@ -1186,8 +1187,8 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s onlyImages: true, showDetails: true, disableFolderSelect: true, - startNodeId: args.model.config.startNodeId, - startNodeIsVirtual: args.model.config.startNodeIsVirtual, + startNodeId: startNodeId, + startNodeIsVirtual: startNodeIsVirtual, dataTypeId: args.model.dataTypeId, submit: function (model) { self.insertMediaInEditor(args.editor, model.selection[0]); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js index d1743baf10..f09acbcc6a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js @@ -161,9 +161,21 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", $scope.switchToMediaPicker = function () { userService.getCurrentUser().then(function (userData) { + + var startNodeId, startNodeIsVirtual; + if (dialogOptions.ignoreUserStartNodes === true) { + startNodeId = -1; + startNodeIsVirtual = true; + } + else { + startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + startNodeIsVirtual = userData.startMediaIds.length !== 1; + } + var mediaPicker = { - startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], - startNodeIsVirtual: userData.startMediaIds.length !== 1, + startNodeId: startNodeId, + startNodeIsVirtual: startNodeIsVirtual, + dataTypeId: dialogOptions.dataTypeId, submit: function (model) { var media = model.selection[0]; From 640d2cedb9c79a99517d9fd503e1361e6844b18f Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 1 Jul 2019 21:48:45 +1000 Subject: [PATCH 077/776] Gets the link and media pickers working in the grid in the rte with ignore start nodes --- .../directives/components/grid/grid.rte.directive.js | 12 +++++++++++- .../src/common/services/tinymce.service.js | 1 + .../src/views/propertyeditors/grid/editors/rte.html | 4 +++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js index dee3cfdab7..8e561ff5ca 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js @@ -4,7 +4,9 @@ angular.module("umbraco.directives") scope: { uniqueId: '=', value: '=', - configuration: "=" + configuration: "=", //this is the RTE configuration + datatypeId: '@', + ignoreUserStartNodes: '@' }, templateUrl: 'views/components/grid/grid-rte.html', replace: true, @@ -35,6 +37,14 @@ angular.module("umbraco.directives") editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize; } + //ensure the grid's global config is being passed up to the RTE, these 2 properties need to be in this format + //since below we are just passing up `scope` as the actual model and for 2 way binding to work with `value` that + //is the way it needs to be unless we start adding watchers. We'll just go with this for now but it's super ugly. + scope.config = { + ignoreUserStartNodes: scope.ignoreUserStartNodes === "true" + } + scope.dataTypeId = scope.datatypeId; //Yes - this casing is rediculous, but it's because the var starts with `data` so it can't be `data-type-id` :/ + //stores a reference to the editor var tinyMceEditor = null; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 3355b15570..19e3d3d970 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -1155,6 +1155,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s var linkPicker = { currentTarget: currentTarget, dataTypeId: args.model.dataTypeId, + ignoreUserStartNodes: args.model.config.ignoreUserStartNodes, anchors: getCurrentAnchorNames(), submit: function (model) { self.insertLinkInEditor(args.editor, model.target, anchorElement); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.html index 7741bcadff..63bc7570e1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.html @@ -3,7 +3,9 @@ + unique-id="control.$uniqueId" + datatype-id="{{model.dataTypeId}}" + ignore-user-start-nodes="{{model.config.ignoreUserStartNodes}}"> From 09793a17448b0afff9fce7c61983b305d3275aee Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 2 Jul 2019 11:07:59 +1000 Subject: [PATCH 078/776] Fixes tests --- src/Umbraco.Core/Constants-DataTypes.cs | 1 + .../Migrations/Install/DatabaseDataCreator.cs | 6 +++--- .../Web/Controllers/ContentControllerTests.cs | 13 +++++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs index 65c5fc4ee7..673da8f9a3 100644 --- a/src/Umbraco.Core/Constants-DataTypes.cs +++ b/src/Umbraco.Core/Constants-DataTypes.cs @@ -19,6 +19,7 @@ namespace Umbraco.Core public const int Textarea = -89; public const int Textbox = -88; + public const int RichtextEditor = -87; public const int Boolean = -49; public const int DateTime = -36; public const int DropDownSingle = -39; diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index b783973f4c..dd5c17713a 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -109,8 +109,8 @@ namespace Umbraco.Core.Migrations.Install InsertDataTypeNodeDto(Constants.DataTypes.LabelDecimal, 39, Constants.DataTypes.Guids.LabelDecimal, "Label (decimal)"); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Upload, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Upload}", SortOrder = 34, UniqueId = Constants.DataTypes.Guids.UploadGuid, Text = "Upload", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Textarea, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Textarea}", SortOrder = 33, UniqueId = Constants.DataTypes.Guids.TextareaGuid, Text = "Textarea", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -88, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-88", SortOrder = 32, UniqueId = Constants.DataTypes.Guids.TextstringGuid, Text = "Textstring", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); - _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -87, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-87", SortOrder = 4, UniqueId = Constants.DataTypes.Guids.RichtextEditorGuid, Text = "Richtext editor", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Textbox, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Textbox}", SortOrder = 32, UniqueId = Constants.DataTypes.Guids.TextstringGuid, Text = "Textstring", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); + _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.RichtextEditor, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.RichtextEditor}", SortOrder = 4, UniqueId = Constants.DataTypes.Guids.RichtextEditorGuid, Text = "Richtext editor", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -51, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-51", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.NumericGuid, Text = "Numeric", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = Constants.DataTypes.Boolean, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = $"-1,{Constants.DataTypes.Boolean}", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.CheckboxGuid, Text = "True/false", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); _database.Insert(Constants.DatabaseSchema.Tables.Node, "id", false, new NodeDto { NodeId = -43, Trashed = false, ParentId = -1, UserId = -1, Level = 1, Path = "-1,-43", SortOrder = 2, UniqueId = Constants.DataTypes.Guids.CheckboxListGuid, Text = "Checkbox list", NodeObjectType = Constants.ObjectTypes.DataType, CreateDate = DateTime.Now }); @@ -270,7 +270,7 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -51, EditorAlias = Constants.PropertyEditors.Aliases.Integer, DbType = "Integer" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -87, EditorAlias = Constants.PropertyEditors.Aliases.TinyMce, DbType = "Ntext", Configuration = "{\"value\":\",code,undo,redo,cut,copy,mcepasteword,stylepicker,bold,italic,bullist,numlist,outdent,indent,mcelink,unlink,mceinsertanchor,mceimage,umbracomacro,mceinserttable,umbracoembed,mcecharmap,|1|1,2,3,|0|500,400|1049,|true|\"}" }); - _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = -88, EditorAlias = Constants.PropertyEditors.Aliases.TextBox, DbType = "Nvarchar" }); + _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.Textbox, EditorAlias = Constants.PropertyEditors.Aliases.TextBox, DbType = "Nvarchar" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.Textarea, EditorAlias = Constants.PropertyEditors.Aliases.TextArea, DbType = "Ntext" }); _database.Insert(Constants.DatabaseSchema.Tables.DataType, "pk", false, new DataTypeDto { NodeId = Constants.DataTypes.Upload, EditorAlias = Constants.PropertyEditors.Aliases.UploadField, DbType = "Nvarchar" }); InsertDataTypeDto(Constants.DataTypes.LabelString, Constants.PropertyEditors.Aliases.Label, "Nvarchar", "{\"umbracoDataValueType\":\"STRING\"}"); diff --git a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs index a4213b4f0e..d77867152a 100644 --- a/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/ContentControllerTests.cs @@ -77,6 +77,19 @@ namespace Umbraco.Tests.Web.Controllers var entityService = new Mock(); entityService.Setup(x => x.GetAllPaths(UmbracoObjectTypes.Document, It.IsAny())) .Returns((UmbracoObjectTypes objType, int[] ids) => ids.Select(x => new TreeEntityPath { Path = $"-1,{x}", Id = x }).ToList()); + entityService.Setup(x => x.GetKey(It.IsAny(), UmbracoObjectTypes.DataType)) + .Returns((int id, UmbracoObjectTypes objType) => + { + switch (id) + { + case Constants.DataTypes.Textbox: + return Attempt.Succeed(Constants.DataTypes.Guids.TextstringGuid); + case Constants.DataTypes.RichtextEditor: + return Attempt.Succeed(Constants.DataTypes.Guids.RichtextEditorGuid); + } + return Attempt.Fail(); + }); + var dataTypeService = new Mock(); dataTypeService.Setup(service => service.GetDataType(It.IsAny())) From f81588bb13ce724639400696070326b6d5e24dc8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 2 Jul 2019 11:48:09 +1000 Subject: [PATCH 079/776] fixes search for content picker with ignore start nodes --- .../views/common/infiniteeditors/treepicker/treepicker.html | 2 +- .../propertyeditors/contentpicker/contentpicker.controller.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html index f8dd60bfad..01c14a284f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html @@ -36,7 +36,7 @@ search-from-id="{{vm.searchInfo.searchFromId}}" search-from-name="{{vm.searchInfo.searchFromName}}" show-search="{{vm.searchInfo.showSearch}}" - datatype-id="{{searchInfo.dataTypeId}}" + datatype-id="{{vm.searchInfo.dataTypeId}}" section="{{vm.section}}"> diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 8a4020d5f9..7fafa20b0f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -139,7 +139,8 @@ function contentPickerController($scope, entityResource, editorState, iconHelper multiPicker: $scope.model.config.multiPicker, entityType: entityType, filterCssClass: "not-allowed not-published", - startNodeId: null, + startNodeId: null, + dataTypeId: $scope.model.dataTypeId, currentNode: editorState ? editorState.current : null, callback: function (data) { if (angular.isArray(data)) { From 4134dd16df02151ced2bb566c284b87b9ec8ec15 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 2 Jul 2019 12:04:58 +1000 Subject: [PATCH 080/776] adds fixme note --- src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs index 8340d24032..57a06c2175 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs @@ -51,6 +51,8 @@ namespace Umbraco.Web.Models.Mapping dest.PropertyEditor = editor; dest.Editor = editor.Alias; + //fixme: although this might get cached, if a content item has 100 properties of different data types, then this means this is going to be 100 extra DB queries :( :( :( + // - ideally, we'd just have the DataTypeKey alongside the DataTypeId which is loaded in the single sql statement which should be relatively easy. var dataTypeKey = _entityService.GetKey(property.PropertyType.DataTypeId, UmbracoObjectTypes.DataType); if (!dataTypeKey.Success) throw new InvalidOperationException("Can't get the unique key from the id: " + property.PropertyType.DataTypeId); From 4ed746e4ef1d01174db393c363ca522db093351c Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 2 Jul 2019 13:37:40 +1000 Subject: [PATCH 081/776] Fixes tree refreshing when changing users, fixes memory/event leak --- .../components/tree/umbtree.directive.js | 4 ---- .../src/common/services/navigation.service.js | 2 +- .../src/controllers/main.controller.js | 11 +++++++++-- .../src/controllers/navigation.controller.js | 16 ++++++++++++---- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js index 4c7bd2f477..2bd93a4b27 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtree.directive.js @@ -92,10 +92,6 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use } } - // TODO: This isn't used!? - function clearCache(section) { - treeService.clearCache({ section: section }); - } /** * Re-loads the tree with the updated parameters diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index e51b7b818e..a36e1a7633 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -349,7 +349,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService reloadSection: function(sectionAlias) { return navReadyPromise.promise.then(function () { - mainTreeApi.clearCache({ section: sectionAlias }); + treeService.clearCache({ section: sectionAlias }); return mainTreeApi.load(sectionAlias); }); }, diff --git a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js index 83b3c920d7..654bbb1d03 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js @@ -9,8 +9,8 @@ * */ function MainController($scope, $location, appState, treeService, notificationsService, - userService, historyService, updateChecker, assetsService, eventsService, - tmhDynamicLocale, localStorageService, editorService, overlayService, focusService) { + userService, historyService, updateChecker, navigationService, eventsService, + tmhDynamicLocale, localStorageService, editorService, overlayService) { //the null is important because we do an explicit bool check on this in the view $scope.authenticated = null; @@ -105,6 +105,13 @@ function MainController($scope, $location, appState, treeService, notificationsS //if the user has changed we need to redirect to the root so they don't try to continue editing the //last item in the URL (NOTE: the user id can equal zero, so we cannot just do !data.lastUserId since that will resolve to true) if (data.lastUserId !== undefined && data.lastUserId !== null && data.lastUserId !== data.user.id) { + + var section = appState.getSectionState("currentSection"); + if (section) { + //if there's a section already assigned, reload it so the tree is cleared + navigationService.reloadSection(section); + } + $location.path("/").search(""); historyService.removeAll(); treeService.clearCache(); diff --git a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js index c426d0d955..e4c94f3c66 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/navigation.controller.js @@ -140,8 +140,9 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar //// TODO: remove this it's not a thing //$scope.selectedId = navigationService.currentId; + var isInit = false; var evts = []; - + //Listen for global state changes evts.push(eventsService.on("appState.globalState.changed", function (e, args) { if (args.key === "showNavigation") { @@ -236,8 +237,10 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar })); //when the application is ready and the user is authorized, setup the data + //this will occur anytime a new user logs in! evts.push(eventsService.on("app.ready", function (evt, data) { - init(); + $scope.authenticated = true; + ensureInit(); })); // event for infinite editors @@ -305,9 +308,14 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar /** * Called when the app is ready and sets up the navigation (should only be called once) */ - function init() { + function ensureInit() { - $scope.authenticated = true; + //only run once ever! + if (isInit) { + return; + } + + isInit = true; var navInit = false; From c414dd06ccd5f0d16e3f75eef0554b3f6d9e0f02 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 2 Jul 2019 15:38:17 +1000 Subject: [PATCH 082/776] updates package lock --- src/Umbraco.Web.UI.Client/package-lock.json | 75 +++++++++++++-------- 1 file changed, 47 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 5622322fda..be1582f1e4 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -1224,7 +1224,7 @@ "arraybuffer.slice": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha1-O7xCdd1YTMGxCAm4nU6LY6aednU=", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", "dev": true }, "asap": { @@ -1286,7 +1286,7 @@ "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha1-ePrtjD0HSrgfIrTphdeehzj3IPg=", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", "dev": true }, "asynckit": { @@ -1538,7 +1538,7 @@ "bl": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha1-oWCRFxcQPAdBDO9j71Gzl8Alr5w=", + "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "dev": true, "optional": true, "requires": { @@ -2535,7 +2535,7 @@ "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", "dev": true }, "continuable-cache": { @@ -4150,7 +4150,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -4186,7 +4186,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -4581,7 +4581,7 @@ "eventemitter3": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", - "integrity": "sha1-CQtNbNvWRe0Qv3UNS1QHlC17oWM=", + "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", "dev": true }, "exec-buffer": { @@ -5418,7 +5418,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -5439,12 +5440,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5459,17 +5462,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5586,7 +5592,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5598,6 +5605,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5612,6 +5620,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5619,12 +5628,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -5643,6 +5654,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5723,7 +5735,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5735,6 +5748,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5820,7 +5834,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -5856,6 +5871,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5875,6 +5891,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5918,12 +5935,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -7682,7 +7701,7 @@ "has-binary2": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha1-d3asYn8+p3JQz8My2rfd9eT10R0=", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", "dev": true, "requires": { "isarray": "2.0.1" @@ -7885,7 +7904,7 @@ "http-proxy": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", - "integrity": "sha1-etOElGWPhGBeL220Q230EPTlvpo=", + "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", "dev": true, "requires": { "eventemitter3": "^3.0.0", @@ -9941,9 +9960,9 @@ "dev": true }, "nouislider": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-12.1.0.tgz", - "integrity": "sha512-SAOabF6hBm8201c6LDbkVOVhgwY49+/ms72ZLUF2qkN5RCf7FfUvEh/hGZ7XcwZHU+I/grlicPmcSk1/rrMnOw==" + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.0.1.tgz", + "integrity": "sha512-YNLKuABWYxmC5WXJ9TUj3N7+iyL/xT3+jm1mgOMXoqBhAL0Pj9BMgyKmLgwRnrxNN+C/fe7sFmpQDDPsxbMT2w==" }, "npm": { "version": "6.4.1", @@ -13741,7 +13760,7 @@ "qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha1-xF6cYYAL0IfviNfiVkI73Unl0HE=", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", "dev": true }, "qs": { @@ -14919,7 +14938,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -16022,7 +16041,7 @@ "type-is": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha1-+JzjQVQcZysl7nrjxz3uOyvlAZQ=", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", "dev": true, "requires": { "media-typer": "0.3.0", @@ -16064,7 +16083,7 @@ "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha1-n+FTahCmZKZSZqHjzPhf02MCvJw=", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", "dev": true }, "unc-path-regex": { @@ -16677,7 +16696,7 @@ "ws": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha1-8c+E/i1ekB686U767OeF8YeiKPI=", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", "dev": true, "requires": { "async-limiter": "~1.0.0", From 3d3647b476aa9b78cdf2ef1fb79f7f2be2747cce Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 2 Jul 2019 10:11:51 +0200 Subject: [PATCH 083/776] Fix tests --- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 2d2cbaa3fd..7e72a5aefb 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -248,6 +248,11 @@ namespace Umbraco.Tests.Testing // register empty content apps collection Composition.WithCollectionBuilder(); + + // manifest + Composition.ManifestValueValidators(); + Composition.ManifestFilters(); + } protected virtual void ComposeMapper(bool configure) From 3c0565e516e1a26f42e93128e2207dd48e811259 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 2 Jul 2019 10:56:44 +0200 Subject: [PATCH 084/776] Bugfix: Null ref-exception when using member pickers. --- src/Umbraco.Web/Editors/EntityController.cs | 47 ++++++++------------- 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 029132c51b..85d5b607b6 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -442,16 +442,7 @@ namespace Umbraco.Web.Editors { //TODO: Need to check for Object types that support hierarchy here, some might not. - int[] startNodes = null; - switch (type) - { - case UmbracoEntityTypes.Document: - startNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); - break; - case UmbracoEntityTypes.Media: - startNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); - break; - } + var startNodes = GetStartNodes(type); var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); @@ -578,16 +569,7 @@ namespace Umbraco.Web.Editors IEnumerable entities; long totalRecords; - int[] startNodes = null; - switch (type) - { - case UmbracoEntityTypes.Document: - startNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); - break; - case UmbracoEntityTypes.Media: - startNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); - break; - } + var startNodes = GetStartNodes(type); var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); @@ -637,6 +619,20 @@ namespace Umbraco.Web.Editors } } + private int[] GetStartNodes(UmbracoEntityTypes type) + { + switch (type) + { + case UmbracoEntityTypes.Document: + return Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + case UmbracoEntityTypes.Media: + return Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + default: + return new int[0]; + } + + } + public PagedResult GetPagedDescendants( int id, @@ -663,16 +659,7 @@ namespace Umbraco.Web.Editors { // root is special: we reduce it to start nodes - int[] aids = null; - switch (type) - { - case UmbracoEntityTypes.Document: - aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); - break; - case UmbracoEntityTypes.Media: - aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); - break; - } + int[] aids = GetStartNodes(type); var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes From 1a437c1c6abe77ef5edd337bbbdb169edde5f41e Mon Sep 17 00:00:00 2001 From: Dave Woestenborghs Date: Mon, 1 Jul 2019 10:26:47 +0200 Subject: [PATCH 085/776] #5750 - fix outside click directive so it doesn't close dialogues from create action --- .../common/directives/components/events/events.directive.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js index abee5e2358..47e6818466 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js @@ -131,6 +131,12 @@ angular.module('umbraco.directives') return; } + // ignore clicks on dialog actions + var actions = $(event.target).parents(".umb-action"); + if (actions.length === 1) { + return; + } + //ignore clicks inside this element if ($(element).has($(event.target)).length > 0) { return; From 7b0979f9fda8c23f3970e83ff4290bd9e8c73f8a Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 2 Jul 2019 19:15:04 +1000 Subject: [PATCH 086/776] Fixes install.ps1 and updates sln to remove old ps1 file --- build/NuSpecs/tools/install.ps1 | 25 +++++++++++++++++++++---- src/umbraco.sln | 1 - 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/build/NuSpecs/tools/install.ps1 b/build/NuSpecs/tools/install.ps1 index 1411cb5c97..0be28f9467 100644 --- a/build/NuSpecs/tools/install.ps1 +++ b/build/NuSpecs/tools/install.ps1 @@ -18,9 +18,20 @@ if ($project) { # Copy umbraco and umbraco_files from package to project folder $umbracoFolder = Join-Path $projectPath "Umbraco" - New-Item -ItemType Directory -Force -Path $umbracoFolder + New-Item -ItemType Directory -Force -Path $umbracoFolder $umbracoFolderSource = Join-Path $installPath "UmbracoFiles\Umbraco" - robocopy $umbracoFolderSource $umbracoFolder /is /it /e /xf UI.xml /LOG:$copyLogsPath\UmbracoCopy.log + + Write-Host "copying files to $umbracoFolder ..." + # see https://support.microsoft.com/en-us/help/954404/return-codes-that-are-used-by-the-robocopy-utility-in-windows-server-2 + robocopy $umbracoFolderSource $umbracoFolder /is /it /e + if (($lastexitcode -eq 1) -or ($lastexitcode -eq 3) -or ($lastexitcode -eq 5) -or ($lastexitcode -eq 7)) + { + write-host "Copy succeeded!" + } + else + { + write-host "Copy failed with exit code:" $lastexitcode + } $copyWebconfig = $true $destinationWebConfig = Join-Path $projectPath "Web.config" @@ -40,7 +51,11 @@ if ($project) { } } } - Catch { } + Catch + { + Write-Host "An error occurred:" + Write-Host $_ + } } if($copyWebconfig -eq $true) @@ -74,7 +89,9 @@ if ($project) { } Catch { - # Not a big problem if this fails, let it go + # Not a big problem if this fails, let it go + # Write-Host "An error occurred:" + # Write-Host $_ } } diff --git a/src/umbraco.sln b/src/umbraco.sln index 7313e06e49..39b757f88c 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -70,7 +70,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{E3F9F378 ProjectSection(SolutionItems) = preProject ..\build\NuSpecs\tools\applications.config.install.xdt = ..\build\NuSpecs\tools\applications.config.install.xdt ..\build\NuSpecs\tools\ClientDependency.config.install.xdt = ..\build\NuSpecs\tools\ClientDependency.config.install.xdt - ..\build\NuSpecs\tools\install.core.ps1 = ..\build\NuSpecs\tools\install.core.ps1 ..\build\NuSpecs\tools\install.ps1 = ..\build\NuSpecs\tools\install.ps1 ..\build\NuSpecs\tools\Readme.txt = ..\build\NuSpecs\tools\Readme.txt ..\build\NuSpecs\tools\ReadmeUpgrade.txt = ..\build\NuSpecs\tools\ReadmeUpgrade.txt From e6484687e51f06171e16a66827ef0ac326ad24de Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 2 Jul 2019 11:17:23 +0200 Subject: [PATCH 087/776] Small fixes after v7 to v8 merge regarding the bypass security feature --- src/Umbraco.Web.UI.Client/package-lock.json | 400 +++++------------- .../src/common/resources/entity.resource.js | 17 + .../src/common/services/tinymce.service.js | 71 +--- .../linkpicker/linkpicker.controller.js | 2 +- .../grid/editors/media.controller.js | 2 +- src/Umbraco.Web/Editors/EntityController.cs | 50 ++- src/Umbraco.Web/Models/AnchorsModel.cs | 7 + src/Umbraco.Web/Umbraco.Web.csproj | 1 + 8 files changed, 181 insertions(+), 369 deletions(-) create mode 100644 src/Umbraco.Web/Models/AnchorsModel.cs diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index be1582f1e4..3e8f053bc6 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -955,7 +955,7 @@ }, "ansi-escapes": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", "dev": true }, @@ -1104,7 +1104,6 @@ "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-3.2.0.tgz", "integrity": "sha1-nNnABpV+vpX62tW9YJiUKoE3N/Y=", "dev": true, - "optional": true, "requires": { "file-type": "^3.1.0" }, @@ -1113,8 +1112,7 @@ "version": "3.9.0", "resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", - "dev": true, - "optional": true + "dev": true } } }, @@ -1178,7 +1176,7 @@ "array-slice": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "integrity": "sha1-42jqFfibxwaff/uJrsOmx9SsItQ=", "dev": true }, "array-sort": { @@ -1540,7 +1538,6 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "dev": true, - "optional": true, "requires": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" @@ -1550,15 +1547,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true + "dev": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1574,7 +1569,6 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -1763,8 +1757,7 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true, - "optional": true + "dev": true }, "buffer-fill": { "version": "1.0.0", @@ -1783,7 +1776,6 @@ "resolved": "https://registry.npmjs.org/buffer-to-vinyl/-/buffer-to-vinyl-1.1.0.tgz", "integrity": "sha1-APFfruOreh3aLN5tkSG//dB7ImI=", "dev": true, - "optional": true, "requires": { "file-type": "^3.1.0", "readable-stream": "^2.0.2", @@ -1795,22 +1787,19 @@ "version": "3.9.0", "resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", - "dev": true, - "optional": true + "dev": true }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true + "dev": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1826,7 +1815,6 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -1835,15 +1823,13 @@ "version": "2.0.3", "resolved": "http://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", - "dev": true, - "optional": true + "dev": true }, "vinyl": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, - "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -1964,8 +1950,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", - "dev": true, - "optional": true + "dev": true }, "caseless": { "version": "0.12.0", @@ -1978,7 +1963,6 @@ "resolved": "https://registry.npmjs.org/caw/-/caw-1.2.0.tgz", "integrity": "sha1-/7Im/n78VHKI3GLuPpcHPCEtEDQ=", "dev": true, - "optional": true, "requires": { "get-proxy": "^1.0.1", "is-obj": "^1.0.0", @@ -1990,8 +1974,7 @@ "version": "0.4.3", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", - "dev": true, - "optional": true + "dev": true } } }, @@ -2266,8 +2249,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/co/-/co-3.1.0.tgz", "integrity": "sha1-TqVOpaCJOBUxheFSEMaNkJK8G3g=", - "dev": true, - "optional": true + "dev": true }, "coa": { "version": "2.0.1", @@ -2391,7 +2373,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", "dev": true, - "optional": true, "requires": { "graceful-readlink": ">= 1.0.0" } @@ -2455,7 +2436,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -2578,7 +2559,7 @@ "core-js": { "version": "2.5.7", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", + "integrity": "sha1-+XJgj/DOrWi4QaFqky0LGDeRgU4=", "dev": true }, "core-util-is": { @@ -2604,7 +2585,6 @@ "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", "dev": true, - "optional": true, "requires": { "capture-stack-trace": "^1.0.0" } @@ -2859,7 +2839,6 @@ "resolved": "https://registry.npmjs.org/decompress/-/decompress-3.0.0.tgz", "integrity": "sha1-rx3VDQbjv8QyRh033hGzjA2ZG+0=", "dev": true, - "optional": true, "requires": { "buffer-to-vinyl": "^1.0.0", "concat-stream": "^1.4.6", @@ -2877,7 +2856,6 @@ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, - "optional": true, "requires": { "arr-flatten": "^1.0.1" } @@ -2886,15 +2864,13 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true, - "optional": true + "dev": true }, "braces": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, - "optional": true, "requires": { "expand-range": "^1.8.1", "preserve": "^0.2.0", @@ -2906,7 +2882,6 @@ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, - "optional": true, "requires": { "is-posix-bracket": "^0.1.0" } @@ -2916,7 +2891,6 @@ "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, - "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -2926,7 +2900,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, - "optional": true, "requires": { "inflight": "^1.0.4", "inherits": "2", @@ -2940,7 +2913,6 @@ "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-5.3.5.tgz", "integrity": "sha1-pVZlqajM3EGRWofHAeMtTgFvrSI=", "dev": true, - "optional": true, "requires": { "extend": "^3.0.0", "glob": "^5.0.3", @@ -2956,15 +2928,13 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true, - "optional": true + "dev": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -2976,15 +2946,13 @@ "version": "0.10.31", "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true, - "optional": true + "dev": true }, "through2": { "version": "0.6.5", "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, - "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -2996,22 +2964,19 @@ "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true, - "optional": true + "dev": true }, "is-extglob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true, - "optional": true + "dev": true }, "is-glob": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, - "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -3020,15 +2985,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true + "dev": true }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, - "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -3038,7 +3001,6 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, - "optional": true, "requires": { "arr-diff": "^2.0.0", "array-unique": "^0.2.1", @@ -3059,15 +3021,13 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "optional": true + "dev": true }, "ordered-read-streams": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz", "integrity": "sha1-cTfmmzKYuzQiR6G77jiByA4v14s=", "dev": true, - "optional": true, "requires": { "is-stream": "^1.0.1", "readable-stream": "^2.0.1" @@ -3078,7 +3038,6 @@ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -3094,7 +3053,6 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -3104,7 +3062,6 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, - "optional": true, "requires": { "is-utf8": "^0.2.0" } @@ -3114,7 +3071,6 @@ "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz", "integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=", "dev": true, - "optional": true, "requires": { "first-chunk-stream": "^1.0.0", "strip-bom": "^2.0.0" @@ -3125,7 +3081,6 @@ "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", "dev": true, - "optional": true, "requires": { "json-stable-stringify-without-jsonify": "^1.0.1", "through2-filter": "^3.0.0" @@ -3136,7 +3091,6 @@ "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", "dev": true, - "optional": true, "requires": { "through2": "~2.0.0", "xtend": "~4.0.0" @@ -3149,7 +3103,6 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, - "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -3161,7 +3114,6 @@ "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-2.4.4.tgz", "integrity": "sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=", "dev": true, - "optional": true, "requires": { "duplexify": "^3.2.0", "glob-stream": "^5.3.2", @@ -3189,7 +3141,6 @@ "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-3.1.0.tgz", "integrity": "sha1-IXx4n5uURQ76rcXF5TeXj8MzxGY=", "dev": true, - "optional": true, "requires": { "is-tar": "^1.0.0", "object-assign": "^2.0.0", @@ -3203,22 +3154,19 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz", "integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=", - "dev": true, - "optional": true + "dev": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -3231,7 +3179,6 @@ "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, - "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -3242,7 +3189,6 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", "dev": true, - "optional": true, "requires": { "clone": "^0.2.0", "clone-stats": "^0.0.1" @@ -3255,7 +3201,6 @@ "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-3.1.0.tgz", "integrity": "sha1-iyOTVoE1X58YnYclag+L3ZbZZm0=", "dev": true, - "optional": true, "requires": { "is-bzip2": "^1.0.0", "object-assign": "^2.0.0", @@ -3270,22 +3215,19 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz", "integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=", - "dev": true, - "optional": true + "dev": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -3298,7 +3240,6 @@ "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, - "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -3309,7 +3250,6 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", "dev": true, - "optional": true, "requires": { "clone": "^0.2.0", "clone-stats": "^0.0.1" @@ -3322,7 +3262,6 @@ "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-3.1.0.tgz", "integrity": "sha1-ssE9+YFmJomRtxXWRH9kLpaW9aA=", "dev": true, - "optional": true, "requires": { "is-gzip": "^1.0.0", "object-assign": "^2.0.0", @@ -3336,22 +3275,19 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz", "integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=", - "dev": true, - "optional": true + "dev": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -3364,7 +3300,6 @@ "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, - "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -3375,7 +3310,6 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", "dev": true, - "optional": true, "requires": { "clone": "^0.2.0", "clone-stats": "^0.0.1" @@ -3388,7 +3322,6 @@ "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-3.4.0.tgz", "integrity": "sha1-YUdbQVIGa74/7hL51inRX+ZHjus=", "dev": true, - "optional": true, "requires": { "is-zip": "^1.0.0", "read-all-stream": "^3.0.0", @@ -3404,7 +3337,6 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, - "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -3417,8 +3349,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "optional": true + "dev": true }, "deep-is": { "version": "0.1.3", @@ -3562,7 +3493,7 @@ "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "integrity": "sha1-XNAfwQFiG0LEzX9dGmYkNxbT850=", "dev": true, "requires": { "esutils": "^2.0.2" @@ -3637,7 +3568,6 @@ "resolved": "https://registry.npmjs.org/download/-/download-4.4.3.tgz", "integrity": "sha1-qlX9rTktldS2jowr4D4MKqIbqaw=", "dev": true, - "optional": true, "requires": { "caw": "^1.0.1", "concat-stream": "^1.4.7", @@ -3661,7 +3591,6 @@ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, - "optional": true, "requires": { "arr-flatten": "^1.0.1" } @@ -3670,15 +3599,13 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true, - "optional": true + "dev": true }, "braces": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, - "optional": true, "requires": { "expand-range": "^1.8.1", "preserve": "^0.2.0", @@ -3690,7 +3617,6 @@ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, - "optional": true, "requires": { "is-posix-bracket": "^0.1.0" } @@ -3700,7 +3626,6 @@ "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, - "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -3710,7 +3635,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, - "optional": true, "requires": { "inflight": "^1.0.4", "inherits": "2", @@ -3724,7 +3648,6 @@ "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-5.3.5.tgz", "integrity": "sha1-pVZlqajM3EGRWofHAeMtTgFvrSI=", "dev": true, - "optional": true, "requires": { "extend": "^3.0.0", "glob": "^5.0.3", @@ -3740,15 +3663,13 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true, - "optional": true + "dev": true }, "readable-stream": { "version": "1.0.34", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -3760,15 +3681,13 @@ "version": "0.10.31", "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true, - "optional": true + "dev": true }, "through2": { "version": "0.6.5", "resolved": "http://registry.npmjs.org/through2/-/through2-0.6.5.tgz", "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, - "optional": true, "requires": { "readable-stream": ">=1.0.33-1 <1.1.0-0", "xtend": ">=4.0.0 <4.1.0-0" @@ -3780,22 +3699,19 @@ "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true, - "optional": true + "dev": true }, "is-extglob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true, - "optional": true + "dev": true }, "is-glob": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, - "optional": true, "requires": { "is-extglob": "^1.0.0" } @@ -3804,15 +3720,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true + "dev": true }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, - "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -3822,7 +3736,6 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, - "optional": true, "requires": { "arr-diff": "^2.0.0", "array-unique": "^0.2.1", @@ -3843,15 +3756,13 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "optional": true + "dev": true }, "ordered-read-streams": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz", "integrity": "sha1-cTfmmzKYuzQiR6G77jiByA4v14s=", "dev": true, - "optional": true, "requires": { "is-stream": "^1.0.1", "readable-stream": "^2.0.1" @@ -3862,7 +3773,6 @@ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -3878,7 +3788,6 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -3888,7 +3797,6 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, - "optional": true, "requires": { "is-utf8": "^0.2.0" } @@ -3898,7 +3806,6 @@ "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz", "integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=", "dev": true, - "optional": true, "requires": { "first-chunk-stream": "^1.0.0", "strip-bom": "^2.0.0" @@ -3909,7 +3816,6 @@ "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", "dev": true, - "optional": true, "requires": { "json-stable-stringify-without-jsonify": "^1.0.1", "through2-filter": "^3.0.0" @@ -3920,7 +3826,6 @@ "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", "dev": true, - "optional": true, "requires": { "through2": "~2.0.0", "xtend": "~4.0.0" @@ -3933,7 +3838,6 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, - "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -3945,7 +3849,6 @@ "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-2.4.4.tgz", "integrity": "sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=", "dev": true, - "optional": true, "requires": { "duplexify": "^3.2.0", "glob-stream": "^5.3.2", @@ -3988,7 +3891,6 @@ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz", "integrity": "sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==", "dev": true, - "optional": true, "requires": { "end-of-stream": "^1.0.0", "inherits": "^2.0.1", @@ -4001,7 +3903,6 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, - "optional": true, "requires": { "once": "^1.4.0" } @@ -4010,15 +3911,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true + "dev": true }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -4028,7 +3927,6 @@ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -4044,7 +3942,6 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -4056,7 +3953,6 @@ "resolved": "https://registry.npmjs.org/each-async/-/each-async-1.1.1.tgz", "integrity": "sha1-3uUim98KtrogEqOV4bhpq/iBNHM=", "dev": true, - "optional": true, "requires": { "onetime": "^1.0.0", "set-immediate-shim": "^1.0.0" @@ -4066,8 +3962,7 @@ "version": "1.1.0", "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true, - "optional": true + "dev": true } } }, @@ -4368,7 +4263,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true, "optional": true } @@ -4463,7 +4358,7 @@ "eslint-scope": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "integrity": "sha1-UL8wcekzi83EMzF5Sgy1M/ATYXI=", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -4473,13 +4368,13 @@ "eslint-utils": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "integrity": "sha1-moUbqJ7nxGA0b5fPiTnHKYgn5RI=", "dev": true }, "eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "integrity": "sha1-PzGA+y4pEBdxastMnW1bXDSmqB0=", "dev": true }, "espree": { @@ -4502,7 +4397,7 @@ "esquery": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", "dev": true, "requires": { "estraverse": "^4.0.0" @@ -4511,7 +4406,7 @@ "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", "dev": true, "requires": { "estraverse": "^4.1.0" @@ -4796,7 +4691,7 @@ "fill-range": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha1-6x53OrsFbc2N8r/favWbizqTZWU=", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", "dev": true, "requires": { "is-number": "^2.1.0", @@ -5050,7 +4945,6 @@ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "dev": true, - "optional": true, "requires": { "pend": "~1.2.0" } @@ -5098,15 +4992,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz", "integrity": "sha1-5hz4BfDeHJhFZ9A4bcXfUO5a9+Q=", - "dev": true, - "optional": true + "dev": true }, "filenamify": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz", "integrity": "sha1-qfL/0RxQO+0wABUCknI3jx8TZaU=", "dev": true, - "optional": true, "requires": { "filename-reserved-regex": "^1.0.0", "strip-outer": "^1.0.0", @@ -5353,8 +5245,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=", - "dev": true, - "optional": true + "dev": true }, "fs-extra": { "version": "1.0.0", @@ -5978,7 +5869,6 @@ "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-1.1.0.tgz", "integrity": "sha1-iUhUSRvFkbDxR9euVw9cZ4tyVus=", "dev": true, - "optional": true, "requires": { "rc": "^1.1.2" } @@ -6140,7 +6030,7 @@ "global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=", "dev": true, "requires": { "global-prefix": "^1.0.1", @@ -6285,7 +6175,6 @@ "resolved": "http://registry.npmjs.org/got/-/got-5.7.1.tgz", "integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=", "dev": true, - "optional": true, "requires": { "create-error-class": "^3.0.1", "duplexer2": "^0.1.4", @@ -6309,7 +6198,6 @@ "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", "dev": true, - "optional": true, "requires": { "readable-stream": "^2.0.2" } @@ -6318,22 +6206,19 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "optional": true + "dev": true }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, - "optional": true, "requires": { "error-ex": "^1.2.0" } @@ -6343,7 +6228,6 @@ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -6359,7 +6243,6 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -6379,8 +6262,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true, - "optional": true + "dev": true }, "growly": { "version": "1.3.0", @@ -6697,7 +6579,6 @@ "resolved": "https://registry.npmjs.org/gulp-decompress/-/gulp-decompress-1.2.0.tgz", "integrity": "sha1-jutlpeAV+O2FMsr+KEVJYGJvDcc=", "dev": true, - "optional": true, "requires": { "archive-type": "^3.0.0", "decompress": "^3.0.0", @@ -6709,15 +6590,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true + "dev": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -6733,7 +6612,6 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -6743,7 +6621,7 @@ "gulp-eslint": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-5.0.0.tgz", - "integrity": "sha512-9GUqCqh85C7rP9120cpxXuZz2ayq3BZc85pCTuPJS03VQYxne0aWPIXWx6LSvsGPa3uRqtSO537vaugOh+5cXg==", + "integrity": "sha1-KiaECV93Syz3kxAmIHjFbMehK1I=", "dev": true, "requires": { "eslint": "^5.0.1", @@ -7351,7 +7229,6 @@ "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz", "integrity": "sha1-uG/zSdgBzrVuHZ59x7vLS33uYAw=", "dev": true, - "optional": true, "requires": { "convert-source-map": "^1.1.1", "graceful-fs": "^4.1.2", @@ -7364,15 +7241,13 @@ "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true, - "optional": true + "dev": true }, "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, - "optional": true, "requires": { "is-utf8": "^0.2.0" } @@ -7382,7 +7257,6 @@ "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, - "optional": true, "requires": { "clone": "^1.0.0", "clone-stats": "^0.0.1", @@ -8198,7 +8072,7 @@ "is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "integrity": "sha1-OV4a6EsR8mrReV5zwXN45IowFXY=", "dev": true, "requires": { "is-relative": "^1.0.0", @@ -8265,8 +8139,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-bzip2/-/is-bzip2-1.0.0.tgz", "integrity": "sha1-XuWOqlounIDiFAe+3yOuWsCRs/w=", - "dev": true, - "optional": true + "dev": true }, "is-callable": { "version": "1.1.4", @@ -8401,8 +8274,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz", "integrity": "sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM=", - "dev": true, - "optional": true + "dev": true }, "is-jpg": { "version": "1.0.1", @@ -8415,8 +8287,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-2.1.1.tgz", "integrity": "sha1-fUxXKDd+84bD4ZSpkRv1fG3DNec=", - "dev": true, - "optional": true + "dev": true }, "is-number": { "version": "3.0.0", @@ -8482,8 +8353,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", - "dev": true, - "optional": true + "dev": true }, "is-regex": { "version": "1.0.4", @@ -8497,7 +8367,7 @@ "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "integrity": "sha1-obtpNc6MXboei5dUubLcwCDiJg0=", "dev": true, "requires": { "is-unc-path": "^1.0.0" @@ -8506,15 +8376,14 @@ "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "integrity": "sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=", "dev": true }, "is-retry-allowed": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", - "dev": true, - "optional": true + "dev": true }, "is-stream": { "version": "1.1.0", @@ -8544,8 +8413,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-tar/-/is-tar-1.0.0.tgz", "integrity": "sha1-L2suF5LB9bs2UZrKqdZcDSb+hT0=", - "dev": true, - "optional": true + "dev": true }, "is-typedarray": { "version": "1.0.0", @@ -8556,7 +8424,7 @@ "is-unc-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "integrity": "sha1-1zHoiY7QkKEsNSrS6u1Qla0yLJ0=", "dev": true, "requires": { "unc-path-regex": "^0.1.2" @@ -8566,8 +8434,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "dev": true, - "optional": true + "dev": true }, "is-utf8": { "version": "0.2.1", @@ -8579,8 +8446,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-0.3.0.tgz", "integrity": "sha1-1LVcafUYhvm2XHDWwmItN+KfSP4=", - "dev": true, - "optional": true + "dev": true }, "is-windows": { "version": "1.0.2", @@ -8598,8 +8464,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-zip/-/is-zip-1.0.0.tgz", "integrity": "sha1-R7Co/004p2QxzP2ZqOFaTIa6IyU=", - "dev": true, - "optional": true + "dev": true }, "isarray": { "version": "0.0.1", @@ -8716,7 +8581,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", "dev": true }, "json-stable-stringify-without-jsonify": { @@ -8939,7 +8804,6 @@ "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", "dev": true, - "optional": true, "requires": { "readable-stream": "^2.0.5" }, @@ -8948,15 +8812,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true + "dev": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -8972,7 +8834,6 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -9278,8 +9139,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", - "dev": true, - "optional": true + "dev": true }, "lodash.isobject": { "version": "2.4.1", @@ -9476,8 +9336,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", - "dev": true, - "optional": true + "dev": true }, "lpad-align": { "version": "1.1.2", @@ -9527,7 +9386,7 @@ "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "integrity": "sha1-KbM/MSqo9UfEpeSQ9Wr87JkTOtY=", "dev": true, "requires": { "kind-of": "^6.0.2" @@ -9724,7 +9583,7 @@ "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=", "dev": true }, "minimatch": { @@ -9913,8 +9772,7 @@ "version": "1.0.0", "resolved": "http://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz", "integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=", - "dev": true, - "optional": true + "dev": true }, "node.extend": { "version": "1.1.8", @@ -12964,8 +12822,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true, - "optional": true + "dev": true }, "p-pipe": { "version": "1.2.0", @@ -13269,7 +13126,7 @@ "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "integrity": "sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c=", "dev": true }, "posix-character-classes": { @@ -13676,8 +13533,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true, - "optional": true + "dev": true }, "preserve": { "version": "0.2.0", @@ -13809,7 +13665,6 @@ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, - "optional": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -13822,7 +13677,6 @@ "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.1.0.tgz", "integrity": "sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po=", "dev": true, - "optional": true, "requires": { "pinkie-promise": "^2.0.0", "readable-stream": "^2.0.0" @@ -13832,15 +13686,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true + "dev": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -13856,7 +13708,6 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -13961,7 +13812,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -14459,7 +14310,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=", "dev": true }, "sax": { @@ -14473,7 +14324,6 @@ "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz", "integrity": "sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=", "dev": true, - "optional": true, "requires": { "commander": "~2.8.1" } @@ -14619,8 +14469,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true, - "optional": true + "dev": true }, "set-value": { "version": "2.0.0", @@ -14648,7 +14497,7 @@ "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=", "dev": true }, "shebang-command": { @@ -15125,8 +14974,7 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.2.2.tgz", "integrity": "sha1-5sgLYjEj19gM8TLOU480YokHJQI=", - "dev": true, - "optional": true + "dev": true }, "static-extend": { "version": "0.1.2", @@ -15170,7 +15018,6 @@ "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", "dev": true, - "optional": true, "requires": { "duplexer2": "~0.1.0", "readable-stream": "^2.0.2" @@ -15181,7 +15028,6 @@ "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", "dev": true, - "optional": true, "requires": { "readable-stream": "^2.0.2" } @@ -15190,15 +15036,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true + "dev": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -15214,7 +15058,6 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -15224,20 +15067,19 @@ "stream-consume": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.1.tgz", - "integrity": "sha512-tNa3hzgkjEP7XbCkbRXe1jpg+ievoa0O4SCFlMOYEscGSS4JJsckGL8swUyAa/ApGU3Ae4t6Honor4HhL+tRyg==", + "integrity": "sha1-0721mMK9CugrjKx6xQsRB6eZbEg=", "dev": true }, "stream-shift": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", - "dev": true, - "optional": true + "dev": true }, "streamroller": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-0.7.0.tgz", - "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", + "integrity": "sha1-odG3z4PTmvsNYwSaWsv5NJO99ks=", "dev": true, "requires": { "date-format": "^1.2.0", @@ -15264,7 +15106,7 @@ "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -15279,7 +15121,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -15296,7 +15138,7 @@ "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", @@ -15410,7 +15252,6 @@ "resolved": "http://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz", "integrity": "sha1-lgu9EoeETzl1pFWKoQOoJV4kVqA=", "dev": true, - "optional": true, "requires": { "chalk": "^1.0.0", "get-stdin": "^4.0.1", @@ -15424,15 +15265,13 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true, - "optional": true + "dev": true }, "chalk": { "version": "1.1.3", "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, - "optional": true, "requires": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -15446,7 +15285,6 @@ "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz", "integrity": "sha1-hHSREZ/MtftDYhfMc39/qtUPYD8=", "dev": true, - "optional": true, "requires": { "is-relative": "^0.1.0" } @@ -15455,15 +15293,13 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz", "integrity": "sha1-kF/uiuhvRbPsYUvDwVyGnfCHboI=", - "dev": true, - "optional": true + "dev": true }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "optional": true + "dev": true } } }, @@ -15494,7 +15330,6 @@ "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", "integrity": "sha1-sv0qv2YEudHmATBXGV34Nrip1jE=", "dev": true, - "optional": true, "requires": { "escape-string-regexp": "^1.0.2" } @@ -15528,7 +15363,6 @@ "resolved": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.3.tgz", "integrity": "sha1-HGYfZnBX9jvLeHWqFDi8FiUlFW4=", "dev": true, - "optional": true, "requires": { "chalk": "^1.0.0" }, @@ -15537,15 +15371,13 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true, - "optional": true + "dev": true }, "chalk": { "version": "1.1.3", "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, - "optional": true, "requires": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -15558,8 +15390,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "optional": true + "dev": true } } }, @@ -15621,7 +15452,6 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", "dev": true, - "optional": true, "requires": { "bl": "^1.0.0", "buffer-alloc": "^1.2.0", @@ -15637,7 +15467,6 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", "dev": true, - "optional": true, "requires": { "once": "^1.4.0" } @@ -15646,15 +15475,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true + "dev": true }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -15664,7 +15491,6 @@ "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -15680,7 +15506,6 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -15785,7 +15610,6 @@ "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-2.0.0.tgz", "integrity": "sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw=", "dev": true, - "optional": true, "requires": { "through2": "~2.0.0", "xtend": "~4.0.0" @@ -15810,8 +15634,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-3.1.3.tgz", "integrity": "sha1-lYYL/MXHbCd/j4Mm/Q9bLiDrohc=", - "dev": true, - "optional": true + "dev": true }, "timers-ext": { "version": "0.1.7", @@ -15873,7 +15696,7 @@ "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", "dev": true, "requires": { "os-tmpdir": "~1.0.2" @@ -15884,7 +15707,6 @@ "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz", "integrity": "sha1-HN+kcqnvUMI57maZm2YsoOs5k38=", "dev": true, - "optional": true, "requires": { "extend-shallow": "^2.0.1" }, @@ -15894,7 +15716,6 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, - "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -15911,8 +15732,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", - "dev": true, - "optional": true + "dev": true }, "to-fast-properties": { "version": "2.0.0", @@ -15991,7 +15811,6 @@ "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", "dev": true, - "optional": true, "requires": { "escape-string-regexp": "^1.0.2" } @@ -16240,19 +16059,18 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz", "integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4=", - "dev": true, - "optional": true + "dev": true }, "upath": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "integrity": "sha1-NSVll+RqWB20eT0M5H+prr/J+r0=", "dev": true }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", "dev": true, "requires": { "punycode": "^2.1.0" @@ -16269,7 +16087,6 @@ "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", "dev": true, - "optional": true, "requires": { "prepend-http": "^1.0.1" } @@ -16355,8 +16172,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", "integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=", - "dev": true, - "optional": true + "dev": true }, "validate-npm-package-license": { "version": "3.0.4", @@ -16401,7 +16217,6 @@ "resolved": "https://registry.npmjs.org/vinyl-assign/-/vinyl-assign-1.2.1.tgz", "integrity": "sha1-TRmIkbVRWRHXcajNnFSApGoHSkU=", "dev": true, - "optional": true, "requires": { "object-assign": "^4.0.1", "readable-stream": "^2.0.0" @@ -16411,22 +16226,19 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "optional": true + "dev": true }, "readable-stream": { "version": "2.3.6", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, - "optional": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -16442,7 +16254,6 @@ "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "optional": true, "requires": { "safe-buffer": "~5.1.0" } @@ -16582,7 +16393,6 @@ "resolved": "https://registry.npmjs.org/ware/-/ware-1.3.0.tgz", "integrity": "sha1-0bFPOdLiy0q4xAmPdW/ksWTkc9Q=", "dev": true, - "optional": true, "requires": { "wrap-fn": "^0.1.0" } @@ -16673,7 +16483,6 @@ "resolved": "https://registry.npmjs.org/wrap-fn/-/wrap-fn-0.1.5.tgz", "integrity": "sha1-8htuQQFv9KfjFyDbxjoJAWvfmEU=", "dev": true, - "optional": true, "requires": { "co": "3.1.0" } @@ -16777,7 +16586,6 @@ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", "dev": true, - "optional": true, "requires": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 189732bf3a..01bb2369dc 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -176,6 +176,23 @@ function entityResource($q, $http, umbRequestHelper) { 'Failed to retrieve url and anchors data for id ' + id); }, + getAnchors: function (rteContent) { + + if (!rteContent || rteContent.length === 0) { + return []; + } + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + 'GetAnchors'), + { + rteContent: rteContent + }), + 'Failed to anchors data for rte content ' + rteContent); + }, + /** * @ngdoc method * @name umbraco.resources.entityResource#getByIds diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 19e3d3d970..d3bdceddc4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -6,7 +6,7 @@ * @description * A service containing all logic for all of the Umbraco TinyMCE plugins */ -function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, stylesheetResource, macroResource, macroService, $routeParams, umbRequestHelper, angularHelper, userService, editorService, editorState, contentEditingHelper) { +function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, stylesheetResource, macroResource, macroService, $routeParams, umbRequestHelper, angularHelper, userService, editorService, entityResource) { //These are absolutely required in order for the macros to render inline //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce @@ -1077,42 +1077,6 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s startWatch(); } - /** - * Internal method ... to retrieve the anchor named properties from the serialized string of a content item's properties - * - * From the given string, generates a string array where each item is the id attribute value from a named anchor - * 'some string with a named anchor' returns ['anchor'] - */ - function getCurrentAnchorNames() { - - if (!editorState.current || !editorState.current.variants) { - return null; - } - - //fixme - this only takes into account the first variant , not the 'current' one. - var jsonProperties = JSON.stringify(contentEditingHelper.getAllProps(editorState.current.variants[0])); - - if (!jsonProperties) { - return null; - } - - var anchors = []; - - var anchorPattern = //gi; - var matches = jsonProperties.match(anchorPattern); - - - if (matches) { - anchors = matches.map(function (v) { - return v.substring(v.indexOf('"') + 1, v.lastIndexOf('\\')); - }); - } - - return anchors.filter(function (val, i, self) { - return self.indexOf(val) === i; - }); - } - args.editor.on('init', function (e) { if (args.model.value) { @@ -1152,20 +1116,25 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s //create link picker self.createLinkPicker(args.editor, function (currentTarget, anchorElement) { - var linkPicker = { - currentTarget: currentTarget, - dataTypeId: args.model.dataTypeId, - ignoreUserStartNodes: args.model.config.ignoreUserStartNodes, - anchors: getCurrentAnchorNames(), - submit: function (model) { - self.insertLinkInEditor(args.editor, model.target, anchorElement); - editorService.close(); - }, - close: function () { - editorService.close(); - } - }; - editorService.linkPicker(linkPicker); + + + entityResource.getAnchors(args.model.value).then(function (anchorValues) { + var linkPicker = { + currentTarget: currentTarget, + dataTypeId: args.model.dataTypeId, + ignoreUserStartNodes: args.model.config.ignoreUserStartNodes, + anchors: anchorValues, + submit: function (model) { + self.insertLinkInEditor(args.editor, model.target, anchorElement); + editorService.close(); + }, + close: function () { + editorService.close(); + } + }; + editorService.linkPicker(linkPicker); + }); + }); //Create the insert media plugin diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js index f09acbcc6a..5fd23f0dd6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js @@ -183,7 +183,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", $scope.model.target.udi = media.udi; $scope.model.target.isMedia = true; $scope.model.target.name = media.name; - $scope.model.target.url = mediaHelper.resolveFile(media); + $scope.model.target.url = media.image; editorService.close(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index 7defe51d29..4e6c87d7a8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -22,7 +22,6 @@ angular.module("umbraco") $scope.setImage = function(){ var startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined; var startNodeIsVirtual = startNodeId ? $scope.model.config.startNodeIsVirtual : undefined; - $scope.mediaPickerOverlay.dataTypeId = $scope.model.dataTypeId; var mediaPicker = { startNodeId: startNodeId, @@ -31,6 +30,7 @@ angular.module("umbraco") showDetails: true, disableFolderSelect: true, onlyImages: true, + dataTypeId: $scope.model.dataTypeId, submit: function(model) { var selectedImage = model.selection[0]; diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 573bb2b872..5ee2dec506 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -21,6 +21,7 @@ using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Core.Xml; +using Umbraco.Web.Models; using Umbraco.Web.Models.Mapping; using Umbraco.Web.Models.TemplateQuery; using Umbraco.Web.Search; @@ -294,6 +295,14 @@ namespace Umbraco.Web.Editors return new UrlAndAnchors(url, anchorValues); } + [HttpGet] + [HttpPost] + public IEnumerable GetAnchors(AnchorsModel model) + { + var anchorValues = Services.ContentService.GetAnchorValuesFromRTEContent(model.RteContent); + return anchorValues; + } + #region GetById @@ -422,16 +431,7 @@ namespace Umbraco.Web.Editors { //TODO: Need to check for Object types that support hierarchy here, some might not. - int[] startNodes = null; - switch (type) - { - case UmbracoEntityTypes.Document: - startNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); - break; - case UmbracoEntityTypes.Media: - startNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); - break; - } + var startNodes = GetStartNodes(type); var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); @@ -556,16 +556,7 @@ namespace Umbraco.Web.Editors IEnumerable entities; long totalRecords; - int[] startNodes = null; - switch (type) - { - case UmbracoEntityTypes.Document: - startNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); - break; - case UmbracoEntityTypes.Media: - startNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); - break; - } + var startNodes = GetStartNodes(type); var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); @@ -630,6 +621,25 @@ namespace Umbraco.Web.Editors } } + private int[] GetStartNodes(UmbracoEntityTypes type) + { + int[] startNodes = null; + switch (type) + { + case UmbracoEntityTypes.Document: + startNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); + break; + case UmbracoEntityTypes.Media: + startNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); + break; + default: + startNodes = Array.Empty(); + break; + } + + return startNodes; + } + public PagedResult GetPagedDescendants( int id, diff --git a/src/Umbraco.Web/Models/AnchorsModel.cs b/src/Umbraco.Web/Models/AnchorsModel.cs new file mode 100644 index 0000000000..9edcf3466b --- /dev/null +++ b/src/Umbraco.Web/Models/AnchorsModel.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Web.Models +{ + public class AnchorsModel + { + public string RteContent { get; set; } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 0c1ab83de0..3c4f9fdfd7 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -204,6 +204,7 @@ + From 5625102e2c0bc37b1d3ff0ffd036834a89a30352 Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 2 Jul 2019 12:42:18 +0200 Subject: [PATCH 088/776] making EditorValidatorOfT public instead of internal. --- src/Umbraco.Web/Editors/EditorValidatorOfT.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Editors/EditorValidatorOfT.cs b/src/Umbraco.Web/Editors/EditorValidatorOfT.cs index 4ca008cf0d..715a49179e 100644 --- a/src/Umbraco.Web/Editors/EditorValidatorOfT.cs +++ b/src/Umbraco.Web/Editors/EditorValidatorOfT.cs @@ -8,7 +8,7 @@ namespace Umbraco.Web.Editors /// Provides a base class for implementations. /// /// The validated object type. - internal abstract class EditorValidator : IEditorValidator + public abstract class EditorValidator : IEditorValidator { public Type ModelType => typeof (T); @@ -16,4 +16,4 @@ namespace Umbraco.Web.Editors protected abstract IEnumerable Validate(T model); } -} \ No newline at end of file +} From d5bc54cf6c4f7211ed3e4687c75c4a8659c54f20 Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 2 Jul 2019 13:00:50 +0200 Subject: [PATCH 089/776] refixing #4708 - old merge error. --- src/Umbraco.Web.UI.Client/src/preview/preview.controller.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js index 7d6584d2f1..1316642e93 100644 --- a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js @@ -113,7 +113,10 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi $scope.exitPreview = function () { var culture = $location.search().culture || getParameterByName("culture"); - var relativeUrl = "/" + $scope.pageId +'?culture='+ culture; + var relativeUrl = "/" + $scope.pageId; + if (culture) { + relativeUrl += '?culture=' + culture; + } window.top.location.href = "../preview/end?redir=" + encodeURIComponent(relativeUrl); }; From 7b4023fd5a034a0938b59582da61d6475e0d88ad Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 2 Jul 2019 13:17:47 +0200 Subject: [PATCH 090/776] Fixes issue when creating DateTypes of type Umbraco.MultiNodeTreePicker with type member, because `-1` can't be casted to `Udi` --- .../views/prevalueeditors/treesource.controller.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js index e20f2f8d3a..fb2dddd72c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js @@ -60,20 +60,6 @@ angular.module('umbraco') $scope.model.value.query = undefined; }; - - //we always need to ensure we dont submit anything broken - var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { - if($scope.model.value.type === "member"){ - $scope.model.value.id = -1; - $scope.model.value.query = ""; - } - }); - - //when the scope is destroyed we need to unsubscribe - $scope.$on('$destroy', function () { - unsubscribe(); - }); - function populate(item){ $scope.clear(); item.icon = iconHelper.convertFromLegacyIcon(item.icon); From 96d5bdd7b20c3b45c05f2ac177b25be78bdb2b14 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 2 Jul 2019 15:26:54 +0200 Subject: [PATCH 091/776] https://github.com/umbraco/Umbraco-CMS/issues/5671 - If modelsbuilder is in live mode, we need to clear cache for all document types when at least one is cleared. This is due to modelsbuilder updating all the models to different versions. --- .../Cache/ContentTypeCacheRefresher.cs | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index 86ff709541..a5134426c2 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Umbraco.Core; using Umbraco.Core.Cache; @@ -17,15 +18,17 @@ namespace Umbraco.Web.Cache private readonly IPublishedSnapshotService _publishedSnapshotService; private readonly IPublishedModelFactory _publishedModelFactory; private readonly IContentTypeCommonRepository _contentTypeCommonRepository; + private readonly IContentTypeService _contentTypeService; private readonly IdkMap _idkMap; - public ContentTypeCacheRefresher(AppCaches appCaches, IPublishedSnapshotService publishedSnapshotService, IPublishedModelFactory publishedModelFactory, IdkMap idkMap, IContentTypeCommonRepository contentTypeCommonRepository) + public ContentTypeCacheRefresher(AppCaches appCaches, IPublishedSnapshotService publishedSnapshotService, IPublishedModelFactory publishedModelFactory, IdkMap idkMap, IContentTypeCommonRepository contentTypeCommonRepository, IContentTypeService contentTypeService) : base(appCaches) { _publishedSnapshotService = publishedSnapshotService; _publishedModelFactory = publishedModelFactory; _idkMap = idkMap; _contentTypeCommonRepository = contentTypeCommonRepository; + _contentTypeService = contentTypeService; } #region Define @@ -50,6 +53,16 @@ namespace Umbraco.Web.Cache _contentTypeCommonRepository.ClearCache(); // always + // We need to special handle the IContentType if modelsbuilder is in live mode, because all models are updated when a IContentType is changed, we need to clear all from cache also. + if (_publishedModelFactory is ILivePublishedModelFactory && payloads.Any(x => x.ItemType == typeof(IContentType).Name)) + { + //This is super nasty, and we need to figure out a better way to to this + //Ensure all doc type ids is part of the payload + var missingPayloads = GetMissingContentTypePayloads(payloads); + + payloads = payloads.Union(missingPayloads).ToArray(); + } + if (payloads.Any(x => x.ItemType == typeof(IContentType).Name)) { ClearAllIsolatedCacheByEntityType(); @@ -96,6 +109,20 @@ namespace Umbraco.Web.Cache base.Refresh(payloads); } + private IEnumerable GetMissingContentTypePayloads(JsonPayload[] payloads) + { + var existingPayloadIds = new HashSet(payloads.Select(x => x.Id)); + var contentTypeIds = _contentTypeService.GetAll().Select(x => x.Id).ToArray(); + + foreach (var contentTypeId in contentTypeIds) + { + if (!existingPayloadIds.Contains(contentTypeId)) + { + yield return new JsonPayload(typeof(IContentType).Name, contentTypeId, ContentTypeChangeTypes.RefreshOther); + } + } + } + public override void RefreshAll() { throw new NotSupportedException(); From 5bf29a907a3c422994be284ce84d26955669d5ab Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 2 Jul 2019 21:45:45 +0200 Subject: [PATCH 092/776] Fixes #5206 --- .../Editors/BackOfficeServerVariables.cs | 4 - src/Umbraco.Web/Umbraco.Web.csproj | 2025 +++++++++++++++++ src/Umbraco.Web/WebServices/TagsController.cs | 84 - 3 files changed, 2025 insertions(+), 88 deletions(-) delete mode 100644 src/Umbraco.Web/WebServices/TagsController.cs diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index 6e27a8f8e5..88c6b5b70a 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -224,10 +224,6 @@ namespace Umbraco.Web.Editors "updateCheckApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetCheck()) }, - { - "tagApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( - controller => controller.GetAllTags(null)) - }, { "templateApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetById(0)) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 0b601f8f94..fc5ed8f8be 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -2025,4 +2025,2029 @@ + + + + + 9.0.30729 + 2.0 + {651E1350-91B6-44B7-BD60-7207006D7003} + Debug + AnyCPU + + + + + umbraco + + + JScript + Grid + IE50 + false + Library + Umbraco.Web + OnBuildSuccess + + + + + + + + + + + + + + + 4.0 + v4.5.2 + + ..\ + true + latest + + + bin\Debug\ + false + 285212672 + false + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + false + 4 + full + prompt + AllRules.ruleset + false + Off + latest + + + bin\Release\ + false + 285212672 + false + + + TRACE + bin\Release\umbraco.xml + true + 4096 + false + + + true + false + false + false + 4 + pdbonly + prompt + AllRules.ruleset + false + Off + + + + {07fbc26b-2927-4a22-8d96-d644c667fecc} + UmbracoExamine + + + ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll + True + + + ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll + True + + + ..\packages\ClientDependency.1.9.7\lib\net45\ClientDependency.Core.dll + + + ..\packages\dotless.1.5.2\lib\dotless.Core.dll + + + ..\packages\Examine.0.1.90\lib\net45\Examine.dll + + + ..\packages\HtmlAgilityPack.1.8.8\lib\Net45\HtmlAgilityPack.dll + + + ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll + + + ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll + + + ..\packages\Markdown.1.14.7\lib\net45\MarkdownSharp.dll + True + + + ..\packages\Microsoft.AspNet.Identity.Core.2.2.2\lib\net45\Microsoft.AspNet.Identity.Core.dll + + + ..\packages\Microsoft.AspNet.Identity.Owin.2.2.2\lib\net45\Microsoft.AspNet.Identity.Owin.dll + + + ..\packages\Microsoft.AspNet.SignalR.Core.2.4.1\lib\net45\Microsoft.AspNet.SignalR.Core.dll + + + + ..\packages\Microsoft.Owin.4.0.1\lib\net45\Microsoft.Owin.dll + + + ..\packages\Microsoft.Owin.Host.SystemWeb.4.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + + + ..\packages\Microsoft.Owin.Security.4.0.1\lib\net45\Microsoft.Owin.Security.dll + + + ..\packages\Microsoft.Owin.Security.Cookies.4.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll + + + ..\packages\Microsoft.Owin.Security.OAuth.4.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll + + + ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + True + + + ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll + True + + + ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + ..\packages\semver.1.1.2\lib\net451\Semver.dll + + + System + + + + + + System.Data + + + + + System.Drawing + + + + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll + + + + + + ..\packages\System.Threading.Tasks.Dataflow.4.9.0\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll + + + + 3.5 + + + + + + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.Helpers.dll + + + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll + + + ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.dll + + + ..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll + + + ..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll + + + System.Web.Services + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.dll + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Deployment.dll + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Razor.dll + + + + System.XML + + + + {5BA5425F-27A7-4677-865E-82246498AA2E} + SqlCE4Umbraco + + + {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} + Umbraco.Core + + + {6EDD2061-82F2-461B-BB6E-879245A832DE} + umbraco.controls + + + umbraco.businesslogic + {E469A9CE-1BEC-423F-AC44-713CD72457EA} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + {CCD75EC3-63DB-4184-B49D-51C1DD337230} + umbraco.cms + + + {C7CB79F0-1C97-4B33-BFA7-00731B579AE2} + umbraco.datalayer + + + umbraco.interfaces + {511F6D8D-7717-440A-9A57-A507E9A8B27F} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + {D7636876-0756-43CB-A192-138C6F0D5E42} + umbraco.providers + + + + + Properties\SolutionInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + True + True + Reference.map + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + AssignDomain2.aspx + ASPXCodeBehind + + + AssignDomain2.aspx + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + True + True + Strings.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + + Code + + + Code + + + Code + + + Code + + + Code + + + + Code + + + True + True + Settings.settings + + + Code + + + + + Code + + + Code + + + Code + + + Code + + + + ASPXCodeBehind + + + Code + + + Code + + + Code + + + + delete.aspx + ASPXCodeBehind + + + delete.aspx + + + editContent.aspx + ASPXCodeBehind + + + editContent.aspx + + + preview.aspx + ASPXCodeBehind + + + preview.aspx + + + publish.aspx + ASPXCodeBehind + + + publish.aspx + + + + + + ASPXCodeBehind + + + + + + + ProgressBar.ascx + ASPXCodeBehind + + + ProgressBar.ascx + + + + ASPXCodeBehind + + + Component + + + + + Code + + + + + + + + + + FeedProxy.aspx + ASPXCodeBehind + + + FeedProxy.aspx + + + EditRelationType.aspx + ASPXCodeBehind + + + EditRelationType.aspx + + + NewRelationType.aspx + ASPXCodeBehind + + + NewRelationType.aspx + + + + RelationTypesWebService.asmx + Component + + + + + Preview.aspx + ASPXCodeBehind + + + Preview.aspx + + + MemberSearch.ascx + ASPXCodeBehind + + + MemberSearch.ascx + + + + + + xsltVisualize.aspx + ASPXCodeBehind + + + xsltVisualize.aspx + + + insertMasterpageContent.aspx + ASPXCodeBehind + + + insertMasterpageContent.aspx + + + insertMasterpagePlaceholder.aspx + ASPXCodeBehind + + + insertMasterpagePlaceholder.aspx + + + republish.aspx + ASPXCodeBehind + + + republish.aspx + + + search.aspx + ASPXCodeBehind + + + search.aspx + + + SendPublish.aspx + ASPXCodeBehind + + + SendPublish.aspx + + + Code + + + Code + + + assemblyBrowser.aspx + ASPXCodeBehind + + + assemblyBrowser.aspx + + + editPackage.aspx + ASPXCodeBehind + + + editPackage.aspx + + + getXsltStatus.asmx + Component + + + xsltChooseExtension.aspx + ASPXCodeBehind + + + xsltChooseExtension.aspx + + + xsltInsertValueOf.aspx + ASPXCodeBehind + + + xsltInsertValueOf.aspx + + + exportDocumenttype.aspx + ASPXCodeBehind + + + importDocumenttype.aspx + ASPXCodeBehind + + + rollBack.aspx + ASPXCodeBehind + + + rollBack.aspx + + + sendToTranslation.aspx + ASPXCodeBehind + + + sendToTranslation.aspx + + + viewAuditTrail.aspx + ASPXCodeBehind + + + viewAuditTrail.aspx + + + EditMemberGroup.aspx + ASPXCodeBehind + + + EditMemberGroup.aspx + + + search.aspx + ASPXCodeBehind + + + search.aspx + + + ViewMembers.aspx + ASPXCodeBehind + + + ViewMembers.aspx + + + Code + + + + + + + + + + tinymce3tinymceCompress.aspx + ASPXCodeBehind + + + tinymce3tinymceCompress.aspx + + + QuickSearchHandler.ashx + + + DictionaryItemList.aspx + ASPXCodeBehind + + + DictionaryItemList.aspx + + + editLanguage.aspx + ASPXCodeBehind + + + editLanguage.aspx + + + + + + Code + + + + + + + + + + + + + + + + + MacroContainerService.asmx + Component + + + TagsAutoCompleteHandler.ashx + + + TreeClientService.asmx + Component + + + + + + + + True + True + Resources.resx + + + default.aspx + ASPXCodeBehind + + + default.aspx + + + details.aspx + ASPXCodeBehind + + + details.aspx + + + preview.aspx + ASPXCodeBehind + + + preview.aspx + + + xml.aspx + ASPXCodeBehind + + + xml.aspx + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + TreeDataService.ashx + + + + + + + XmlTree.xsd + + + + + CacheRefresher.asmx + Component + + + CheckForUpgrade.asmx + Component + + + CMSNode.asmx + Component + + + codeEditorSave.asmx + Component + + + legacyAjaxCalls.asmx + Component + + + nodeSorter.asmx + Component + + + progressStatus.asmx + Component + + + publication.asmx + Component + + + + ASPXCodeBehind + + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + + + + + + True + True + Reference.map + + + + + + + + + + + Component + + + + Component + + + + + Mvc\web.config + + + + MSDiscoCodeGenerator + Reference.cs + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + ASPXCodeBehind + + + + + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + + + + + + + Reference.map + + + Reference.map + + + SettingsSingleFileGenerator + Settings1.Designer.cs + + + + + + ASPXCodeBehind + + + + + + + + + + Form + + + Designer + + + + + Form + + + + + + + Form + + + + + + + + + + XmlTree.xsd + + + + + + MSDiscoCodeGenerator + Reference.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + ResXFileCodeGenerator + Strings.Designer.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + + + + + package.xsd + + + umbraco.xsd + + + umbraco.xsd + + + umbraco.xsd + + + + + + + + Dynamic + Web References\org.umbraco.our\ + https://our.umbraco.com/umbraco/webservices/api/repository.asmx + + + + + Settings + umbraco_org_umbraco_our_Repository + + + Dynamic + Web References\org.umbraco.update\ + http://update.umbraco.org/checkforupgrade.asmx + + + + + Settings + umbraco_org_umbraco_update_CheckForUpgrade + + + + + + + 11.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v15.0 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Umbraco.Web/WebServices/TagsController.cs b/src/Umbraco.Web/WebServices/TagsController.cs deleted file mode 100644 index e314e0a609..0000000000 --- a/src/Umbraco.Web/WebServices/TagsController.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using AutoMapper; -using Examine; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Web.Models; -using Umbraco.Web.WebApi; - -namespace Umbraco.Web.WebServices -{ - /// - /// A public web service for querying tags - /// - /// - /// This controller does not contain methods to query for content, media or members based on tags, those methods would require - /// authentication and should not be exposed publicly. - /// - public class TagsController : UmbracoApiController - { - /// - /// Get every tag stored in the database (with optional group) - /// - public IEnumerable GetAllTags(string group = null) - { - return Umbraco.TagQuery.GetAllTags(group); - } - - /// - /// Get all tags for content items (with optional group) - /// - /// - /// - public IEnumerable GetAllContentTags(string group = null) - { - return Umbraco.TagQuery.GetAllContentTags(group); - } - - /// - /// Get all tags for media items (with optional group) - /// - /// - /// - public IEnumerable GetAllMediaTags(string group = null) - { - return Umbraco.TagQuery.GetAllMediaTags(group); - } - - /// - /// Get all tags for member items (with optional group) - /// - /// - /// - public IEnumerable GetAllMemberTags(string group = null) - { - return Umbraco.TagQuery.GetAllMemberTags(group); - } - - /// - /// Returns all tags attached to a property by entity id - /// - /// - /// - /// - /// - public IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string tagGroup = null) - { - return Umbraco.TagQuery.GetTagsForProperty(contentId, propertyTypeAlias, tagGroup); - } - - /// - /// Returns all tags attached to an entity (content, media or member) by entity id - /// - /// - /// - /// - public IEnumerable GetTagsForEntity(int contentId, string tagGroup = null) - { - return Umbraco.TagQuery.GetTagsForEntity(contentId, tagGroup); - } - - } -} From 5b7ae7150fecc1b665b306a5f3eab37958b7e433 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 2 Jul 2019 21:55:54 +0200 Subject: [PATCH 093/776] Fix weird merge --- src/Umbraco.Web/Umbraco.Web.csproj | 6074 +++++++++------------------- 1 file changed, 2024 insertions(+), 4050 deletions(-) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index fc5ed8f8be..167c99b9b9 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1,4053 +1,2027 @@  - - - 9.0.30729 - 2.0 - {651E1350-91B6-44B7-BD60-7207006D7003} - Debug - AnyCPU - - - - - umbraco - - - JScript - Grid - IE50 - false - Library - Umbraco.Web - OnBuildSuccess - - - - - - - - - - - - - - - 4.0 - v4.5.2 - - ..\ - true - latest - - - bin\Debug\ - false - 285212672 - false - - - DEBUG;TRACE - - - true - 4096 - false - - - false - false - false - false - 4 - full - prompt - AllRules.ruleset - false - Off - latest - - - bin\Release\ - false - 285212672 - false - - - TRACE - bin\Release\umbraco.xml - true - 4096 - false - - - true - false - false - false - 4 - pdbonly - prompt - AllRules.ruleset - false - Off - - - - {07fbc26b-2927-4a22-8d96-d644c667fecc} - UmbracoExamine - - - ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll - True - - - ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll - True - - - ..\packages\ClientDependency.1.9.7\lib\net45\ClientDependency.Core.dll - - - ..\packages\dotless.1.5.2\lib\dotless.Core.dll - - - ..\packages\Examine.0.1.90\lib\net45\Examine.dll - - - ..\packages\HtmlAgilityPack.1.8.8\lib\Net45\HtmlAgilityPack.dll - - - ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - - - ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll - - - ..\packages\Markdown.1.14.7\lib\net45\MarkdownSharp.dll - True - - - ..\packages\Microsoft.AspNet.Identity.Core.2.2.2\lib\net45\Microsoft.AspNet.Identity.Core.dll - - - ..\packages\Microsoft.AspNet.Identity.Owin.2.2.2\lib\net45\Microsoft.AspNet.Identity.Owin.dll - - - ..\packages\Microsoft.AspNet.SignalR.Core.2.4.1\lib\net45\Microsoft.AspNet.SignalR.Core.dll - - - - ..\packages\Microsoft.Owin.4.0.1\lib\net45\Microsoft.Owin.dll - - - ..\packages\Microsoft.Owin.Host.SystemWeb.4.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - - - ..\packages\Microsoft.Owin.Security.4.0.1\lib\net45\Microsoft.Owin.Security.dll - - - ..\packages\Microsoft.Owin.Security.Cookies.4.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll - - - ..\packages\Microsoft.Owin.Security.OAuth.4.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll - - - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - True - - - ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll - True - - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - ..\packages\semver.1.1.2\lib\net451\Semver.dll - - - System - - - - - - System.Data - - - - - System.Drawing - - - - - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll - - - - - - ..\packages\System.Threading.Tasks.Dataflow.4.9.0\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll - - - ..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll - - - - 3.5 - - - - - - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.Helpers.dll - - - ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll - - - ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.dll - - - ..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll - - - ..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll - - - System.Web.Services - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.dll - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Deployment.dll - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Razor.dll - - - - System.XML - - - - {5BA5425F-27A7-4677-865E-82246498AA2E} - SqlCE4Umbraco - - - {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} - Umbraco.Core - - - {6EDD2061-82F2-461B-BB6E-879245A832DE} - umbraco.controls - - - umbraco.businesslogic - {E469A9CE-1BEC-423F-AC44-713CD72457EA} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - {CCD75EC3-63DB-4184-B49D-51C1DD337230} - umbraco.cms - - - {C7CB79F0-1C97-4B33-BFA7-00731B579AE2} - umbraco.datalayer - - - umbraco.interfaces - {511F6D8D-7717-440A-9A57-A507E9A8B27F} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - {D7636876-0756-43CB-A192-138C6F0D5E42} - umbraco.providers - - - - - Properties\SolutionInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - True - True - Reference.map - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - AssignDomain2.aspx - ASPXCodeBehind - - - AssignDomain2.aspx - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - True - True - Strings.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - - Code - - - Code - - - Code - - - Code - - - Code - - - - Code - - - True - True - Settings.settings - - - Code - - - - - Code - - - Code - - - Code - - - Code - - - - ASPXCodeBehind - - - Code - - - Code - - - Code - - - - delete.aspx - ASPXCodeBehind - - - delete.aspx - - - editContent.aspx - ASPXCodeBehind - - - editContent.aspx - - - preview.aspx - ASPXCodeBehind - - - preview.aspx - - - publish.aspx - ASPXCodeBehind - - - publish.aspx - - - - - - ASPXCodeBehind - - - - - - - ProgressBar.ascx - ASPXCodeBehind - - - ProgressBar.ascx - - - - ASPXCodeBehind - - - Component - - - - - Code - - - - - - - - - - FeedProxy.aspx - ASPXCodeBehind - - - FeedProxy.aspx - - - EditRelationType.aspx - ASPXCodeBehind - - - EditRelationType.aspx - - - NewRelationType.aspx - ASPXCodeBehind - - - NewRelationType.aspx - - - - RelationTypesWebService.asmx - Component - - - - - Preview.aspx - ASPXCodeBehind - - - Preview.aspx - - - MemberSearch.ascx - ASPXCodeBehind - - - MemberSearch.ascx - - - - - - xsltVisualize.aspx - ASPXCodeBehind - - - xsltVisualize.aspx - - - insertMasterpageContent.aspx - ASPXCodeBehind - - - insertMasterpageContent.aspx - - - insertMasterpagePlaceholder.aspx - ASPXCodeBehind - - - insertMasterpagePlaceholder.aspx - - - republish.aspx - ASPXCodeBehind - - - republish.aspx - - - search.aspx - ASPXCodeBehind - - - search.aspx - - - SendPublish.aspx - ASPXCodeBehind - - - SendPublish.aspx - - - Code - - - Code - - - assemblyBrowser.aspx - ASPXCodeBehind - - - assemblyBrowser.aspx - - - editPackage.aspx - ASPXCodeBehind - - - editPackage.aspx - - - getXsltStatus.asmx - Component - - - xsltChooseExtension.aspx - ASPXCodeBehind - - - xsltChooseExtension.aspx - - - xsltInsertValueOf.aspx - ASPXCodeBehind - - - xsltInsertValueOf.aspx - - - exportDocumenttype.aspx - ASPXCodeBehind - - - importDocumenttype.aspx - ASPXCodeBehind - - - rollBack.aspx - ASPXCodeBehind - - - rollBack.aspx - - - sendToTranslation.aspx - ASPXCodeBehind - - - sendToTranslation.aspx - - - viewAuditTrail.aspx - ASPXCodeBehind - - - viewAuditTrail.aspx - - - EditMemberGroup.aspx - ASPXCodeBehind - - - EditMemberGroup.aspx - - - search.aspx - ASPXCodeBehind - - - search.aspx - - - ViewMembers.aspx - ASPXCodeBehind - - - ViewMembers.aspx - - - Code - - - - - - - - - - tinymce3tinymceCompress.aspx - ASPXCodeBehind - - - tinymce3tinymceCompress.aspx - - - QuickSearchHandler.ashx - - - DictionaryItemList.aspx - ASPXCodeBehind - - - DictionaryItemList.aspx - - - editLanguage.aspx - ASPXCodeBehind - - - editLanguage.aspx - - - - - - Code - - - - - - - - - - - - - - - - - MacroContainerService.asmx - Component - - - TagsAutoCompleteHandler.ashx - - - TreeClientService.asmx - Component - - - - - - - - True - True - Resources.resx - - - default.aspx - ASPXCodeBehind - - - default.aspx - - - details.aspx - ASPXCodeBehind - - - details.aspx - - - preview.aspx - ASPXCodeBehind - - - preview.aspx - - - xml.aspx - ASPXCodeBehind - - - xml.aspx - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - TreeDataService.ashx - - - - - - - XmlTree.xsd - - - - - CacheRefresher.asmx - Component - - - CheckForUpgrade.asmx - Component - - - CMSNode.asmx - Component - - - codeEditorSave.asmx - Component - - - legacyAjaxCalls.asmx - Component - - - nodeSorter.asmx - Component - - - progressStatus.asmx - Component - - - publication.asmx - Component - - - - ASPXCodeBehind - - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - - - - - - True - True - Reference.map - - - - - - - - - - - Component - - - - Component - - - - - Mvc\web.config - - - - MSDiscoCodeGenerator - Reference.cs - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - ASPXCodeBehind - - - - - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - - - - - - - Reference.map - - - Reference.map - - - SettingsSingleFileGenerator - Settings1.Designer.cs - - - - - - ASPXCodeBehind - - - - - - - - - - Form - - - Designer - - - - - Form - - - - - - - Form - - - - - - - - - - XmlTree.xsd - - - - - - MSDiscoCodeGenerator - Reference.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - ResXFileCodeGenerator - Strings.Designer.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - - - - package.xsd - - - umbraco.xsd - - - umbraco.xsd - - - umbraco.xsd - - - - - - - - Dynamic - Web References\org.umbraco.our\ - https://our.umbraco.com/umbraco/webservices/api/repository.asmx - - - - - Settings - umbraco_org_umbraco_our_Repository - - - Dynamic - Web References\org.umbraco.update\ - http://update.umbraco.org/checkforupgrade.asmx - - - - - Settings - umbraco_org_umbraco_update_CheckForUpgrade - - - - - - - 11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v15.0 - - - - - - - - - - - - - - - - - - - - 9.0.30729 - 2.0 - {651E1350-91B6-44B7-BD60-7207006D7003} - Debug - AnyCPU - - - - - umbraco - - - JScript - Grid - IE50 - false - Library - Umbraco.Web - OnBuildSuccess - - - - - - - - - - - - - - - 4.0 - v4.5.2 - - ..\ - true - latest - - - bin\Debug\ - false - 285212672 - false - - - DEBUG;TRACE - - - true - 4096 - false - - - false - false - false - false - 4 - full - prompt - AllRules.ruleset - false - Off - latest - - - bin\Release\ - false - 285212672 - false - - - TRACE - bin\Release\umbraco.xml - true - 4096 - false - - - true - false - false - false - 4 - pdbonly - prompt - AllRules.ruleset - false - Off - - - - {07fbc26b-2927-4a22-8d96-d644c667fecc} - UmbracoExamine - - - ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll - True - - - ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll - True - - - ..\packages\ClientDependency.1.9.7\lib\net45\ClientDependency.Core.dll - - - ..\packages\dotless.1.5.2\lib\dotless.Core.dll - - - ..\packages\Examine.0.1.90\lib\net45\Examine.dll - - - ..\packages\HtmlAgilityPack.1.8.8\lib\Net45\HtmlAgilityPack.dll - - - ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - - - ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll - - - ..\packages\Markdown.1.14.7\lib\net45\MarkdownSharp.dll - True - - - ..\packages\Microsoft.AspNet.Identity.Core.2.2.2\lib\net45\Microsoft.AspNet.Identity.Core.dll - - - ..\packages\Microsoft.AspNet.Identity.Owin.2.2.2\lib\net45\Microsoft.AspNet.Identity.Owin.dll - - - ..\packages\Microsoft.AspNet.SignalR.Core.2.4.1\lib\net45\Microsoft.AspNet.SignalR.Core.dll - - - - ..\packages\Microsoft.Owin.4.0.1\lib\net45\Microsoft.Owin.dll - - - ..\packages\Microsoft.Owin.Host.SystemWeb.4.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - - - ..\packages\Microsoft.Owin.Security.4.0.1\lib\net45\Microsoft.Owin.Security.dll - - - ..\packages\Microsoft.Owin.Security.Cookies.4.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll - - - ..\packages\Microsoft.Owin.Security.OAuth.4.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll - - - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - True - - - ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll - True - - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - ..\packages\semver.1.1.2\lib\net451\Semver.dll - - - System - - - - - - System.Data - - - - - System.Drawing - - - - - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll - - - - - - ..\packages\System.Threading.Tasks.Dataflow.4.9.0\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll - - - ..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll - - - - 3.5 - - - - - - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.Helpers.dll - - - ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll - - - ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.dll - - - ..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll - - - ..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll - - - System.Web.Services - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.dll - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Deployment.dll - - - ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Razor.dll - - - - System.XML - - - - {5BA5425F-27A7-4677-865E-82246498AA2E} - SqlCE4Umbraco - - - {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} - Umbraco.Core - - - {6EDD2061-82F2-461B-BB6E-879245A832DE} - umbraco.controls - - - umbraco.businesslogic - {E469A9CE-1BEC-423F-AC44-713CD72457EA} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - {CCD75EC3-63DB-4184-B49D-51C1DD337230} - umbraco.cms - - - {C7CB79F0-1C97-4B33-BFA7-00731B579AE2} - umbraco.datalayer - - - umbraco.interfaces - {511F6D8D-7717-440A-9A57-A507E9A8B27F} - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - {D7636876-0756-43CB-A192-138C6F0D5E42} - umbraco.providers - - - - - Properties\SolutionInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - True - True - Reference.map - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - AssignDomain2.aspx - ASPXCodeBehind - - - AssignDomain2.aspx - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - True - True - Strings.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - - Code - - - Code - - - Code - - - Code - - - Code - - - - Code - - - True - True - Settings.settings - - - Code - - - - - Code - - - Code - - - Code - - - Code - - - - ASPXCodeBehind - - - Code - - - Code - - - Code - - - - delete.aspx - ASPXCodeBehind - - - delete.aspx - - - editContent.aspx - ASPXCodeBehind - - - editContent.aspx - - - preview.aspx - ASPXCodeBehind - - - preview.aspx - - - publish.aspx - ASPXCodeBehind - - - publish.aspx - - - - - - ASPXCodeBehind - - - - - - - ProgressBar.ascx - ASPXCodeBehind - - - ProgressBar.ascx - - - - ASPXCodeBehind - - - Component - - - - - Code - - - - - - - - - - FeedProxy.aspx - ASPXCodeBehind - - - FeedProxy.aspx - - - EditRelationType.aspx - ASPXCodeBehind - - - EditRelationType.aspx - - - NewRelationType.aspx - ASPXCodeBehind - - - NewRelationType.aspx - - - - RelationTypesWebService.asmx - Component - - - - - Preview.aspx - ASPXCodeBehind - - - Preview.aspx - - - MemberSearch.ascx - ASPXCodeBehind - - - MemberSearch.ascx - - - - - - xsltVisualize.aspx - ASPXCodeBehind - - - xsltVisualize.aspx - - - insertMasterpageContent.aspx - ASPXCodeBehind - - - insertMasterpageContent.aspx - - - insertMasterpagePlaceholder.aspx - ASPXCodeBehind - - - insertMasterpagePlaceholder.aspx - - - republish.aspx - ASPXCodeBehind - - - republish.aspx - - - search.aspx - ASPXCodeBehind - - - search.aspx - - - SendPublish.aspx - ASPXCodeBehind - - - SendPublish.aspx - - - Code - - - Code - - - assemblyBrowser.aspx - ASPXCodeBehind - - - assemblyBrowser.aspx - - - editPackage.aspx - ASPXCodeBehind - - - editPackage.aspx - - - getXsltStatus.asmx - Component - - - xsltChooseExtension.aspx - ASPXCodeBehind - - - xsltChooseExtension.aspx - - - xsltInsertValueOf.aspx - ASPXCodeBehind - - - xsltInsertValueOf.aspx - - - exportDocumenttype.aspx - ASPXCodeBehind - - - importDocumenttype.aspx - ASPXCodeBehind - - - rollBack.aspx - ASPXCodeBehind - - - rollBack.aspx - - - sendToTranslation.aspx - ASPXCodeBehind - - - sendToTranslation.aspx - - - viewAuditTrail.aspx - ASPXCodeBehind - - - viewAuditTrail.aspx - - - EditMemberGroup.aspx - ASPXCodeBehind - - - EditMemberGroup.aspx - - - search.aspx - ASPXCodeBehind - - - search.aspx - - - ViewMembers.aspx - ASPXCodeBehind - - - ViewMembers.aspx - - - Code - - - - - - - - - - tinymce3tinymceCompress.aspx - ASPXCodeBehind - - - tinymce3tinymceCompress.aspx - - - QuickSearchHandler.ashx - - - DictionaryItemList.aspx - ASPXCodeBehind - - - DictionaryItemList.aspx - - - editLanguage.aspx - ASPXCodeBehind - - - editLanguage.aspx - - - - - - Code - - - - - - - - - - - - - - - - - MacroContainerService.asmx - Component - - - TagsAutoCompleteHandler.ashx - - - TreeClientService.asmx - Component - - - - - - - - True - True - Resources.resx - - - default.aspx - ASPXCodeBehind - - - default.aspx - - - details.aspx - ASPXCodeBehind - - - details.aspx - - - preview.aspx - ASPXCodeBehind - - - preview.aspx - - - xml.aspx - ASPXCodeBehind - - - xml.aspx - - - ASPXCodeBehind - - - - - - - - - - - - - - - - - - - - - - - - - - - - TreeDataService.ashx - - - - - - - XmlTree.xsd - - - - - CacheRefresher.asmx - Component - - - CheckForUpgrade.asmx - Component - - - CMSNode.asmx - Component - - - codeEditorSave.asmx - Component - - - legacyAjaxCalls.asmx - Component - - - nodeSorter.asmx - Component - - - progressStatus.asmx - Component - - - publication.asmx - Component - - - - ASPXCodeBehind - - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - uQuery.cs - - - - - - - - True - True - Reference.map - - - - - - - - - - - Component - - - - Component - - - - - Mvc\web.config - - - - MSDiscoCodeGenerator - Reference.cs - - - - - - - - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - ASPXCodeBehind - - - - - - ASPXCodeBehind - - - - - - - - ASPXCodeBehind - - - - - ASPXCodeBehind - - - - - - - - - Reference.map - - - Reference.map - - - SettingsSingleFileGenerator - Settings1.Designer.cs - - - - - - ASPXCodeBehind - - - - - - - - - - Form - - - Designer - - - - - Form - - - - - - - Form - - - - - - - - - - XmlTree.xsd - - - - - - MSDiscoCodeGenerator - Reference.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - ResXFileCodeGenerator - Strings.Designer.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - - - - package.xsd - - - umbraco.xsd - - - umbraco.xsd - - - umbraco.xsd - - - - - - - - Dynamic - Web References\org.umbraco.our\ - https://our.umbraco.com/umbraco/webservices/api/repository.asmx - - - - - Settings - umbraco_org_umbraco_our_Repository - - - Dynamic - Web References\org.umbraco.update\ - http://update.umbraco.org/checkforupgrade.asmx - - - - - Settings - umbraco_org_umbraco_update_CheckForUpgrade - - - - - - - 11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v15.0 - - - - - - - - - - - - - - - + + + 9.0.30729 + 2.0 + {651E1350-91B6-44B7-BD60-7207006D7003} + Debug + AnyCPU + + + + + umbraco + + + JScript + Grid + IE50 + false + Library + Umbraco.Web + OnBuildSuccess + + + + + + + + + + + + + + + 4.0 + v4.5.2 + + ..\ + true + latest + + + bin\Debug\ + false + 285212672 + false + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + false + 4 + full + prompt + AllRules.ruleset + false + Off + latest + + + bin\Release\ + false + 285212672 + false + + + TRACE + bin\Release\umbraco.xml + true + 4096 + false + + + true + false + false + false + 4 + pdbonly + prompt + AllRules.ruleset + false + Off + + + + {07fbc26b-2927-4a22-8d96-d644c667fecc} + UmbracoExamine + + + ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.dll + True + + + ..\packages\AutoMapper.3.3.1\lib\net40\AutoMapper.Net4.dll + True + + + ..\packages\ClientDependency.1.9.7\lib\net45\ClientDependency.Core.dll + + + ..\packages\dotless.1.5.2\lib\dotless.Core.dll + + + ..\packages\Examine.0.1.90\lib\net45\Examine.dll + + + ..\packages\HtmlAgilityPack.1.8.8\lib\Net45\HtmlAgilityPack.dll + + + ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll + + + ..\packages\Lucene.Net.2.9.4.1\lib\net40\Lucene.Net.dll + + + ..\packages\Markdown.1.14.7\lib\net45\MarkdownSharp.dll + True + + + ..\packages\Microsoft.AspNet.Identity.Core.2.2.2\lib\net45\Microsoft.AspNet.Identity.Core.dll + + + ..\packages\Microsoft.AspNet.Identity.Owin.2.2.2\lib\net45\Microsoft.AspNet.Identity.Owin.dll + + + ..\packages\Microsoft.AspNet.SignalR.Core.2.4.1\lib\net45\Microsoft.AspNet.SignalR.Core.dll + + + + ..\packages\Microsoft.Owin.4.0.1\lib\net45\Microsoft.Owin.dll + + + ..\packages\Microsoft.Owin.Host.SystemWeb.4.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + + + ..\packages\Microsoft.Owin.Security.4.0.1\lib\net45\Microsoft.Owin.Security.dll + + + ..\packages\Microsoft.Owin.Security.Cookies.4.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll + + + ..\packages\Microsoft.Owin.Security.OAuth.4.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll + + + ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + True + + + ..\packages\MiniProfiler.2.1.0\lib\net40\MiniProfiler.dll + True + + + ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + ..\packages\semver.1.1.2\lib\net451\Semver.dll + + + System + + + + + + System.Data + + + + + System.Drawing + + + + + + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll + + + + + + ..\packages\System.Threading.Tasks.Dataflow.4.9.0\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll + + + + 3.5 + + + + + + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.Helpers.dll + + + ..\packages\Microsoft.AspNet.WebApi.Core.5.2.7\lib\net45\System.Web.Http.dll + + + ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.7\lib\net45\System.Web.Http.WebHost.dll + + + ..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll + + + ..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll + + + System.Web.Services + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.dll + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Deployment.dll + + + ..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Razor.dll + + + + System.XML + + + + {5BA5425F-27A7-4677-865E-82246498AA2E} + SqlCE4Umbraco + + + {31785BC3-256C-4613-B2F5-A1B0BDDED8C1} + Umbraco.Core + + + {6EDD2061-82F2-461B-BB6E-879245A832DE} + umbraco.controls + + + umbraco.businesslogic + {E469A9CE-1BEC-423F-AC44-713CD72457EA} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + {CCD75EC3-63DB-4184-B49D-51C1DD337230} + umbraco.cms + + + {C7CB79F0-1C97-4B33-BFA7-00731B579AE2} + umbraco.datalayer + + + umbraco.interfaces + {511F6D8D-7717-440A-9A57-A507E9A8B27F} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + {D7636876-0756-43CB-A192-138C6F0D5E42} + umbraco.providers + + + + + Properties\SolutionInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + True + True + Reference.map + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + AssignDomain2.aspx + ASPXCodeBehind + + + AssignDomain2.aspx + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + True + True + Strings.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + + Code + + + Code + + + Code + + + Code + + + Code + + + + Code + + + True + True + Settings.settings + + + Code + + + + + Code + + + Code + + + Code + + + Code + + + + ASPXCodeBehind + + + Code + + + Code + + + Code + + + + delete.aspx + ASPXCodeBehind + + + delete.aspx + + + editContent.aspx + ASPXCodeBehind + + + editContent.aspx + + + preview.aspx + ASPXCodeBehind + + + preview.aspx + + + publish.aspx + ASPXCodeBehind + + + publish.aspx + + + + + + ASPXCodeBehind + + + + + + + ProgressBar.ascx + ASPXCodeBehind + + + ProgressBar.ascx + + + + ASPXCodeBehind + + + Component + + + + + Code + + + + + + + + + + FeedProxy.aspx + ASPXCodeBehind + + + FeedProxy.aspx + + + EditRelationType.aspx + ASPXCodeBehind + + + EditRelationType.aspx + + + NewRelationType.aspx + ASPXCodeBehind + + + NewRelationType.aspx + + + + RelationTypesWebService.asmx + Component + + + + + Preview.aspx + ASPXCodeBehind + + + Preview.aspx + + + MemberSearch.ascx + ASPXCodeBehind + + + MemberSearch.ascx + + + + + + xsltVisualize.aspx + ASPXCodeBehind + + + xsltVisualize.aspx + + + insertMasterpageContent.aspx + ASPXCodeBehind + + + insertMasterpageContent.aspx + + + insertMasterpagePlaceholder.aspx + ASPXCodeBehind + + + insertMasterpagePlaceholder.aspx + + + republish.aspx + ASPXCodeBehind + + + republish.aspx + + + search.aspx + ASPXCodeBehind + + + search.aspx + + + SendPublish.aspx + ASPXCodeBehind + + + SendPublish.aspx + + + Code + + + Code + + + assemblyBrowser.aspx + ASPXCodeBehind + + + assemblyBrowser.aspx + + + editPackage.aspx + ASPXCodeBehind + + + editPackage.aspx + + + getXsltStatus.asmx + Component + + + xsltChooseExtension.aspx + ASPXCodeBehind + + + xsltChooseExtension.aspx + + + xsltInsertValueOf.aspx + ASPXCodeBehind + + + xsltInsertValueOf.aspx + + + exportDocumenttype.aspx + ASPXCodeBehind + + + importDocumenttype.aspx + ASPXCodeBehind + + + rollBack.aspx + ASPXCodeBehind + + + rollBack.aspx + + + sendToTranslation.aspx + ASPXCodeBehind + + + sendToTranslation.aspx + + + viewAuditTrail.aspx + ASPXCodeBehind + + + viewAuditTrail.aspx + + + EditMemberGroup.aspx + ASPXCodeBehind + + + EditMemberGroup.aspx + + + search.aspx + ASPXCodeBehind + + + search.aspx + + + ViewMembers.aspx + ASPXCodeBehind + + + ViewMembers.aspx + + + Code + + + + + + + + + + tinymce3tinymceCompress.aspx + ASPXCodeBehind + + + tinymce3tinymceCompress.aspx + + + QuickSearchHandler.ashx + + + DictionaryItemList.aspx + ASPXCodeBehind + + + DictionaryItemList.aspx + + + editLanguage.aspx + ASPXCodeBehind + + + editLanguage.aspx + + + + + + Code + + + + + + + + + + + + + + + + + MacroContainerService.asmx + Component + + + TagsAutoCompleteHandler.ashx + + + TreeClientService.asmx + Component + + + + + + + + True + True + Resources.resx + + + default.aspx + ASPXCodeBehind + + + default.aspx + + + details.aspx + ASPXCodeBehind + + + details.aspx + + + preview.aspx + ASPXCodeBehind + + + preview.aspx + + + xml.aspx + ASPXCodeBehind + + + xml.aspx + + + ASPXCodeBehind + + + + + + + + + + + + + + + + + + + + + + + + + + + + TreeDataService.ashx + + + + + + + XmlTree.xsd + + + + + CacheRefresher.asmx + Component + + + CheckForUpgrade.asmx + Component + + + CMSNode.asmx + Component + + + codeEditorSave.asmx + Component + + + legacyAjaxCalls.asmx + Component + + + nodeSorter.asmx + Component + + + progressStatus.asmx + Component + + + publication.asmx + Component + + + + ASPXCodeBehind + + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + uQuery.cs + + + + + + + + True + True + Reference.map + + + + + + + + + + Component + + + + Component + + + + + Mvc\web.config + + + + MSDiscoCodeGenerator + Reference.cs + + + + + + + + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + ASPXCodeBehind + + + + + + ASPXCodeBehind + + + + + + + + ASPXCodeBehind + + + + + ASPXCodeBehind + + + + + + + + + Reference.map + + + Reference.map + + + SettingsSingleFileGenerator + Settings1.Designer.cs + + + + + + ASPXCodeBehind + + + + + + + + + + Form + + + Designer + + + + + Form + + + + + + + Form + + + + + + + + + + XmlTree.xsd + + + + + + MSDiscoCodeGenerator + Reference.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + ResXFileCodeGenerator + Strings.Designer.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + + + + + package.xsd + + + umbraco.xsd + + + umbraco.xsd + + + umbraco.xsd + + + + + + + + Dynamic + Web References\org.umbraco.our\ + https://our.umbraco.com/umbraco/webservices/api/repository.asmx + + + + + Settings + umbraco_org_umbraco_our_Repository + + + Dynamic + Web References\org.umbraco.update\ + http://update.umbraco.org/checkforupgrade.asmx + + + + + Settings + umbraco_org_umbraco_update_CheckForUpgrade + + + + + + + 11.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v15.0 + + + + + + + + + + + + + + + \ No newline at end of file From d3b4b9fe64aedbc21a9b338834ceb97f4b1029e3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 3 Jul 2019 11:10:34 +1000 Subject: [PATCH 094/776] fix merge --- src/Umbraco.Web/Editors/EntityController.cs | 23 +-------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 822a5bdab1..6ee16ae1ee 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -621,25 +621,6 @@ namespace Umbraco.Web.Editors } } - private int[] GetStartNodes(UmbracoEntityTypes type) - { - int[] startNodes = null; - switch (type) - { - case UmbracoEntityTypes.Document: - startNodes = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); - break; - case UmbracoEntityTypes.Media: - startNodes = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); - break; - default: - startNodes = Array.Empty(); - break; - } - - return startNodes; - } - private int[] GetStartNodes(UmbracoEntityTypes type) { switch (type) @@ -649,12 +630,10 @@ namespace Umbraco.Web.Editors case UmbracoEntityTypes.Media: return Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); default: - return new int[0]; + return Array.Empty(); } - } - public PagedResult GetPagedDescendants( int id, UmbracoEntityTypes type, From df11935598cd33779bedefa2ed28d942e66b219d Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 3 Jul 2019 11:23:25 +1000 Subject: [PATCH 095/776] Revert and adjusts "Fixes issue when creating DateTypes of type Umbraco.MultiNodeTreePicker with type member, because `-1` can't be casted to `Udi`" --- .../prevalueeditors/treesource.controller.js | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js index fb2dddd72c..404abbab1f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js @@ -55,11 +55,25 @@ angular.module('umbraco') }; $scope.clear = function() { - $scope.model.value.id = undefined; - $scope.node = undefined; - $scope.model.value.query = undefined; + $scope.model.value.id = null; + $scope.node = null; + $scope.model.value.query = null; }; + + //we always need to ensure we dont submit anything broken + var unsubscribe = $scope.$on("formSubmitting", function (ev, args) { + if($scope.model.value.type === "member"){ + $scope.model.value.id = null; + $scope.model.value.query = ""; + } + }); + + //when the scope is destroyed we need to unsubscribe + $scope.$on('$destroy', function () { + unsubscribe(); + }); + function populate(item){ $scope.clear(); item.icon = iconHelper.convertFromLegacyIcon(item.icon); From f7382255c2628cdb867effd24d5582503dc05c82 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 3 Jul 2019 13:16:40 +1000 Subject: [PATCH 096/776] Fixes N+1 Issues caused by the new bypass start nodes changes --- src/Umbraco.Core/Models/PropertyType.cs | 8 +++++++ .../Factories/PropertyGroupFactory.cs | 1 + .../Implement/ContentTypeCommonRepository.cs | 23 +++++++++++++++++++ .../Implement/ContentTypeRepositoryBase.cs | 4 +++- .../Implement/MemberTypeRepository.cs | 1 + .../ContentEditing/ContentPropertyBasic.cs | 6 +++-- .../ContentEditing/PropertyTypeBasic.cs | 5 ++++ .../Mapping/ContentPropertyBasicMapper.cs | 8 +------ .../Mapping/ContentTypeMapDefinition.cs | 3 +++ .../Models/Mapping/PropertyTypeGroupMapper.cs | 1 + 10 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 1e950a876c..34775ccf89 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -21,6 +21,7 @@ namespace Umbraco.Core.Models private string _alias; private string _description; private int _dataTypeId; + private Guid _dataTypeKey; private Lazy _propertyGroupId; private string _propertyEditorAlias; private ValueStorageType _valueStorageType; @@ -139,6 +140,13 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _dataTypeId, nameof(DataTypeId)); } + [DataMember] + public Guid DataTypeKey + { + get => _dataTypeKey; + set => SetPropertyValueAndDetectChanges(value, ref _dataTypeKey, nameof(DataTypeKey)); + } + /// /// Gets or sets the alias of the property editor for this property type. /// diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs index c134748047..db8e2b20d9 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs @@ -54,6 +54,7 @@ namespace Umbraco.Core.Persistence.Factories propertyType.Alias = typeDto.Alias; propertyType.DataTypeId = typeDto.DataTypeId; + propertyType.DataTypeKey = typeDto.DataTypeDto.NodeDto.UniqueId; propertyType.Description = typeDto.Description; propertyType.Id = typeDto.Id; propertyType.Key = typeDto.UniqueId; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs index ccafb9f771..5ecd041bc5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Scoping; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -189,9 +190,30 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var sql2 = Sql() .Select(r => r.Select(x => x.DataTypeDto)) + + //TODO: Why doesn't this overload have the ability to auto alias columns like the inner select above, instead we need to manually apply all aliases + //QUESTION: Why doesn't this work? but the below `.AndSelect()` works? The problem now is that the output SQL has duplicate column names. + // NPoco seems to be able to map this correctly but it would be better to have aliased columns like below. These alias names seem to follow some sort + // of convention with the double underscore and these columns are in the exact same order as the auto-produced ones, however NPoco does not map these + // columns? + //.AndSelect( + // x => Alias(x.NodeId, "NodeDto__NodeId"), + // x => Alias(x.UniqueId, "NodeDto__UniqueId"), + // x => Alias(x.ParentId, "NodeDto__ParentId"), + // x => Alias(x.Level, "NodeDto__Level"), + // x => Alias(x.Path, "NodeDto__Path"), + // x => Alias(x.SortOrder, "NodeDto__SortOrder"), + // x => Alias(x.Trashed, "NodeDto__Trashed"), + // x => Alias(x.UserId, "NodeDto__UserId"), + // x => Alias(x.Text, "NodeDto__Text"), + // x => Alias(x.NodeObjectType, "NodeDto__NodeObjectType"), + // x => Alias(x.CreateDate, "NodeDto__CreateDate")) + .AndSelect() + .AndSelect() .From() .InnerJoin().On((pt, dt) => pt.DataTypeId == dt.NodeId) + .InnerJoin().On((dt, n) => dt.NodeId == n.NodeId) .InnerJoin().On((pt, ct) => pt.ContentTypeId == ct.NodeId) .LeftJoin().On((pt, ptg) => pt.PropertyTypeGroupId == ptg.Id) .LeftJoin().On((pt, mpt) => pt.Id == mpt.PropertyTypeId) @@ -290,6 +312,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { Description = dto.Description, DataTypeId = dto.DataTypeId, + DataTypeKey = dto.DataTypeDto.NodeDto.UniqueId, Id = dto.Id, Key = dto.UniqueId, Mandatory = dto.Mandatory, diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 591fa2b660..22c9244d8f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -1013,8 +1013,9 @@ AND umbracoNode.id <> @id", if (propertyType.PropertyEditorAlias.IsNullOrWhiteSpace() == false) { var sql = Sql() - .SelectAll() + .Select(dt => dt.Select(x => x.NodeDto)) .From() + .InnerJoin().On((dt, n) => dt.NodeId == n.NodeId) .Where("propertyEditorAlias = @propertyEditorAlias", new { propertyEditorAlias = propertyType.PropertyEditorAlias }) .OrderBy(typeDto => typeDto.NodeId); var datatype = Database.FirstOrDefault(sql); @@ -1022,6 +1023,7 @@ AND umbracoNode.id <> @id", if (datatype != null) { propertyType.DataTypeId = datatype.NodeId; + propertyType.DataTypeKey = datatype.NodeDto.UniqueId; } else { diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs index 61981a42e3..d96854743e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs @@ -225,6 +225,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { //this reset's its current data type reference which will be re-assigned based on the property editor assigned on the next line propertyType.DataTypeId = 0; + propertyType.DataTypeKey = default; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs index 7d6d066e35..f0f5bba7c4 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs @@ -22,8 +22,10 @@ namespace Umbraco.Web.Models.ContentEditing [Required] public int Id { get; set; } - [DataMember(Name = "dataTypeId", IsRequired = false)] - public Guid? DataTypeId { get; set; } + //fixme: This name dataTypeId is inconsistent, but requires us to change it everywhere in angular + [DataMember(Name = "dataTypeId", IsRequired = false)] + [ReadOnly(true)] + public Guid DataTypeKey { get; set; } [DataMember(Name = "value")] public object Value { get; set; } diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeBasic.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeBasic.cs index cde9d0dabc..d180a68a2c 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeBasic.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; @@ -43,6 +44,10 @@ namespace Umbraco.Web.Models.ContentEditing [Required] public int DataTypeId { get; set; } + [DataMember(Name = "dataTypeKey")] + [ReadOnly(true)] + public Guid DataTypeKey { get; set; } + //SD: Is this really needed ? [DataMember(Name = "groupId")] public int GroupId { get; set; } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs index 57a06c2175..36c1b360b2 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs @@ -50,13 +50,7 @@ namespace Umbraco.Web.Models.Mapping dest.Alias = property.Alias; dest.PropertyEditor = editor; dest.Editor = editor.Alias; - - //fixme: although this might get cached, if a content item has 100 properties of different data types, then this means this is going to be 100 extra DB queries :( :( :( - // - ideally, we'd just have the DataTypeKey alongside the DataTypeId which is loaded in the single sql statement which should be relatively easy. - var dataTypeKey = _entityService.GetKey(property.PropertyType.DataTypeId, UmbracoObjectTypes.DataType); - if (!dataTypeKey.Success) - throw new InvalidOperationException("Can't get the unique key from the id: " + property.PropertyType.DataTypeId); - dest.DataTypeId = dataTypeKey.Result; + dest.DataTypeKey = property.PropertyType.DataTypeKey; // if there's a set of property aliases specified, we will check if the current property's value should be mapped. // if it isn't one of the ones specified in 'includeProperties', we will just return the result without mapping the Value. diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs index a438f04781..fc029eabe4 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs @@ -219,6 +219,7 @@ namespace Umbraco.Web.Models.Mapping { target.Name = source.Label; target.DataTypeId = source.DataTypeId; + target.DataTypeKey = source.DataTypeKey; target.Mandatory = source.Validation.Mandatory; target.ValidationRegExp = source.Validation.Pattern; target.Variations = source.AllowCultureVariant ? ContentVariation.Culture : ContentVariation.Nothing; @@ -334,6 +335,7 @@ namespace Umbraco.Web.Models.Mapping target.Alias = source.Alias; target.AllowCultureVariant = source.AllowCultureVariant; target.DataTypeId = source.DataTypeId; + target.DataTypeKey = source.DataTypeKey; target.Description = source.Description; target.GroupId = source.GroupId; target.Id = source.Id; @@ -349,6 +351,7 @@ namespace Umbraco.Web.Models.Mapping target.Alias = source.Alias; target.AllowCultureVariant = source.AllowCultureVariant; target.DataTypeId = source.DataTypeId; + target.DataTypeKey = source.DataTypeKey; target.Description = source.Description; target.GroupId = source.GroupId; target.Id = source.Id; diff --git a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs index 8c5f347799..a184ac92cf 100644 --- a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs @@ -231,6 +231,7 @@ namespace Umbraco.Web.Models.Mapping GroupId = groupId, Inherited = inherited, DataTypeId = p.DataTypeId, + DataTypeKey = p.DataTypeKey, SortOrder = p.SortOrder, ContentTypeId = contentType.Id, ContentTypeName = contentType.Name, From cd6ef35bf9e33f47d2ea888fe9e63502b45a25fb Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 3 Jul 2019 15:17:51 +1000 Subject: [PATCH 097/776] comments out the fix during investigation - adds some notes --- .../ILivePublishedModelFactory.cs | 4 ++ .../PublishedModelFactoryExtensions.cs | 3 +- .../Cache/ContentTypeCacheRefresher.cs | 40 +++++++++---------- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs index 4027184f3c..0810f2207b 100644 --- a/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/ILivePublishedModelFactory.cs @@ -13,6 +13,10 @@ /// /// Refreshes the factory. /// + /// + /// This will typically re-compiled models/classes into a new DLL that are used to populate the cache. + /// This is called prior to refreshing the cache. + /// void Refresh(); } } diff --git a/src/Umbraco.Core/PublishedModelFactoryExtensions.cs b/src/Umbraco.Core/PublishedModelFactoryExtensions.cs index 4e026490a4..d428accbbf 100644 --- a/src/Umbraco.Core/PublishedModelFactoryExtensions.cs +++ b/src/Umbraco.Core/PublishedModelFactoryExtensions.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core public static class PublishedModelFactoryExtensions { /// - /// Executes an action with a safe live factory/ + /// Executes an action with a safe live factory /// /// /// If the factory is a live factory, ensures it is refreshed and locked while executing the action. @@ -20,6 +20,7 @@ namespace Umbraco.Core { lock (liveFactory.SyncRoot) { + //Call refresh on the live factory to re-compile the models liveFactory.Refresh(); action(); } diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index a5134426c2..75f2889cab 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -53,15 +53,15 @@ namespace Umbraco.Web.Cache _contentTypeCommonRepository.ClearCache(); // always - // We need to special handle the IContentType if modelsbuilder is in live mode, because all models are updated when a IContentType is changed, we need to clear all from cache also. - if (_publishedModelFactory is ILivePublishedModelFactory && payloads.Any(x => x.ItemType == typeof(IContentType).Name)) - { - //This is super nasty, and we need to figure out a better way to to this - //Ensure all doc type ids is part of the payload - var missingPayloads = GetMissingContentTypePayloads(payloads); + //// We need to special handle the IContentType if modelsbuilder is in live mode, because all models are updated when a IContentType is changed, we need to clear all from cache also. + //if (_publishedModelFactory is ILivePublishedModelFactory && payloads.Any(x => x.ItemType == typeof(IContentType).Name)) + //{ + // //This is super nasty, and we need to figure out a better way to to this + // //Ensure all doc type ids is part of the payload + // var missingPayloads = GetMissingContentTypePayloads(payloads); - payloads = payloads.Union(missingPayloads).ToArray(); - } + // payloads = payloads.Union(missingPayloads).ToArray(); + //} if (payloads.Any(x => x.ItemType == typeof(IContentType).Name)) { @@ -109,19 +109,19 @@ namespace Umbraco.Web.Cache base.Refresh(payloads); } - private IEnumerable GetMissingContentTypePayloads(JsonPayload[] payloads) - { - var existingPayloadIds = new HashSet(payloads.Select(x => x.Id)); - var contentTypeIds = _contentTypeService.GetAll().Select(x => x.Id).ToArray(); + //private IEnumerable GetMissingContentTypePayloads(JsonPayload[] payloads) + //{ + // var existingPayloadIds = new HashSet(payloads.Select(x => x.Id)); + // var contentTypeIds = _contentTypeService.GetAll().Select(x => x.Id).ToArray(); - foreach (var contentTypeId in contentTypeIds) - { - if (!existingPayloadIds.Contains(contentTypeId)) - { - yield return new JsonPayload(typeof(IContentType).Name, contentTypeId, ContentTypeChangeTypes.RefreshOther); - } - } - } + // foreach (var contentTypeId in contentTypeIds) + // { + // if (!existingPayloadIds.Contains(contentTypeId)) + // { + // yield return new JsonPayload(typeof(IContentType).Name, contentTypeId, ContentTypeChangeTypes.RefreshOther); + // } + // } + //} public override void RefreshAll() { From d0895186818b550c266ad3966a71fe7a0f0dec26 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 3 Jul 2019 15:40:18 +1000 Subject: [PATCH 098/776] Fixes SQL generation to populate the nodedto object of the datatypedto --- .../Implement/ContentTypeCommonRepository.cs | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs index 5ecd041bc5..6b751eb8ff 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs @@ -189,27 +189,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var groupDtos = Database.Fetch(sql1); var sql2 = Sql() - .Select(r => r.Select(x => x.DataTypeDto)) - - //TODO: Why doesn't this overload have the ability to auto alias columns like the inner select above, instead we need to manually apply all aliases - //QUESTION: Why doesn't this work? but the below `.AndSelect()` works? The problem now is that the output SQL has duplicate column names. - // NPoco seems to be able to map this correctly but it would be better to have aliased columns like below. These alias names seem to follow some sort - // of convention with the double underscore and these columns are in the exact same order as the auto-produced ones, however NPoco does not map these - // columns? - //.AndSelect( - // x => Alias(x.NodeId, "NodeDto__NodeId"), - // x => Alias(x.UniqueId, "NodeDto__UniqueId"), - // x => Alias(x.ParentId, "NodeDto__ParentId"), - // x => Alias(x.Level, "NodeDto__Level"), - // x => Alias(x.Path, "NodeDto__Path"), - // x => Alias(x.SortOrder, "NodeDto__SortOrder"), - // x => Alias(x.Trashed, "NodeDto__Trashed"), - // x => Alias(x.UserId, "NodeDto__UserId"), - // x => Alias(x.Text, "NodeDto__Text"), - // x => Alias(x.NodeObjectType, "NodeDto__NodeObjectType"), - // x => Alias(x.CreateDate, "NodeDto__CreateDate")) - .AndSelect() - + .Select(r => r.Select(x => x.DataTypeDto, r1 => r1.Select(x => x.NodeDto))) .AndSelect() .From() .InnerJoin().On((pt, dt) => pt.DataTypeId == dt.NodeId) From c5f1cc15fdb37c00c4c9460a48e0cf1f1b23df7c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 3 Jul 2019 08:33:30 +0200 Subject: [PATCH 099/776] Refactored `dataTypeId` when a guid into `dataTypeKey` --- .../components/grid/grid.rte.directive.js | 4 +-- .../tree/umbtreesearchbox.directive.js | 6 ++-- .../src/common/resources/entity.resource.js | 20 ++++++------ .../src/common/services/search.service.js | 4 +-- .../src/common/services/tinymce.service.js | 4 +-- .../linkpicker/linkpicker.controller.js | 6 ++-- .../linkpicker/linkpicker.html | 32 +++++++++---------- .../mediapicker/mediapicker.controller.js | 6 ++-- .../treepicker/treepicker.controller.js | 8 ++--- .../treepicker/treepicker.html | 22 ++++++------- .../contentpicker/contentpicker.controller.js | 6 ++-- .../grid/editors/media.controller.js | 2 +- .../propertyeditors/grid/editors/rte.html | 2 +- .../mediapicker/mediapicker.controller.js | 2 +- .../multiurlpicker.controller.js | 2 +- src/Umbraco.Web/Editors/EntityController.cs | 24 +++++++------- .../ContentEditing/ContentPropertyBasic.cs | 3 +- .../Trees/ContentTreeControllerBase.cs | 8 ++--- .../Trees/TreeQueryStringParameters.cs | 2 +- 19 files changed, 81 insertions(+), 82 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js index 8e561ff5ca..cd1f011018 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js @@ -5,7 +5,7 @@ angular.module("umbraco.directives") uniqueId: '=', value: '=', configuration: "=", //this is the RTE configuration - datatypeId: '@', + datatypeKey: '@', ignoreUserStartNodes: '@' }, templateUrl: 'views/components/grid/grid-rte.html', @@ -43,7 +43,7 @@ angular.module("umbraco.directives") scope.config = { ignoreUserStartNodes: scope.ignoreUserStartNodes === "true" } - scope.dataTypeId = scope.datatypeId; //Yes - this casing is rediculous, but it's because the var starts with `data` so it can't be `data-type-id` :/ + scope.dataTypeKey = scope.datatypeKey; //Yes - this casing is rediculous, but it's because the var starts with `data` so it can't be `data-type-id` :/ //stores a reference to the editor var tinyMceEditor = null; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js index 3b6fb5f9cd..9c28cbe367 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js @@ -12,7 +12,7 @@ function treeSearchBox(localizationService, searchService, $q) { searchFromName: "@", showSearch: "@", section: "@", - datatypeId: "@", + datatypeKey: "@", hideSearchCallback: "=", searchCallback: "=" }, @@ -63,8 +63,8 @@ function treeSearchBox(localizationService, searchService, $q) { } //append dataTypeId value if there is one - if (scope.datatypeId) { - searchArgs["dataTypeId"] = scope.datatypeId; + if (scope.datatypeKey) { + searchArgs["dataTypeKey"] = scope.datatypeKey; } searcher(searchArgs).then(function (data) { diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 01bb2369dc..69522f3fa4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -327,8 +327,8 @@ function entityResource($q, $http, umbRequestHelper) { { type: type }, { culture: culture} ]; - if (options && options.dataTypeId) { - args.push({ dataTypeId: options.dataTypeId }); + if (options && options.dataTypeKey) { + args.push({ dataTypeKey: options.dataTypeKey }); } return umbRequestHelper.resourcePromise( @@ -356,8 +356,8 @@ function entityResource($q, $http, umbRequestHelper) { getChildren: function (id, type, options) { var args = [{ id: id }, { type: type }]; - if (options && options.dataTypeId) { - args.push({ dataTypeId: options.dataTypeId }); + if (options && options.dataTypeKey) { + args.push({ dataTypeKey: options.dataTypeKey }); } return umbRequestHelper.resourcePromise( @@ -434,7 +434,7 @@ function entityResource($q, $http, umbRequestHelper) { orderBy: options.orderBy, orderDirection: options.orderDirection, filter: encodeURIComponent(options.filter), - dataTypeId: options.dataTypeId + dataTypeKey: options.dataTypeKey } )), 'Failed to retrieve child data for id ' + parentId); @@ -476,7 +476,7 @@ function entityResource($q, $http, umbRequestHelper) { filter: '', orderDirection: "Ascending", orderBy: "SortOrder", - dataTypeId: null + dataTypeKey: null }; if (options === undefined) { options = {}; @@ -506,7 +506,7 @@ function entityResource($q, $http, umbRequestHelper) { orderBy: options.orderBy, orderDirection: options.orderDirection, filter: encodeURIComponent(options.filter), - dataTypeId: options.dataTypeId + dataTypeKey: options.dataTypeKey } )), 'Failed to retrieve child data for id ' + parentId); @@ -535,15 +535,15 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity array. * */ - search: function (query, type, searchFrom, canceler, dataTypeId) { + search: function (query, type, searchFrom, canceler, dataTypeKey) { var args = [{ query: query }, { type: type }]; if (searchFrom) { args.push({ searchFrom: searchFrom }); } - if (dataTypeId) { - args.push({ dataTypeId: dataTypeId }); + if (dataTypeKey) { + args.push({ dataTypeKey: dataTypeKey }); } var httpConfig = {}; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js index cdba3647a3..fef286ec7e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js @@ -67,7 +67,7 @@ angular.module('umbraco.services') throw "args.term is required"; } - return entityResource.search(args.term, "Document", args.searchFrom, args.canceler, args.dataTypeId).then(function (data) { + return entityResource.search(args.term, "Document", args.searchFrom, args.canceler, args.dataTypeKey).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureContentResult(item); }); @@ -92,7 +92,7 @@ angular.module('umbraco.services') throw "args.term is required"; } - return entityResource.search(args.term, "Media", args.searchFrom, args.canceler, args.dataTypeId).then(function (data) { + return entityResource.search(args.term, "Media", args.searchFrom, args.canceler, args.dataTypeKey).then(function (data) { _.each(data, function (item) { searchResultFormatter.configureMediaResult(item); }); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index d3bdceddc4..e61bd38bc0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -1121,7 +1121,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s entityResource.getAnchors(args.model.value).then(function (anchorValues) { var linkPicker = { currentTarget: currentTarget, - dataTypeId: args.model.dataTypeId, + dataTypeKey: args.model.dataTypeKey, ignoreUserStartNodes: args.model.config.ignoreUserStartNodes, anchors: anchorValues, submit: function (model) { @@ -1159,7 +1159,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s disableFolderSelect: true, startNodeId: startNodeId, startNodeIsVirtual: startNodeIsVirtual, - dataTypeId: args.model.dataTypeId, + dataTypeKey: args.model.dataTypeKey, submit: function (model) { self.insertMediaInEditor(args.editor, model.selection[0]); editorService.close(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js index 5fd23f0dd6..5a0ab51fd0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js @@ -20,14 +20,14 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", $scope.model.title = value; }); } - $scope.customTreeParams = dialogOptions.dataTypeId ? "dataTypeId=" + dialogOptions.dataTypeId : ""; + $scope.customTreeParams = dialogOptions.dataTypeKey ? "dataTypeKey=" + dialogOptions.dataTypeKey : ""; $scope.dialogTreeApi = {}; $scope.model.target = {}; $scope.searchInfo = { searchFromId: null, searchFromName: null, showSearch: false, - dataTypeId: dialogOptions.dataTypeId, + dataTypeKey: dialogOptions.dataTypeKey, results: [], selectedSearchResults: [] }; @@ -175,7 +175,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", var mediaPicker = { startNodeId: startNodeId, startNodeIsVirtual: startNodeIsVirtual, - dataTypeId: dialogOptions.dataTypeId, + dataTypeKey: dialogOptions.dataTypeKey, submit: function (model) { var media = model.selection[0]; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html index e1880d5ac5..704b61e333 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html @@ -25,7 +25,7 @@ ng-model="model.target.url" ng-disabled="model.target.id || model.target.udi" /> - + - + - + - +
Link to page
- +
- - +
- - - +
-
- - - + - +
Link to media
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index 481f15f647..bcda921269 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -57,7 +57,7 @@ angular.module("umbraco") totalItems: 0, totalPages: 0, filter: '', - dataTypeId: $scope.model.dataTypeId + dataTypeKey: $scope.model.dataTypeKey }; //preload selected item @@ -160,7 +160,7 @@ angular.module("umbraco") } if (folder.id > 0) { - entityResource.getAncestors(folder.id, "media", null, { dataTypeId: $scope.model.dataTypeId }) + entityResource.getAncestors(folder.id, "media", null, { dataTypeKey: $scope.model.dataTypeKey }) .then(function (anc) { $scope.path = _.filter(anc, function (f) { @@ -318,7 +318,7 @@ angular.module("umbraco") totalItems: 0, totalPages: 0, filter: '', - dataTypeId: $scope.model.dataTypeId + dataTypeKey: $scope.model.dataTypeKey }; getChildren($scope.currentFolder.id); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js index 88caac716a..31430c81cb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js @@ -28,12 +28,12 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", vm.treeAlias = $scope.model.treeAlias; vm.multiPicker = $scope.model.multiPicker; vm.hideHeader = (typeof $scope.model.hideHeader) === "boolean" ? $scope.model.hideHeader : true; - vm.dataTypeId = $scope.model.dataTypeId; + vm.dataTypeKey = $scope.model.dataTypeKey; vm.searchInfo = { searchFromId: $scope.model.startNodeId, searchFromName: null, showSearch: false, - dataTypeId: vm.dataTypeId, + dataTypeKey: vm.dataTypeKey, results: [], selectedSearchResults: [] } @@ -192,8 +192,8 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", if (vm.selectedLanguage && vm.selectedLanguage.id) { queryParams["culture"] = vm.selectedLanguage.culture; } - if (vm.dataTypeId) { - queryParams["dataTypeId"] = vm.dataTypeId; + if (vm.dataTypeKey) { + queryParams["dataTypeKey"] = vm.dataTypeKey; } var queryString = $.param(queryParams); //create the query string from the params object diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html index 01c14a284f..78c75f6f8d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.html @@ -27,7 +27,7 @@ {{language.name}}
- +
- + - + {{ vm.emptyStateMessage }} - +
-
- + - - - + - + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 7fafa20b0f..6667c3b539 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -86,7 +86,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper showOpenButton: false, showEditButton: false, showPathOnHover: false, - dataTypeId: null, + dataTypeKey: null, maxNumber: 1, minNumber: 0, startNode: { @@ -140,7 +140,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper entityType: entityType, filterCssClass: "not-allowed not-published", startNodeId: null, - dataTypeId: $scope.model.dataTypeId, + dataTypeKey: $scope.model.dataTypeKey, currentNode: editorState ? editorState.current : null, callback: function (data) { if (angular.isArray(data)) { @@ -162,7 +162,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper // pre-value config on to the dialog options angular.extend(dialogOptions, $scope.model.config); - dialogOptions.dataTypeId = $scope.model.dataTypeId; + dialogOptions.dataTypeKey = $scope.model.dataTypeKey; // if we can't pick more than one item, explicitly disable multiPicker in the dialog options if ($scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) === 1) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index 4e6c87d7a8..574aa87557 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -30,7 +30,7 @@ angular.module("umbraco") showDetails: true, disableFolderSelect: true, onlyImages: true, - dataTypeId: $scope.model.dataTypeId, + dataTypeKey: $scope.model.dataTypeKey, submit: function(model) { var selectedImage = model.selection[0]; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.html index 63bc7570e1..7bfc6d125e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.html @@ -4,7 +4,7 @@ configuration="model.config.rte" value="control.value" unique-id="control.$uniqueId" - datatype-id="{{model.dataTypeId}}" + datatype-key="{{model.dataTypeKey}}" ignore-user-start-nodes="{{model.config.ignoreUserStartNodes}}"> 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 41c1fa8c16..f7e88bef36 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 @@ -187,7 +187,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl var mediaPicker = { startNodeId: $scope.model.config.startNodeId, startNodeIsVirtual: $scope.model.config.startNodeIsVirtual, - dataTypeId: $scope.model.dataTypeId, + dataTypeKey: $scope.model.dataTypeKey, multiPicker: multiPicker, onlyImages: onlyImages, disableFolderSelect: disableFolderSelect, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js index abaee3fca0..69bcc6b41a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js @@ -77,7 +77,7 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en var linkPicker = { currentTarget: target, - dataTypeId: $scope.model.dataTypeId, + dataTypeKey: $scope.model.dataTypeKey, ignoreUserStartNodes : $scope.model.config.ignoreUserStartNodes, submit: function (model) { if (model.target.url || model.target.anchor) { diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 6ee16ae1ee..b3edb308a2 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -104,10 +104,10 @@ namespace Umbraco.Web.Editors /// /// A starting point for the search, generally a node id, but for members this is a member type alias /// - /// If set used to look up whether user and group start node permissions will be ignored. + /// If set used to look up whether user and group start node permissions will be ignored. /// [HttpGet] - public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null, Guid? dataTypeId = null) + public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null, Guid? dataTypeKey = null) { // NOTE: Theoretically you shouldn't be able to see member data if you don't have access to members right? ... but there is a member picker, so can't really do that @@ -116,7 +116,7 @@ namespace Umbraco.Web.Editors //TODO: This uses the internal UmbracoTreeSearcher, this instead should delgate to the ISearchableTree implementation for the type - var ignoreUserStartNodes = dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); + var ignoreUserStartNodes = dataTypeKey.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value); return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes); } @@ -424,7 +424,7 @@ namespace Umbraco.Web.Editors } #endregion - public IEnumerable GetChildren(int id, UmbracoEntityTypes type, Guid? dataTypeId = null) + public IEnumerable GetChildren(int id, UmbracoEntityTypes type, Guid? dataTypeKey = null) { var objectType = ConvertToObjectType(type); if (objectType.HasValue) @@ -433,7 +433,7 @@ namespace Umbraco.Web.Editors var startNodes = GetStartNodes(type); - var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); + var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey); // root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes if (id == Constants.System.Root && startNodes.Length > 0 && startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes) @@ -482,7 +482,7 @@ namespace Umbraco.Web.Editors string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = "", - Guid? dataTypeId = null) + Guid? dataTypeKey = null) { if (int.TryParse(id, out var intId)) { @@ -507,7 +507,7 @@ namespace Umbraco.Web.Editors //the EntityService can search paged members from the root intId = -1; - return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter, dataTypeId); + return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter, dataTypeKey); } //the EntityService cannot search members of a certain type, this is currently not supported and would require @@ -543,7 +543,7 @@ namespace Umbraco.Web.Editors string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = "", - Guid? dataTypeId = null) + Guid? dataTypeKey = null) { if (pageNumber <= 0) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -558,7 +558,7 @@ namespace Umbraco.Web.Editors var startNodes = GetStartNodes(type); - var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); + var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey); // root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes if (id == Constants.System.Root && startNodes.Length > 0 && startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes) @@ -642,7 +642,7 @@ namespace Umbraco.Web.Editors string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = "", - Guid? dataTypeId = null) + Guid? dataTypeKey = null) { if (pageNumber <= 0) throw new HttpResponseException(HttpStatusCode.NotFound); @@ -661,7 +661,7 @@ namespace Umbraco.Web.Editors int[] aids = GetStartNodes(type); - var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeId); + var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey); entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes ? Services.EntityService.GetPagedDescendants(objectType.Value, pageNumber - 1, pageSize, out totalRecords, SqlContext.Query().Where(x => x.Name.Contains(filter)), @@ -704,7 +704,7 @@ namespace Umbraco.Web.Editors } } - private bool IsDataTypeIgnoringUserStartNodes(Guid? dataTypeId) => dataTypeId.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value); + private bool IsDataTypeIgnoringUserStartNodes(Guid? dataTypeKey) => dataTypeKey.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value); public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) { diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs index f0f5bba7c4..c5c22484ad 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs @@ -22,8 +22,7 @@ namespace Umbraco.Web.Models.ContentEditing [Required] public int Id { get; set; } - //fixme: This name dataTypeId is inconsistent, but requires us to change it everywhere in angular - [DataMember(Name = "dataTypeId", IsRequired = false)] + [DataMember(Name = "dataTypeKey", IsRequired = false)] [ReadOnly(true)] public Guid DataTypeKey { get; set; } diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs index 4fa9db13e3..015c91cb81 100644 --- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs @@ -338,7 +338,7 @@ namespace Umbraco.Web.Trees //Here we need to figure out if the node is a container and if so check if the user has a custom start node, then check if that start node is a child // of this container node. If that is true, the HasChildren must be true so that the tree node still renders even though this current node is a container/list view. if (isContainer && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false) - { + { var startNodes = Services.EntityService.GetAll(UmbracoObjectType, UserStartNodes); //if any of these start nodes' parent is current, then we need to render children normally so we need to switch some logic and tell // the UI that this node does have children and that it isn't a container @@ -396,7 +396,7 @@ namespace Umbraco.Web.Trees } var menu = new MenuItemCollection(); - // only add empty recycle bin if the current user is allowed to delete by default + // only add empty recycle bin if the current user is allowed to delete by default if (deleteAllowed) { menu.Items.Add(new MenuItem("emptyRecycleBin", Services.TextService) @@ -538,8 +538,8 @@ namespace Umbraco.Web.Trees { if (_ignoreUserStartNodes.HasValue) return _ignoreUserStartNodes.Value; - var dataTypeId = queryStrings.GetValue(TreeQueryStringParameters.DataTypeId); - _ignoreUserStartNodes = dataTypeId.HasValue ? Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeId.Value) : false; + var dataTypeKey = queryStrings.GetValue(TreeQueryStringParameters.DataTypeKey); + _ignoreUserStartNodes = dataTypeKey.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value); return _ignoreUserStartNodes.Value; } diff --git a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs index 9d012cb25c..02a198401b 100644 --- a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs +++ b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs @@ -8,7 +8,7 @@ public const string Use = "use"; public const string Application = "application"; public const string StartNodeId = "startNodeId"; - public const string DataTypeId = "dataTypeId"; + public const string DataTypeKey = "dataTypeKey"; //public const string OnNodeClick = "OnNodeClick"; //public const string RenderParent = "RenderParent"; } From 7c52b9602c957d914b115a556a06f75acd1522fe Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 3 Jul 2019 17:43:30 +1000 Subject: [PATCH 100/776] Reduce some allocations and in advertent SQL calls along with inadvertent enumeration of the entire cache, adds some notes --- src/Umbraco.Core/EnumerableExtensions.cs | 3 +- .../PublishedCache/NuCache/ContentStore.cs | 17 +++-- .../NuCache/DataSource/DatabaseDataSource.cs | 4 + .../NuCache/PublishedSnapshotService.cs | 73 ++++++++++++------- 4 files changed, 65 insertions(+), 32 deletions(-) diff --git a/src/Umbraco.Core/EnumerableExtensions.cs b/src/Umbraco.Core/EnumerableExtensions.cs index 3719bb0750..59f5937874 100644 --- a/src/Umbraco.Core/EnumerableExtensions.cs +++ b/src/Umbraco.Core/EnumerableExtensions.cs @@ -10,6 +10,8 @@ namespace Umbraco.Core ///
public static class EnumerableExtensions { + internal static bool IsCollectionEmpty(this IReadOnlyCollection list) => list == null || list.Count == 0; + internal static bool HasDuplicates(this IEnumerable items, bool includeNull) { var hs = new HashSet(); @@ -112,7 +114,6 @@ namespace Umbraco.Core } } - /// /// Returns true if all items in the other collection exist in this collection /// diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index f13fb21d33..182450b149 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using CSharpTest.Net.Collections; +using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Scoping; @@ -275,6 +276,9 @@ namespace Umbraco.Web.PublishedCache.NuCache public void UpdateContentTypes(IEnumerable types) { + //nothing to do if this is empty, no need to lock/allocate/iterate/etc... + if (!types.Any()) return; + var lockInfo = new WriteLockInfo(); try { @@ -330,13 +334,16 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - public void UpdateContentTypes(IEnumerable removedIds, IEnumerable refreshedTypes, IEnumerable kits) + public void UpdateContentTypes(IReadOnlyCollection removedIds, IReadOnlyCollection refreshedTypes, IReadOnlyCollection kits) { - var removedIdsA = removedIds?.ToArray() ?? Array.Empty(); - var refreshedTypesA = refreshedTypes?.ToArray() ?? Array.Empty(); - var refreshedIdsA = refreshedTypesA.Select(x => x.Id).ToArray(); + var removedIdsA = removedIds ?? Array.Empty(); + var refreshedTypesA = refreshedTypes ?? Array.Empty(); + var refreshedIdsA = refreshedTypesA.Select(x => x.Id).ToList(); kits = kits ?? Array.Empty(); + if (kits.Count == 0 && refreshedIdsA.Count == 0 && removedIdsA.Count == 0) + return; //exit - there is nothing to do here + var lockInfo = new WriteLockInfo(); try { @@ -352,7 +359,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var node = link.Value; if (node == null) continue; var contentTypeId = node.ContentType.Id; - if (removedIdsA.Contains(contentTypeId)) removedContentTypeNodes.Add(node.Id); + if (removedIds.Contains(contentTypeId)) removedContentTypeNodes.Add(node.Id); if (refreshedIdsA.Contains(contentTypeId)) refreshedContentTypeNodes.Add(node.Id); } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs index dcb3f44487..3b66bd52d0 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs @@ -98,6 +98,8 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource public IEnumerable GetTypeContentSources(IScope scope, IEnumerable ids) { + if (!ids.Any()) return Enumerable.Empty(); + var sql = ContentSourcesSelect(scope) .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed) .WhereIn(x => x.ContentTypeId, ids) @@ -169,6 +171,8 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource public IEnumerable GetTypeMediaSources(IScope scope, IEnumerable ids) { + if (!ids.Any()) return Enumerable.Empty(); + var sql = MediaSourcesSelect(scope) .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed) .WhereIn(x => x.ContentTypeId, ids) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 642e969819..e407dde1bb 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -814,31 +814,34 @@ namespace Umbraco.Web.PublishedCache.NuCache ((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync(); } - - private void Notify(ContentStore store, ContentTypeCacheRefresher.JsonPayload[] payloads, Action, IEnumerable, IEnumerable, IEnumerable> action) + + private void Notify(ContentStore store, ContentTypeCacheRefresher.JsonPayload[] payloads, Action, List, List, List> action) + where T: IContentTypeComposition { + if (payloads.Length == 0) return; //nothing to do + + //TODO: In the case of Pure Live here - we actually need to refresh all of the content types + // AFAIK this is a call to SetValueLocked(_contentNodes... ) or is it a call to _contentStore.SetAll + var nameOfT = typeof(T).Name; - var removedIds = new List(); - var refreshedIds = new List(); - var otherIds = new List(); - var newIds = new List(); + List removedIds = null, refreshedIds = null, otherIds = null, newIds = null; foreach (var payload in payloads) { if (payload.ItemType != nameOfT) continue; if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.Remove)) - removedIds.Add(payload.Id); + AddToList(ref removedIds, payload.Id); else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.RefreshMain)) - refreshedIds.Add(payload.Id); + AddToList(ref refreshedIds, payload.Id); else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.RefreshOther)) - otherIds.Add(payload.Id); + AddToList(ref otherIds, payload.Id); else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.Create)) - newIds.Add(payload.Id); + AddToList(ref newIds, payload.Id); } - if (removedIds.Count == 0 && refreshedIds.Count == 0 && otherIds.Count == 0 && newIds.Count == 0) return; + if (removedIds.IsCollectionEmpty() && refreshedIds.IsCollectionEmpty() && otherIds.IsCollectionEmpty() && newIds.IsCollectionEmpty()) return; using (store.GetScopedWriteLock(_scopeProvider)) { @@ -925,15 +928,19 @@ namespace Umbraco.Web.PublishedCache.NuCache } } + //Methods used to prevent allocations of lists + private void AddToList(ref List list, int val) => GetOrCreateList(ref list).Add(val); + private List GetOrCreateList(ref List list) => list ?? (list = new List()); + #endregion #region Content Types - private IEnumerable CreateContentTypes(PublishedItemType itemType, int[] ids) + private IReadOnlyCollection CreateContentTypes(PublishedItemType itemType, int[] ids) { // XxxTypeService.GetAll(empty) returns everything! if (ids.Length == 0) - return Enumerable.Empty(); + return Array.Empty(); IEnumerable contentTypes; switch (itemType) @@ -953,7 +960,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // some may be missing - not checking here - return contentTypes.Select(x => _publishedContentTypeFactory.CreateContentType(x)); + return contentTypes.Select(x => _publishedContentTypeFactory.CreateContentType(x)).ToList(); } private IPublishedContentType CreateContentType(PublishedItemType itemType, int id) @@ -977,44 +984,58 @@ namespace Umbraco.Web.PublishedCache.NuCache return contentType == null ? null : _publishedContentTypeFactory.CreateContentType(contentType); } - private void RefreshContentTypesLocked(IEnumerable removedIds, IEnumerable refreshedIds, IEnumerable otherIds, IEnumerable newIds) + private void RefreshContentTypesLocked(List removedIds, List refreshedIds, List otherIds, List newIds) { + if (removedIds.IsCollectionEmpty() && refreshedIds.IsCollectionEmpty() && otherIds.IsCollectionEmpty() && newIds.IsCollectionEmpty()) + return; + // locks: // content (and content types) are read-locked while reading content // contentStore is wlocked (so readable, only no new views) // and it can be wlocked by 1 thread only at a time - var refreshedIdsA = refreshedIds.ToArray(); - using (var scope = _scopeProvider.CreateScope()) { scope.ReadLock(Constants.Locks.ContentTypes); - var typesA = CreateContentTypes(PublishedItemType.Content, refreshedIdsA).ToArray(); - var kits = _dataSource.GetTypeContentSources(scope, refreshedIdsA); + var typesA = refreshedIds.IsCollectionEmpty() + ? Array.Empty() + : CreateContentTypes(PublishedItemType.Content, refreshedIds.ToArray()).ToArray(); + + var kits = refreshedIds.IsCollectionEmpty() + ? Array.Empty() + : _dataSource.GetTypeContentSources(scope, refreshedIds).ToArray(); _contentStore.UpdateContentTypes(removedIds, typesA, kits); - _contentStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Content, otherIds.ToArray()).ToArray()); - _contentStore.NewContentTypes(CreateContentTypes(PublishedItemType.Content, newIds.ToArray()).ToArray()); + if (!otherIds.IsCollectionEmpty()) + _contentStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Content, otherIds.ToArray())); + if (!newIds.IsCollectionEmpty()) + _contentStore.NewContentTypes(CreateContentTypes(PublishedItemType.Content, newIds.ToArray())); scope.Complete(); } } - private void RefreshMediaTypesLocked(IEnumerable removedIds, IEnumerable refreshedIds, IEnumerable otherIds, IEnumerable newIds) + private void RefreshMediaTypesLocked(List removedIds, List refreshedIds, List otherIds, List newIds) { + if (removedIds.IsCollectionEmpty() && refreshedIds.IsCollectionEmpty() && otherIds.IsCollectionEmpty() && newIds.IsCollectionEmpty()) + return; + // locks: // media (and content types) are read-locked while reading media // mediaStore is wlocked (so readable, only no new views) // and it can be wlocked by 1 thread only at a time - var refreshedIdsA = refreshedIds.ToArray(); - using (var scope = _scopeProvider.CreateScope()) { scope.ReadLock(Constants.Locks.MediaTypes); - var typesA = CreateContentTypes(PublishedItemType.Media, refreshedIdsA).ToArray(); - var kits = _dataSource.GetTypeMediaSources(scope, refreshedIdsA); + var typesA = refreshedIds == null + ? Array.Empty() + : CreateContentTypes(PublishedItemType.Media, refreshedIds.ToArray()).ToArray(); + + var kits = refreshedIds == null + ? Array.Empty() + : _dataSource.GetTypeMediaSources(scope, refreshedIds).ToArray(); _mediaStore.UpdateContentTypes(removedIds, typesA, kits); _mediaStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray()); From 7cc91f71c2582c73121d1b0b1af81e69e0c394e1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 3 Jul 2019 18:11:00 +1000 Subject: [PATCH 101/776] Fixes pure live mode when changing doc types --- .../PublishedModelFactoryExtensions.cs | 7 +++++ .../Changes/ContentTypeChangeTypes.cs | 24 ++++++++++++++--- .../Cache/ContentTypeCacheRefresher.cs | 27 +------------------ .../NuCache/PublishedSnapshotService.cs | 20 +++++++++++--- 4 files changed, 44 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Core/PublishedModelFactoryExtensions.cs b/src/Umbraco.Core/PublishedModelFactoryExtensions.cs index d428accbbf..de6eeb6a42 100644 --- a/src/Umbraco.Core/PublishedModelFactoryExtensions.cs +++ b/src/Umbraco.Core/PublishedModelFactoryExtensions.cs @@ -8,6 +8,13 @@ namespace Umbraco.Core ///
public static class PublishedModelFactoryExtensions { + /// + /// Returns true if the current is an implementation of + /// + /// + /// + public static bool IsLiveFactory(this IPublishedModelFactory factory) => factory is ILivePublishedModelFactory; + /// /// Executes an action with a safe live factory /// diff --git a/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs b/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs index 497f7d47a9..bf7f87fd1a 100644 --- a/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs +++ b/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs @@ -6,9 +6,25 @@ namespace Umbraco.Core.Services.Changes public enum ContentTypeChangeTypes : byte { None = 0, - Create = 1, // item type has been created, no impact - RefreshMain = 2, // changed, impacts content (adding property or composition does NOT) - RefreshOther = 4, // changed, other changes - Remove = 8 // item type has been removed + + /// + /// Item type has been created, no impact + /// + Create = 1, + + /// + /// Content type changes impact only the Content type being saved + /// + RefreshMain = 2, + + /// + /// Content type changes impacts the content type being saved and others used that are composed of it + /// + RefreshOther = 4, // changed, other change + + /// + /// Content type was removed + /// + Remove = 8 } } diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index 75f2889cab..266cddf6d5 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -18,17 +18,15 @@ namespace Umbraco.Web.Cache private readonly IPublishedSnapshotService _publishedSnapshotService; private readonly IPublishedModelFactory _publishedModelFactory; private readonly IContentTypeCommonRepository _contentTypeCommonRepository; - private readonly IContentTypeService _contentTypeService; private readonly IdkMap _idkMap; - public ContentTypeCacheRefresher(AppCaches appCaches, IPublishedSnapshotService publishedSnapshotService, IPublishedModelFactory publishedModelFactory, IdkMap idkMap, IContentTypeCommonRepository contentTypeCommonRepository, IContentTypeService contentTypeService) + public ContentTypeCacheRefresher(AppCaches appCaches, IPublishedSnapshotService publishedSnapshotService, IPublishedModelFactory publishedModelFactory, IdkMap idkMap, IContentTypeCommonRepository contentTypeCommonRepository) : base(appCaches) { _publishedSnapshotService = publishedSnapshotService; _publishedModelFactory = publishedModelFactory; _idkMap = idkMap; _contentTypeCommonRepository = contentTypeCommonRepository; - _contentTypeService = contentTypeService; } #region Define @@ -53,16 +51,6 @@ namespace Umbraco.Web.Cache _contentTypeCommonRepository.ClearCache(); // always - //// We need to special handle the IContentType if modelsbuilder is in live mode, because all models are updated when a IContentType is changed, we need to clear all from cache also. - //if (_publishedModelFactory is ILivePublishedModelFactory && payloads.Any(x => x.ItemType == typeof(IContentType).Name)) - //{ - // //This is super nasty, and we need to figure out a better way to to this - // //Ensure all doc type ids is part of the payload - // var missingPayloads = GetMissingContentTypePayloads(payloads); - - // payloads = payloads.Union(missingPayloads).ToArray(); - //} - if (payloads.Any(x => x.ItemType == typeof(IContentType).Name)) { ClearAllIsolatedCacheByEntityType(); @@ -109,19 +97,6 @@ namespace Umbraco.Web.Cache base.Refresh(payloads); } - //private IEnumerable GetMissingContentTypePayloads(JsonPayload[] payloads) - //{ - // var existingPayloadIds = new HashSet(payloads.Select(x => x.Id)); - // var contentTypeIds = _contentTypeService.GetAll().Select(x => x.Id).ToArray(); - - // foreach (var contentTypeId in contentTypeIds) - // { - // if (!existingPayloadIds.Contains(contentTypeId)) - // { - // yield return new JsonPayload(typeof(IContentType).Name, contentTypeId, ContentTypeChangeTypes.RefreshOther); - // } - // } - //} public override void RefreshAll() { diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index e407dde1bb..b585781ea4 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -42,6 +42,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly IMemberRepository _memberRepository; private readonly IGlobalSettings _globalSettings; private readonly IEntityXmlSerializer _entitySerializer; + private readonly IPublishedModelFactory _publishedModelFactory; private readonly IDefaultCultureAccessor _defaultCultureAccessor; private readonly UrlSegmentProviderCollection _urlSegmentProviders; @@ -73,7 +74,8 @@ namespace Umbraco.Web.PublishedCache.NuCache IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, IDefaultCultureAccessor defaultCultureAccessor, IDataSource dataSource, IGlobalSettings globalSettings, - IEntityXmlSerializer entitySerializer, IPublishedModelFactory publishedModelFactory, + IEntityXmlSerializer entitySerializer, + IPublishedModelFactory publishedModelFactory, UrlSegmentProviderCollection urlSegmentProviders) : base(publishedSnapshotAccessor, variationContextAccessor) { @@ -95,6 +97,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // we need an Xml serializer here so that the member cache can support XPath, // for members this is done by navigating the serialized-to-xml member _entitySerializer = entitySerializer; + _publishedModelFactory = publishedModelFactory; // we always want to handle repository events, configured or not // assuming no repository event will trigger before the whole db is ready @@ -708,6 +711,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } } + /// public override void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged) { // no cache, trash everything @@ -800,6 +804,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } } + /// public override void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads) { // no cache, nothing we can do @@ -812,6 +817,16 @@ namespace Umbraco.Web.PublishedCache.NuCache Notify(_contentStore, payloads, RefreshContentTypesLocked); Notify(_mediaStore, payloads, RefreshMediaTypesLocked); + if (_publishedModelFactory.IsLiveFactory()) + { + //In the case of Pure Live - we actually need to refresh all of the content + using (_contentStore.GetScopedWriteLock(_scopeProvider)) + { + NotifyLocked(new[] { new ContentCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out var draftChanged, out var publishedChanged); + + } + } + ((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync(); } @@ -820,9 +835,6 @@ namespace Umbraco.Web.PublishedCache.NuCache { if (payloads.Length == 0) return; //nothing to do - //TODO: In the case of Pure Live here - we actually need to refresh all of the content types - // AFAIK this is a call to SetValueLocked(_contentNodes... ) or is it a call to _contentStore.SetAll - var nameOfT = typeof(T).Name; List removedIds = null, refreshedIds = null, otherIds = null, newIds = null; From 126c4cbd46de303bd3cf6ca095d68216d16f914a Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 3 Jul 2019 18:25:19 +1000 Subject: [PATCH 102/776] ensures media is rebuild and adds more notes --- .../NuCache/PublishedSnapshotService.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index b585781ea4..dad9811af8 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -819,19 +819,25 @@ namespace Umbraco.Web.PublishedCache.NuCache if (_publishedModelFactory.IsLiveFactory()) { - //In the case of Pure Live - we actually need to refresh all of the content + //In the case of Pure Live - we actually need to refresh all of the content and the media + //see https://github.com/umbraco/Umbraco-CMS/issues/5671 + //The underlying issue is that in Pure Live the ILivePublishedModelFactory will re-compile all of the classes/models + //into a new DLL for the application which includes both content types and media types. + //Since the models in the cache are based on these actual classes, all of the objects in the cache need to be updated + //to use the newest version of the class. using (_contentStore.GetScopedWriteLock(_scopeProvider)) + using (_mediaStore.GetScopedWriteLock(_scopeProvider)) { NotifyLocked(new[] { new ContentCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out var draftChanged, out var publishedChanged); - + NotifyLocked(new[] { new MediaCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out var anythingChanged); } } ((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync(); } - + private void Notify(ContentStore store, ContentTypeCacheRefresher.JsonPayload[] payloads, Action, List, List, List> action) - where T: IContentTypeComposition + where T : IContentTypeComposition { if (payloads.Length == 0) return; //nothing to do From 31716e574ca0e6c5aa002a59711452d1ece4cc5b Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 3 Jul 2019 18:34:11 +1000 Subject: [PATCH 103/776] Update src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs good catch! Co-Authored-By: Bjarke Berg --- src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 182450b149..2d501fa3b5 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -359,7 +359,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var node = link.Value; if (node == null) continue; var contentTypeId = node.ContentType.Id; - if (removedIds.Contains(contentTypeId)) removedContentTypeNodes.Add(node.Id); + if (removedIdsA.Contains(contentTypeId)) removedContentTypeNodes.Add(node.Id); if (refreshedIdsA.Contains(contentTypeId)) refreshedContentTypeNodes.Add(node.Id); } From 37bf6fe9385f026f6921ea03330e2ded57914fa6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 3 Jul 2019 19:15:54 +1000 Subject: [PATCH 104/776] Automatically refresh the page if we encounter a ModelBindingException --- .../Mvc/ModelBindingExceptionFilter.cs | 23 +++++++++++++++++++ src/Umbraco.Web/Mvc/RenderMvcController.cs | 2 ++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 3 files changed, 26 insertions(+) create mode 100644 src/Umbraco.Web/Mvc/ModelBindingExceptionFilter.cs diff --git a/src/Umbraco.Web/Mvc/ModelBindingExceptionFilter.cs b/src/Umbraco.Web/Mvc/ModelBindingExceptionFilter.cs new file mode 100644 index 0000000000..714a916e6a --- /dev/null +++ b/src/Umbraco.Web/Mvc/ModelBindingExceptionFilter.cs @@ -0,0 +1,23 @@ +using System.Web.Mvc; + +namespace Umbraco.Web.Mvc +{ + /// + /// An exception filter checking if we get a in which case it returns the html to auto refresh the page + /// + internal class ModelBindingExceptionFilter : FilterAttribute, IExceptionFilter + { + public void OnException(ExceptionContext filterContext) + { + if (!filterContext.ExceptionHandled && filterContext.Exception is ModelBindingException) + { + filterContext.Result = new ContentResult + { + Content = "

Loading page...

", + ContentType = "text/html" + }; + filterContext.ExceptionHandled = true; + } + } + } +} diff --git a/src/Umbraco.Web/Mvc/RenderMvcController.cs b/src/Umbraco.Web/Mvc/RenderMvcController.cs index c2aa3bd8ed..64c9ad52c4 100644 --- a/src/Umbraco.Web/Mvc/RenderMvcController.cs +++ b/src/Umbraco.Web/Mvc/RenderMvcController.cs @@ -10,10 +10,12 @@ using Umbraco.Web.Routing; namespace Umbraco.Web.Mvc { + /// /// Represents the default front-end rendering controller. /// [PreRenderViewActionFilter] + [ModelBindingExceptionFilter] public class RenderMvcController : UmbracoController, IRenderMvcController { private PublishedRequest _publishedRequest; diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c9cbc46a35..b6b7bc5599 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -218,6 +218,7 @@ + From 4133a1fe76638defdb41527c0355f7a75cb38e0d Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 3 Jul 2019 13:14:35 +0200 Subject: [PATCH 105/776] Refactored the ModelBindingExceptionFilter to also handle InvalidCastException, and also ensure the models are the same type. Otherwise a infinite loop was introduced, when requesting a wrong model in the view. Also refactored the why we do the retry from Html to Http headers --- .../Mvc/ModelBindingExceptionFilter.cs | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web/Mvc/ModelBindingExceptionFilter.cs b/src/Umbraco.Web/Mvc/ModelBindingExceptionFilter.cs index 714a916e6a..b9161dbea0 100644 --- a/src/Umbraco.Web/Mvc/ModelBindingExceptionFilter.cs +++ b/src/Umbraco.Web/Mvc/ModelBindingExceptionFilter.cs @@ -1,23 +1,54 @@ -using System.Web.Mvc; +using System; +using System.Net; +using System.Text.RegularExpressions; +using System.Web.Mvc; namespace Umbraco.Web.Mvc { /// - /// An exception filter checking if we get a in which case it returns the html to auto refresh the page + /// An exception filter checking if we get a or with the same model. in which case it returns a redirect to the same page after 1 sec. /// internal class ModelBindingExceptionFilter : FilterAttribute, IExceptionFilter { + private static readonly Regex GetPublishedModelsTypesRegex = new Regex("Umbraco.Web.PublishedModels.(\\w+)", RegexOptions.Compiled); + public void OnException(ExceptionContext filterContext) { - if (!filterContext.ExceptionHandled && filterContext.Exception is ModelBindingException) + if (!filterContext.ExceptionHandled + && ((filterContext.Exception is ModelBindingException || filterContext.Exception is InvalidCastException) + && IsMessageAboutTheSameModelType(filterContext.Exception.Message))) { - filterContext.Result = new ContentResult - { - Content = "

Loading page...

", - ContentType = "text/html" - }; + filterContext.HttpContext.Response.Headers.Add(HttpResponseHeader.RetryAfter.ToString(), "1"); + filterContext.Result = new RedirectResult(filterContext.HttpContext.Request.RawUrl, false); + filterContext.ExceptionHandled = true; } } + + /// + /// Returns true if the message is about two models with the same name. + /// + /// + /// Message could be something like: + /// + /// InvalidCastException: + /// [A]Umbraco.Web.PublishedModels.Home cannot be cast to [B]Umbraco.Web.PublishedModels.Home. Type A originates from 'App_Web_all.generated.cs.8f9494c4.rtdigm_z, Version=0.0.0.3, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Users\User\AppData\Local\Temp\Temporary ASP.NET Files\root\c5c63f4d\c168d9d4\App_Web_all.generated.cs.8f9494c4.rtdigm_z.dll'. Type B originates from 'App_Web_all.generated.cs.8f9494c4.rbyqlplu, Version=0.0.0.5, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Users\User\AppData\Local\Temp\Temporary ASP.NET Files\root\c5c63f4d\c168d9d4\App_Web_all.generated.cs.8f9494c4.rbyqlplu.dll'. + /// + /// + /// ModelBindingException: + /// Cannot bind source content type Umbraco.Web.PublishedModels.Home to model type Umbraco.Web.PublishedModels.Home. Both view and content models are PureLive, with different versions. The application is in an unstable state and is going to be restarted. The application is restarting now. + /// + /// + private bool IsMessageAboutTheSameModelType(string exceptionMessage) + { + var matches = GetPublishedModelsTypesRegex.Matches(exceptionMessage); + + if (matches.Count >= 2) + { + return string.Equals(matches[0].Value, matches[1].Value, StringComparison.InvariantCulture); + } + + return false; + } } } From 57e3187e3a84c3992c7797bc041e5eb3fa9a1bad Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 3 Jul 2019 22:10:01 +0200 Subject: [PATCH 106/776] For some reason this file was not part of the project any more :-( --- src/Umbraco.Web/Umbraco.Web.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index b6b7bc5599..105a40b4a7 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -137,6 +137,7 @@ + From c4b5483d8c1c5c0b44e356ee856319ec1821fa76 Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Thu, 4 Jul 2019 11:19:10 +1000 Subject: [PATCH 107/776] make log naming and colors consistent between views --- .../views/logviewer/overview.controller.js | 12 +++---- .../src/views/logviewer/overview.html | 2 +- .../src/views/logviewer/search.controller.js | 35 ++++++------------- 3 files changed, 17 insertions(+), 32 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js index fc24fbe1bc..5cee21668e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js @@ -12,9 +12,9 @@ vm.commonLogMessagesCount = 10; // ChartJS Options - for count/overview of log distribution - vm.logTypeLabels = ["Info", "Debug", "Warning", "Error", "Critical"]; + vm.logTypeLabels = ["Debug", "Info", "Warning", "Error", "Fatal"]; vm.logTypeData = [0, 0, 0, 0, 0]; - vm.logTypeColors = [ '#dcdcdc', '#97bbcd', '#46bfbd', '#fdb45c', '#f7464a']; + vm.logTypeColors = ['#eaddd5', '#2bc37c', '#3544b1', '#ff9412', '#d42054']; vm.chartOptions = { legend: { display: true, @@ -74,7 +74,7 @@ "query": "Not(@Level='Verbose') and Not(@Level='Debug')" }, { - "name": "Find all logs that has an exception property (Warning, Error & Critical with Exceptions)", + "name": "Find all logs that has an exception property (Warning, Error & Fatal with Exceptions)", "query": "Has(@Exception)" }, { @@ -113,8 +113,8 @@ vm.commonLogMessages = data; }); - //Set loading indicatior to false when these 3 queries complete - $q.all([savedSearches, numOfErrors, logCounts, commonMsgs]).then(function(data) { + //Set loading indicator to false when these 3 queries complete + $q.all([savedSearches, numOfErrors, logCounts, commonMsgs]).then(function() { vm.loading = false; }); @@ -148,7 +148,7 @@ conjunction: " to " }; - vm.dateRangeChange = function(selectedDates, dateStr, instance) { + vm.dateRangeChange = function(selectedDates) { if(selectedDates.length > 0){ vm.startDate = selectedDates[0].toIsoDateString(); diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html index 854bed755f..896cb17da7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html @@ -78,7 +78,7 @@ class="datepicker" ng-model="vm.period" options="vm.config" - on-close="vm.dateRangeChange(selectedDates, dateStr, instance)"> + on-close="vm.dateRangeChange(selectedDates)"> diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/search.controller.js b/src/Umbraco.Web.UI.Client/src/views/logviewer/search.controller.js index d4b0ea8f8e..fb627855b6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/search.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/search.controller.js @@ -11,26 +11,28 @@ vm.showBackButton = true; vm.page = {}; + // this array is also used to map the logTypeColor param onto the log items + // in setLogTypeColors() vm.logLevels = [ { name: 'Verbose', - logTypeColor: 'gray' + logTypeColor: '' }, { name: 'Debug', - logTypeColor: 'secondary' + logTypeColor: 'gray' }, { name: 'Information', - logTypeColor: 'primary' + logTypeColor: 'success' }, { name: 'Warning', - logTypeColor: 'warning' + logTypeColor: 'primary' }, { name: 'Error', - logTypeColor: 'danger' + logTypeColor: 'warning' }, { name: 'Fatal', @@ -118,7 +120,7 @@ "query": "Not(@Level='Verbose') and Not(@Level='Debug')" }, { - "name": "Find all logs that has an exception property (Warning, Error & Critical with Exceptions)", + "name": "Find all logs that has an exception property (Warning, Error & Fatal with Exceptions)", "query": "Has(@Exception)" }, { @@ -173,25 +175,8 @@ } function setLogTypeColor(logItems) { - angular.forEach(logItems, function (log) { - switch (log.Level) { - case "Information": - log.logTypeColor = "primary"; - break; - case "Debug": - log.logTypeColor = "secondary"; - break; - case "Warning": - log.logTypeColor = "warning"; - break; - case "Fatal": - case "Error": - log.logTypeColor = "danger"; - break; - default: - log.logTypeColor = "gray"; - } - }); + logItems.forEach(logItem => + logItem.logTypeColor = vm.logLevels.find(x => x.name === logItem.Level).logTypeColor); } function getFilterName(array) { From a0bcfeddcc6631a48d73dfb6fd3e88a96fa7d146 Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Thu, 4 Jul 2019 11:48:02 +1000 Subject: [PATCH 108/776] fix date range formatting on flatpickr - 'to' wasnt displayed after choosing a range --- .../views/logviewer/overview.controller.js | 24 ++++++++++++------- .../src/views/logviewer/overview.html | 2 +- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js index 5cee21668e..79ac8af5f8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js @@ -43,14 +43,20 @@ vm.searchLogQuery = searchLogQuery; vm.findMessageTemplate = findMessageTemplate; - function preFlightCheck(){ - vm.loading = true; + function preFlightCheck(instance){ + vm.loading = true; + + //Do our pre-flight check (to see if we can view logs) //IE the log file is NOT too big such as 1GB & crash the site logViewerResource.canViewLogs(vm.startDate, vm.endDate).then(function(result){ vm.loading = false; vm.canLoadLogs = result; + if (instance) { + instance.setDate(vm.period); + } + if(result){ //Can view logs - so initalise init(); @@ -117,7 +123,7 @@ $q.all([savedSearches, numOfErrors, logCounts, commonMsgs]).then(function() { vm.loading = false; }); - + $timeout(function () { navigationService.syncTree({ tree: "logViewer", path: "-1" }); }); @@ -146,9 +152,9 @@ mode: "range", maxDate: "today", conjunction: " to " - }; - - vm.dateRangeChange = function(selectedDates) { + }; + + vm.dateRangeChange = function(selectedDates, dateStr, instance) { if(selectedDates.length > 0){ vm.startDate = selectedDates[0].toIsoDateString(); @@ -158,9 +164,9 @@ vm.period = [vm.startDate]; }else{ vm.period = [vm.startDate, vm.endDate]; - } - - preFlightCheck(); + } + + preFlightCheck(instance); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html index 896cb17da7..854bed755f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html @@ -78,7 +78,7 @@ class="datepicker" ng-model="vm.period" options="vm.config" - on-close="vm.dateRangeChange(selectedDates)"> + on-close="vm.dateRangeChange(selectedDates, dateStr, instance)"> From 926acb910e2dd3c20b782f3a34b4bc60147e5257 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 4 Jul 2019 10:35:48 +0200 Subject: [PATCH 109/776] Forces the initial migration state of V7 sites that are allowed to be migrated into v7.14 state. --- src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 834eade986..6c8099ffb0 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -74,6 +74,10 @@ namespace Umbraco.Core.Migrations.Upgrade throw new InvalidOperationException($"Version {currentVersion} cannot be migrated to {UmbracoVersion.SemanticVersion}." + $" Please upgrade first to at least {minVersion}."); + // Force newer versions of 7, into 7.14, when migrating + if (currentVersion.Major == 7) + return GetInitState(minVersion); + // initial state is eg "{init-7.14.0}" return GetInitState(currentVersion); } From 6dbb9889035fdc5e5447053787cd315343831f6b Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 4 Jul 2019 10:47:56 +0200 Subject: [PATCH 110/776] Refactor to limit to only v7.14.0 - V7.15.* --- src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 6c8099ffb0..e8fd3414ec 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -74,8 +74,9 @@ namespace Umbraco.Core.Migrations.Upgrade throw new InvalidOperationException($"Version {currentVersion} cannot be migrated to {UmbracoVersion.SemanticVersion}." + $" Please upgrade first to at least {minVersion}."); - // Force newer versions of 7, into 7.14, when migrating - if (currentVersion.Major == 7) + // Force versions between 7.14.*-7.15.* into into 7.14 initial state. Because there is no db-changes, + // and we don't want users to workaround my putting in version 7.14.0 them self. + if (minVersion <= currentVersion && currentVersion < new SemVersion(7, 16)) return GetInitState(minVersion); // initial state is eg "{init-7.14.0}" From 2cd01735343e49e853555d662ad6e9493ee69a45 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Thu, 4 Jul 2019 14:35:06 +0200 Subject: [PATCH 111/776] Fixed incorrect label for "Fatal" errors on the log viewer pie chart. Made the log viewer title variable so it either says "Log Overview For Today" or "Log Overview For Selected Time Period" (previously it said the former even if you'd adjusted the dates you were looking at. Fixed up the selected date period display so the conjunction "to" is used between the dates when the selected time period is changed and querystring is updated. Minor code tidy-up. --- .../views/logviewer/overview.controller.js | 134 +++++++++--------- .../src/views/logviewer/overview.html | 11 +- .../Editors/LogViewerController.cs | 6 +- 3 files changed, 77 insertions(+), 74 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js index fc24fbe1bc..0afad09b81 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js @@ -10,11 +10,12 @@ vm.numberOfErrors = 0; vm.commonLogMessages = []; vm.commonLogMessagesCount = 10; + vm.dateRangeLabel = ""; // ChartJS Options - for count/overview of log distribution - vm.logTypeLabels = ["Info", "Debug", "Warning", "Error", "Critical"]; + vm.logTypeLabels = ["Info", "Debug", "Warning", "Error", "Fatal"]; vm.logTypeData = [0, 0, 0, 0, 0]; - vm.logTypeColors = [ '#dcdcdc', '#97bbcd', '#46bfbd', '#fdb45c', '#f7464a']; + vm.logTypeColors = ["#dcdcdc", "#97bbcd", "#46bfbd", "#fdb45c", "#f7464a"]; vm.chartOptions = { legend: { display: true, @@ -23,35 +24,41 @@ }; let querystring = $location.search(); - if(querystring.startDate){ + if (querystring.startDate) { vm.startDate = querystring.startDate; - }else{ + vm.dateRangeLabel = getDateRangeLabel("Selected Time Period"); + } else { vm.startDate = new Date(Date.now()); - vm.startDate.setDate(vm.startDate.getDate()-1); + vm.startDate.setDate(vm.startDate.getDate() - 1); vm.startDate = vm.startDate.toIsoDateString(); + vm.dateRangeLabel = getDateRangeLabel("Today"); } - if(querystring.endDate){ + if (querystring.endDate) { vm.endDate = querystring.endDate; - }else{ + + if (querystring.endDate === querystring.startDate) { + vm.dateRangeLabel = getDateRangeLabel("Selected Date"); + } + } else { vm.endDate = new Date(Date.now()).toIsoDateString(); } - vm.period = [vm.startDate, vm.endDate]; + vm.period = [vm.startDate, vm.endDate]; //functions vm.searchLogQuery = searchLogQuery; vm.findMessageTemplate = findMessageTemplate; - - function preFlightCheck(){ + + function preFlightCheck() { vm.loading = true; //Do our pre-flight check (to see if we can view logs) //IE the log file is NOT too big such as 1GB & crash the site - logViewerResource.canViewLogs(vm.startDate, vm.endDate).then(function(result){ + logViewerResource.canViewLogs(vm.startDate, vm.endDate).then(function (result) { vm.loading = false; vm.canLoadLogs = result; - if(result){ + if (result) { //Can view logs - so initalise init(); } @@ -62,39 +69,39 @@ function init() { vm.loading = true; - + var savedSearches = logViewerResource.getSavedSearches().then(function (data) { vm.searches = data; }, - // fallback to some defaults if error from API response - function () { - vm.searches = [ - { - "name": "Find all logs where the Level is NOT Verbose and NOT Debug", - "query": "Not(@Level='Verbose') and Not(@Level='Debug')" - }, - { - "name": "Find all logs that has an exception property (Warning, Error & Critical with Exceptions)", - "query": "Has(@Exception)" - }, - { - "name": "Find all logs that have the property 'Duration'", - "query": "Has(Duration)" - }, - { - "name": "Find all logs that have the property 'Duration' and the duration is greater than 1000ms", - "query": "Has(Duration) and Duration > 1000" - }, - { - "name": "Find all logs that are from the namespace 'Umbraco.Core'", - "query": "StartsWith(SourceContext, 'Umbraco.Core')" - }, - { - "name": "Find all logs that use a specific log message template", - "query": "@MessageTemplate = '[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)'" - } - ] - }); + // fallback to some defaults if error from API response + function () { + vm.searches = [ + { + "name": "Find all logs where the Level is NOT Verbose and NOT Debug", + "query": "Not(@Level='Verbose') and Not(@Level='Debug')" + }, + { + "name": "Find all logs that has an exception property (Warning, Error & Critical with Exceptions)", + "query": "Has(@Exception)" + }, + { + "name": "Find all logs that have the property 'Duration'", + "query": "Has(Duration)" + }, + { + "name": "Find all logs that have the property 'Duration' and the duration is greater than 1000ms", + "query": "Has(Duration) and Duration > 1000" + }, + { + "name": "Find all logs that are from the namespace 'Umbraco.Core'", + "query": "StartsWith(SourceContext, 'Umbraco.Core')" + }, + { + "name": "Find all logs that use a specific log message template", + "query": "@MessageTemplate = '[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)'" + } + ]; + }); var numOfErrors = logViewerResource.getNumberOfErrors(vm.startDate, vm.endDate).then(function (data) { vm.numberOfErrors = data; @@ -109,12 +116,12 @@ vm.logTypeData.push(data.Fatal); }); - var commonMsgs = logViewerResource.getMessageTemplates(vm.startDate, vm.endDate).then(function(data){ + var commonMsgs = logViewerResource.getMessageTemplates(vm.startDate, vm.endDate).then(function (data) { vm.commonLogMessages = data; }); //Set loading indicatior to false when these 3 queries complete - $q.all([savedSearches, numOfErrors, logCounts, commonMsgs]).then(function(data) { + $q.all([savedSearches, numOfErrors, logCounts, commonMsgs]).then(function (data) { vm.loading = false; }); @@ -123,20 +130,21 @@ }); } - function searchLogQuery(logQuery){ - $location.path("/settings/logViewer/search").search({lq: logQuery, startDate: vm.startDate, endDate: vm.endDate}); + function searchLogQuery(logQuery) { + $location.path("/settings/logViewer/search").search({ lq: logQuery, startDate: vm.startDate, endDate: vm.endDate }); } - function findMessageTemplate(template){ + function findMessageTemplate(template) { var logQuery = "@MessageTemplate='" + template.MessageTemplate + "'"; searchLogQuery(logQuery); } - - - - preFlightCheck(); + function getDateRangeLabel(suffix) { + return "Log Overview for " + suffix; + } + preFlightCheck(); + ///////////////////// vm.config = { @@ -147,20 +155,18 @@ maxDate: "today", conjunction: " to " }; - - vm.dateRangeChange = function(selectedDates, dateStr, instance) { - - if(selectedDates.length > 0){ - vm.startDate = selectedDates[0].toIsoDateString(); - vm.endDate = selectedDates[selectedDates.length-1].toIsoDateString(); // Take the last date as end - if(vm.startDate === vm.endDate){ - vm.period = [vm.startDate]; - }else{ - vm.period = [vm.startDate, vm.endDate]; - } - - preFlightCheck(); + vm.dateRangeChange = function (selectedDates, dateStr, instance) { + + if (selectedDates.length > 0) { + + // Update view by re-requesting route with updated querystring. + // By doing this we make sure the URL matches the selected time period, aiding sharing the link. + // Also resolves a minor layout issue where the " to " conjunction between the selected dates + // is collapsed to a comma. + const startDate = selectedDates[0].toIsoDateString(); + const endDate = selectedDates[selectedDates.length - 1].toIsoDateString(); // Take the last date as end + $location.path("/settings/logViewer/overview").search({ startDate: startDate, endDate: endDate }); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html index 854bed755f..7648bbf162 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html @@ -3,7 +3,7 @@ - + diff --git a/src/Umbraco.Web/Editors/LogViewerController.cs b/src/Umbraco.Web/Editors/LogViewerController.cs index 79eb3bb312..d9fcfd108a 100644 --- a/src/Umbraco.Web/Editors/LogViewerController.cs +++ b/src/Umbraco.Web/Editors/LogViewerController.cs @@ -15,11 +15,11 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] public class LogViewerController : UmbracoAuthorizedJsonController { - private ILogViewer _logViewer; + private readonly ILogViewer _logViewer; public LogViewerController(ILogViewer logViewer) { - _logViewer = logViewer; + _logViewer = logViewer ?? throw new ArgumentNullException(nameof(logViewer)); } private bool CanViewLogs(LogTimePeriod logTimePeriod) @@ -91,8 +91,6 @@ namespace Umbraco.Web.Editors var direction = orderDirection == "Descending" ? Direction.Descending : Direction.Ascending; - - return _logViewer.GetLogs(logTimePeriod, filterExpression: filterExpression, pageNumber: pageNumber, orderDirection: direction, logLevels: logLevels); } From 015ad64e30c090a4ce4a63a5366b5f24481f96d6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 5 Jul 2019 14:36:33 +1000 Subject: [PATCH 112/776] Updates the exception thrown during upgrade to provide some meaningful feedback to the user. --- .../Upgrade/V_8_0_0/PropertyEditorsMigration.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs index 064ffc7228..dac62abb75 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs @@ -30,7 +30,20 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 .Where(x => x.EditorAlias == toAlias)); if (oldCount > 0) - throw new InvalidOperationException($"Cannot rename datatype alias \"{fromAlias}\" to \"{toAlias}\" because the target alias is already used."); + { + // If we throw it means that the upgrade will exit and cannot continue. + // This will occur if a v7 site has the old "Obsolete" property editors that are already named with the `toAlias` name. + // TODO: We should have an additional upgrade step when going from 7 -> 8 like we did with 6 -> 7 that shows a compatibility report, + // this would include this check and then we can provide users with information on what they should do (i.e. before upgrading to v8 they will + // need to migrate these old obsolete editors to non-obsolete editors) + + throw new InvalidOperationException( + $"Cannot rename datatype alias \"{fromAlias}\" to \"{toAlias}\" because the target alias is already used." + + $"This is generally because when upgrading from a v7 to v8 site, the v7 site contains Data Types that reference old and already Obsolete " + + $"Property Editors. Before upgrading to v8, any Data Types using property editors that are named with the prefix '(Obsolete)' must be migrated " + + $"to the non-obsolete v7 property editors of the same type."); + } + } Database.Execute(Sql() From 5ada85df29dee4793eff496d70e674b881c07e85 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Fri, 5 Jul 2019 12:11:03 +0200 Subject: [PATCH 113/776] Provide the correct assembly redirects --- build/NuSpecs/tools/Web.config.install.xdt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt index 4f8a1927a8..f0d194d341 100644 --- a/build/NuSpecs/tools/Web.config.install.xdt +++ b/build/NuSpecs/tools/Web.config.install.xdt @@ -346,7 +346,7 @@ - + @@ -358,11 +358,11 @@ - + - + @@ -370,27 +370,27 @@ - + - + - + - + - + - + From 1af43498d97ff588cc9636e7d7542de830b4f1f1 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 7 Jul 2019 11:16:02 +0200 Subject: [PATCH 114/776] Make it possible to save a member without resetting the password --- .../src/views/member/member.edit.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js index b2d91e2f66..7276bd9a86 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js @@ -139,7 +139,7 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR //anytime a user is changing a member's password without the oldPassword, we are in effect resetting it so we need to set that flag here var passwordProp = _.find(contentEditingHelper.getAllProps($scope.content), function (e) { return e.alias === '_umb_password' }); - if (!passwordProp.value.reset) { + if (passwordProp && passwordProp.value && !passwordProp.value.reset) { //so if the admin is not explicitly resetting the password, flag it for resetting if a new password is being entered passwordProp.value.reset = !passwordProp.value.oldPassword && passwordProp.config.allowManuallyChangingPassword; } From 80d7f1b2c99eb0332446e70170ebe78bbb44e160 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 7 Jul 2019 11:36:26 +0200 Subject: [PATCH 115/776] Make it possible to save a member without resetting the password --- .../src/views/member/member.edit.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js index 339358dbf2..eb99e46a1f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js @@ -133,7 +133,7 @@ function MemberEditController($scope, $routeParams, $location, $q, $window, appS //anytime a user is changing a member's password without the oldPassword, we are in effect resetting it so we need to set that flag here var passwordProp = _.find(contentEditingHelper.getAllProps($scope.content), function (e) { return e.alias === '_umb_password' }); - if (!passwordProp.value.reset) { + if (passwordProp && passwordProp.value && !passwordProp.value.reset) { //so if the admin is not explicitly resetting the password, flag it for resetting if a new password is being entered passwordProp.value.reset = !passwordProp.value.oldPassword && passwordProp.config.allowManuallyChangingPassword; } From 6473f600c732c4d0be2af96c0f424374cec8685f Mon Sep 17 00:00:00 2001 From: arkadiuszbiel Date: Mon, 8 Jul 2019 08:48:49 +0100 Subject: [PATCH 116/776] Remove last casting to LucenIndex --- src/Umbraco.Web/Editors/ExamineManagementController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Editors/ExamineManagementController.cs b/src/Umbraco.Web/Editors/ExamineManagementController.cs index 5f969cdd80..7c040ae508 100644 --- a/src/Umbraco.Web/Editors/ExamineManagementController.cs +++ b/src/Umbraco.Web/Editors/ExamineManagementController.cs @@ -85,7 +85,7 @@ namespace Umbraco.Web.Editors }; } - + /// /// Check if the index has been rebuilt @@ -250,7 +250,7 @@ namespace Umbraco.Web.Editors private void Indexer_IndexOperationComplete(object sender, EventArgs e) { - var indexer = (LuceneIndex)sender; + var indexer = (IIndex)sender; _logger.Debug("Logging operation completed for index {IndexName}", indexer.Name); @@ -259,7 +259,7 @@ namespace Umbraco.Web.Editors _logger .Info($"Rebuilding index '{indexer.Name}' done, {indexer.CommitCount} items committed (can differ from the number of items in the index)"); + >($"Rebuilding index '{indexer.Name}' done."); var cacheKey = "temp_indexing_op_" + indexer.Name; _runtimeCache.Clear(cacheKey); From a4749f201d33918c3b576a86cd1c54fdce9c9ab5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 8 Jul 2019 23:03:20 +1000 Subject: [PATCH 117/776] Ensures luceneIndexer.WaitForIndexQueueOnShutdown is called even when not in MainDom --- src/Umbraco.Examine/ExamineExtensions.cs | 4 ++- src/Umbraco.Web/Search/ExamineComponent.cs | 36 +++++++++++----------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Examine/ExamineExtensions.cs b/src/Umbraco.Examine/ExamineExtensions.cs index 43a3ccc196..1b8033c458 100644 --- a/src/Umbraco.Examine/ExamineExtensions.cs +++ b/src/Umbraco.Examine/ExamineExtensions.cs @@ -77,7 +77,7 @@ namespace Umbraco.Examine /// /// This is not thread safe, use with care /// - internal static void UnlockLuceneIndexes(this IExamineManager examineManager, ILogger logger) + internal static void ConfigureLuceneIndexes(this IExamineManager examineManager, ILogger logger, bool disableExamineIndexing) { foreach (var luceneIndexer in examineManager.Indexes.OfType()) { @@ -86,6 +86,8 @@ namespace Umbraco.Examine //that could end up halting shutdown for a very long time causing overlapping appdomains and many other problems. luceneIndexer.WaitForIndexQueueOnShutdown = false; + if (disableExamineIndexing) continue; //exit if not enabled, we don't need to unlock them if we're not maindom + //we should check if the index is locked ... it shouldn't be! We are using simple fs lock now and we are also ensuring that //the indexes are not operational unless MainDom is true var dir = luceneIndexer.GetLuceneDirectory(); diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index 74b9e720b1..ed248a9e24 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -30,8 +30,9 @@ namespace Umbraco.Web.Search private readonly IValueSetBuilder _mediaValueSetBuilder; private readonly IValueSetBuilder _memberValueSetBuilder; private static bool _disableExamineIndexing = false; - private static volatile bool _isConfigured = false; - private static readonly object IsConfiguredLocker = new object(); + private static bool _isConfigured = false; + private static object _configuredInit = null; + private static object _isConfiguredLocker = new object(); private readonly IScopeProvider _scopeProvider; private readonly ServiceContext _services; private static BackgroundTaskRunner _rebuildOnStartupRunner; @@ -91,7 +92,7 @@ namespace Umbraco.Web.Search if (!examineShutdownRegistered) { - _logger.Debug("Examine shutdown not registered, this AppDomain is not the MainDom, Examine will be disabled"); + _logger.Info("Examine shutdown not registered, this AppDomain is not the MainDom, Examine will be disabled"); //if we could not register the shutdown examine ourselves, it means we are not maindom! in this case all of examine should be disabled! Suspendable.ExamineEvents.SuspendIndexers(_logger); @@ -120,7 +121,7 @@ namespace Umbraco.Web.Search MediaCacheRefresher.CacheUpdated += MediaCacheRefresherUpdated; MemberCacheRefresher.CacheUpdated += MemberCacheRefresherUpdated; - EnsureUnlocked(_logger, _examineManager); + ConfigureIndexes(_logger, _examineManager); // TODO: Instead of waiting 5000 ms, we could add an event handler on to fulfilling the first request, then start? RebuildIndexes(_indexRebuilder, _logger, true, 5000); @@ -161,25 +162,24 @@ namespace Umbraco.Web.Search } /// - /// Must be called to each index is unlocked before any indexing occurs + /// Called on startup to configure each index. /// /// /// Indexing rebuilding can occur on a normal boot if the indexes are empty or on a cold boot by the database server messenger. Before /// either of these happens, we need to configure the indexes. + /// Configuring also ensure the indexes are not locked. /// - private static void EnsureUnlocked(ILogger logger, IExamineManager examineManager) + private static void ConfigureIndexes(ILogger logger, IExamineManager examineManager) { - if (_disableExamineIndexing) return; - if (_isConfigured) return; - - lock (IsConfiguredLocker) - { - //double check - if (_isConfigured) return; - - _isConfigured = true; - examineManager.UnlockLuceneIndexes(logger); - } + LazyInitializer.EnsureInitialized( + ref _configuredInit, + ref _isConfigured, + ref _isConfiguredLocker, + () => + { + examineManager.ConfigureLuceneIndexes(logger, _disableExamineIndexing); + return null; + }); } #region Cache refresher updated event handlers @@ -800,7 +800,7 @@ namespace Umbraco.Web.Search if (_waitMilliseconds > 0) Thread.Sleep(_waitMilliseconds); - EnsureUnlocked(_logger, _indexRebuilder.ExamineManager); + ConfigureIndexes(_logger, _indexRebuilder.ExamineManager); _indexRebuilder.RebuildIndexes(_onlyEmptyIndexes); } } From 686562909032fe95f618fd047f1297e7e087c108 Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Tue, 4 Jun 2019 14:41:50 +1000 Subject: [PATCH 118/776] use position:sticky and IntersectionObserver instead of element cloning and position:fixed - much simpler. --- .../components/umbstickybar.directive.js | 197 +++++++----------- 1 file changed, 75 insertions(+), 122 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js index 2f2df7c12b..912a122c91 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js @@ -4,7 +4,7 @@ @restrict A @description -Use this directive make an element sticky and follow the page when scrolling. +Use this directive make an element sticky and follow the page when scrolling. `umb-sticky-bar--active` class is applied when the element is stuck

Markup example

@@ -12,140 +12,93 @@ Use this directive make an element sticky and follow the page when scrolling.
 
         
+ umb-sticky-bar>
-

CSS example

-
-    .my-sticky-bar {
-        padding: 15px 0;
-        background: #000000;
-        position: relative;
-        top: 0;
-    }
-
-    .my-sticky-bar.-umb-sticky-bar {
-        top: 100px;
-    }
-
- -@param {string} scrollableContainer Set the class (".element") or the id ("#element") of the scrollable container element. **/ (function () { 'use strict'; - function StickyBarDirective($rootScope) { + function StickyBarDirective() { + + /** + Toggle `umb-sticky-bar--active` class on the sticky-bar element + **/ + function setClass(addClass, current) { + current.classList.toggle('umb-sticky-bar--active', addClass); + } + + /** + Inserts two elements in the umbStickyBar parent element + These are used by the IntersectionObserve to calculate scroll position + **/ + function addSentinels(current) { + ['-top', '-bottom'].forEach(s => { + const sentinel = document.createElement('div'); + sentinel.classList.add('umb-sticky-sentinel', s); + current.parentElement.appendChild(sentinel); + }); + } + + /** + Calls into setClass when the footer sentinel enters/exits the bottom of the container + Container is the parent element of the umbStickyBar element + **/ + function observeFooter(current, container) { + const observer = new IntersectionObserver((records, observer) => { + let [target, rootBounds, intersected] + = [records[0].boundingClientRect, records[0].rootBounds, records[0].intersectionRatio === 1]; + + if (target.bottom > rootBounds.top && intersected) { + setClass(true, current); + } + if (target.top < rootBounds.top && target.bottom < rootBounds.bottom) { + setClass(false, current); + } + }, { + threshold: [1], + root: container + }); + + observer.observe(current.parentElement.querySelector('.umb-sticky-sentinel.-bottom')); + } + + /** + Calls into setClass when the header sentinel enters/exits the top of the container + Container is the parent element of the umbStickyBar element + **/ + function observeHeader(current, container) { + const observer = new IntersectionObserver((records, observer) => { + let [target, rootBounds] = [records[0].boundingClientRect, records[0].rootBounds]; + + if (target.bottom < rootBounds.top) { + setClass(true, current); + } + + if (target.bottom >= rootBounds.top && target.bottom < rootBounds.bottom) { + setClass(false, current); + } + }, { + threshold: [0], + root: container + }); + + observer.observe(current.parentElement.querySelector('.umb-sticky-sentinel.-top')); + } function link(scope, el, attr, ctrl) { + + let _el = el[0]; + let container = _el.closest('[data-element="editor-container"]'); + + addSentinels(_el); - var bar = $(el); - var scrollableContainer = null; - var clonedBar = null; - var cloneIsMade = false; - - function activate() { - - if (bar.parents(".umb-property").length > 1) { - bar.addClass("nested"); - return; - } - - if (attr.scrollableContainer) { - scrollableContainer = bar.closest(attr.scrollableContainer); - } else { - scrollableContainer = $(window); - } - - scrollableContainer.on('scroll.umbStickyBar', determineVisibility).trigger("scroll"); - $(window).on('resize.umbStickyBar', determineVisibility); - - scope.$on('$destroy', function () { - scrollableContainer.off('.umbStickyBar'); - $(window).off('.umbStickyBar'); - }); - - } - - function determineVisibility() { - - var barTop = bar[0].offsetTop; - var scrollTop = scrollableContainer.scrollTop(); - - if (scrollTop > barTop) { - - if (!cloneIsMade) { - - createClone(); - - clonedBar.css({ - 'visibility': 'visible' - }); - - } else { - - calculateSize(); - - } - - } else { - - if (cloneIsMade) { - - //remove cloned element (switched places with original on creation) - bar.remove(); - bar = clonedBar; - clonedBar = null; - - bar.removeClass('-umb-sticky-bar'); - bar.css({ - position: 'relative', - 'width': 'auto', - 'height': 'auto', - 'z-index': 'auto', - 'visibility': 'visible' - }); - - cloneIsMade = false; - - } - - } - - } - - function calculateSize() { - var width = bar.innerWidth(); - clonedBar.css({ - width: width + 10 // + 10 (5*2) because we need to add border to avoid seeing the shadow beneath. Look at the CSS. - }); - } - - function createClone() { - //switch place with cloned element, to keep binding intact - clonedBar = bar; - bar = clonedBar.clone(); - clonedBar.after(bar); - clonedBar.addClass('-umb-sticky-bar'); - clonedBar.css({ - 'position': 'fixed', - // if you change this z-index value, make sure the sticky editor sub headers do not - // clash with umb-dropdown (e.g. the content actions dropdown in content list view) - 'z-index': 99, - 'visibility': 'hidden' - }); - - cloneIsMade = true; - calculateSize(); - - } - - activate(); - + observeHeader(_el, container); + observeFooter(_el, container); } var directive = { From d17022d87dcdb6a6867be6caf2da5f7f36cec47b Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Tue, 4 Jun 2019 14:42:04 +1000 Subject: [PATCH 119/776] make it look pretty --- .../subheader/umb-editor-sub-header.less | 29 +++++++++++++++---- .../src/less/property-editors.less | 3 +- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less index 78cccac57a..6cf3598638 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less @@ -32,15 +32,32 @@ border-radius: 3px; } -.umb-editor-sub-header.-umb-sticky-bar { - box-shadow: 0 6px 3px -3px rgba(0,0,0,.16); +[umb-sticky-bar] { transition: box-shadow 240ms; - margin-top: 0; + margin-top: 0; margin-bottom: 0; - top: calc(@appHeaderHeight + @editorHeaderHeight); + position:sticky; + z-index: 99; - .umb-editor--infinityMode & { - top: calc(@editorHeaderHeight); + &.umb-sticky-bar--active { + box-shadow: 0 6px 3px -3px rgba(0,0,0,.16); + } +} + +.umb-sticky-sentinel { + position: absolute; + left: 0; + width: 100%; + pointer-events: none; + + &.-top { + top:0px; + height:1px; + } + + &.-bottom { + bottom:50px; + height:10px; } } 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 9e8dd37ab9..92351d09ca 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -5,6 +5,7 @@ // -------------------------------------------------- .umb-property-editor { width: 100%; + position:relative; } .umb-property-editor-tiny { @@ -165,8 +166,6 @@ .sp-replacer { display: inline-flex; margin-right: 18px; - border: solid 1px @gray-8; - border-radius: 3px; } label { From 469054e9b34293f08cf6ef97ea131f916d324d88 Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Tue, 4 Jun 2019 14:42:20 +1000 Subject: [PATCH 120/776] No longer need scrollable-container attribute --- .../views/components/editor/subheader/umb-editor-sub-header.html | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header.html index c385223baf..140931ec4b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header.html @@ -1,6 +1,5 @@
From e92f68d04054ead39f10f12f270003d985d7a276 Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Tue, 4 Jun 2019 14:55:12 +1000 Subject: [PATCH 121/776] improve variable naming --- .../directives/components/umbstickybar.directive.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js index 912a122c91..9538930e0a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js @@ -92,13 +92,13 @@ Use this directive make an element sticky and follow the page when scrolling. `u function link(scope, el, attr, ctrl) { - let _el = el[0]; + let current = el[0]; let container = _el.closest('[data-element="editor-container"]'); - addSentinels(_el); + addSentinels(current); - observeHeader(_el, container); - observeFooter(_el, container); + observeHeader(current, container); + observeFooter(current, container); } var directive = { From 3144b51c91cc8843811a235e3b2161fc3f9b9f2a Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Thu, 27 Jun 2019 18:58:39 +1000 Subject: [PATCH 122/776] fix behaviour on page load - don't add active class if sticky-bar is in viewport --- .../components/umbstickybar.directive.js | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js index 9538930e0a..07e45ff0f7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js @@ -24,14 +24,24 @@ Use this directive make an element sticky and follow the page when scrolling. `u 'use strict'; function StickyBarDirective() { - + + /** + On initial load, the intersector fires if the grid editor is in the viewport + This flag is used to suppress the setClass behaviour on the initial load + **/ + var initial = true; + /** Toggle `umb-sticky-bar--active` class on the sticky-bar element - **/ + **/ function setClass(addClass, current) { - current.classList.toggle('umb-sticky-bar--active', addClass); + if (!initial) { + current.classList.toggle('umb-sticky-bar--active', addClass); + } else { + initial = false; + } } - + /** Inserts two elements in the umbStickyBar parent element These are used by the IntersectionObserve to calculate scroll position @@ -40,9 +50,9 @@ Use this directive make an element sticky and follow the page when scrolling. `u ['-top', '-bottom'].forEach(s => { const sentinel = document.createElement('div'); sentinel.classList.add('umb-sticky-sentinel', s); - current.parentElement.appendChild(sentinel); + current.parentElement.appendChild(sentinel); }); - } + } /** Calls into setClass when the footer sentinel enters/exits the bottom of the container @@ -50,15 +60,14 @@ Use this directive make an element sticky and follow the page when scrolling. `u **/ function observeFooter(current, container) { const observer = new IntersectionObserver((records, observer) => { - let [target, rootBounds, intersected] - = [records[0].boundingClientRect, records[0].rootBounds, records[0].intersectionRatio === 1]; - + let [target, rootBounds, intersected] = [records[0].boundingClientRect, records[0].rootBounds, records[0].intersectionRatio === 1]; + if (target.bottom > rootBounds.top && intersected) { setClass(true, current); } if (target.top < rootBounds.top && target.bottom < rootBounds.bottom) { setClass(false, current); - } + } }, { threshold: [1], root: container @@ -66,14 +75,14 @@ Use this directive make an element sticky and follow the page when scrolling. `u observer.observe(current.parentElement.querySelector('.umb-sticky-sentinel.-bottom')); } - + /** Calls into setClass when the header sentinel enters/exits the top of the container Container is the parent element of the umbStickyBar element - **/ + **/ function observeHeader(current, container) { const observer = new IntersectionObserver((records, observer) => { - let [target, rootBounds] = [records[0].boundingClientRect, records[0].rootBounds]; + let [target, rootBounds] = [records[0].boundingClientRect, records[0].rootBounds]; if (target.bottom < rootBounds.top) { setClass(true, current); @@ -86,19 +95,19 @@ Use this directive make an element sticky and follow the page when scrolling. `u threshold: [0], root: container }); - + observer.observe(current.parentElement.querySelector('.umb-sticky-sentinel.-top')); } function link(scope, el, attr, ctrl) { - - let current = el[0]; - let container = _el.closest('[data-element="editor-container"]'); - + + let current = el[0]; + let container = current.closest('[data-element="editor-container"]'); + addSentinels(current); observeHeader(current, container); - observeFooter(current, container); + observeFooter(current, container); } var directive = { From df896af47672b37b70526492e197c98077ce7d1e Mon Sep 17 00:00:00 2001 From: Claus Date: Wed, 10 Jul 2019 12:58:15 +0200 Subject: [PATCH 123/776] adding build targets package. --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 ++ src/Umbraco.Web.UI/packages.config | 1 + 2 files changed, 3 insertions(+) diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index a0624a2dd7..646b86f665 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -1,5 +1,6 @@  + @@ -1088,5 +1089,6 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\" + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config index 18cbfdb042..a8271c1370 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -32,6 +32,7 @@ + From 39bd18ec1a7bcd5449753d737af46b0203b42792 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 11 Jul 2019 10:45:41 +0100 Subject: [PATCH 124/776] At some point we have upgraded Serilog.Filters.Expressions package & the signature/method name got changed to ByIncludingOnly() --- src/Umbraco.Web.UI/config/serilog.user.Release.config | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/config/serilog.user.Release.config b/src/Umbraco.Web.UI/config/serilog.user.Release.config index 24e5e4e4be..8f207406e3 100644 --- a/src/Umbraco.Web.UI/config/serilog.user.Release.config +++ b/src/Umbraco.Web.UI/config/serilog.user.Release.config @@ -26,12 +26,13 @@ --> + From 943b54dcf7713617c529c10edd115d65e0078f1b Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 11 Jul 2019 11:55:01 +0200 Subject: [PATCH 125/776] Update 1_Bug.md --- .github/ISSUE_TEMPLATE/1_Bug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/1_Bug.md b/.github/ISSUE_TEMPLATE/1_Bug.md index a1e33e3854..619452f700 100644 --- a/.github/ISSUE_TEMPLATE/1_Bug.md +++ b/.github/ISSUE_TEMPLATE/1_Bug.md @@ -12,7 +12,7 @@ thoroughly. Then, proceed by filling out the rest of the details in the issue template below. The more details you can give us, the easier it will be for us to determine the cause of a problem. -See: https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/.github/CONTRIBUTING.md +See: https://github.com/umbraco/Umbraco-CMS/blob/v8/dev/.github/CONTRIBUTING.md --> From 03a4b75176052cc15afe81e26b2b8f307c888e75 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 11 Jul 2019 15:11:37 +0100 Subject: [PATCH 126/776] Ran .\build SetUmbracoVersion 8.1.1 to bump version --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 9ed398d52f..841e054aee 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.1.0")] -[assembly: AssemblyInformationalVersion("8.1.0")] +[assembly: AssemblyFileVersion("8.1.1")] +[assembly: AssemblyInformationalVersion("8.1.1")] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index b27f6aa335..e58f44e1ae 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -345,9 +345,9 @@ False True - 8100 + 8110 / - http://localhost:8100 + http://localhost:8110 False False From 89bb94aa1ae542b17bf4f3672f0f00e69b37e5bc Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 12 Jul 2019 10:57:26 +1000 Subject: [PATCH 127/776] Fixes issue during upgrade where umbraco can't clear cdf log files --- .../JavaScript/ClientDependencyConfiguration.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web/JavaScript/ClientDependencyConfiguration.cs b/src/Umbraco.Web/JavaScript/ClientDependencyConfiguration.cs index 777786675f..2bf069b06d 100644 --- a/src/Umbraco.Web/JavaScript/ClientDependencyConfiguration.cs +++ b/src/Umbraco.Web/JavaScript/ClientDependencyConfiguration.cs @@ -106,8 +106,10 @@ namespace Umbraco.Web.JavaScript } try - { - var fullPath = currentHttpContext.Server.MapPath(XmlFileMapper.FileMapDefaultFolder); + { + var fullPath = XmlFileMapper.FileMapDefaultFolder.StartsWith("~/") + ? currentHttpContext.Server.MapPath(XmlFileMapper.FileMapDefaultFolder) + : XmlFileMapper.FileMapDefaultFolder; if (fullPath != null) { cdfTempDirectories.Add(fullPath); @@ -122,13 +124,12 @@ namespace Umbraco.Web.JavaScript var success = true; foreach (var directory in cdfTempDirectories) { - var directoryInfo = new DirectoryInfo(directory); - if (directoryInfo.Exists == false) - continue; - try { - directoryInfo.Delete(true); + if (!Directory.Exists(directory)) + continue; + + Directory.Delete(directory, true); } catch (Exception ex) { From 1d49c8626ec40519a19764959b18efb2868d8f61 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 12 Jul 2019 11:23:36 +1000 Subject: [PATCH 128/776] Revert "V8: Don't show multiple open menus (take two) (#5609)" This reverts commit 9ce996cbba49199423f4e79e223497d5c3c8cd4f. --- .../components/events/events.directive.js | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js index 47e6818466..15e74bbd90 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js @@ -101,14 +101,14 @@ angular.module('umbraco.directives') var eventBindings = []; function oneTimeClick(event) { - // ignore clicks on button groups toggles (i.e. the save and publish button) - var parents = $(event.target).closest("[data-element='button-group-toggle']"); - if (parents.length > 0) { - return; - } + var el = event.target.nodeName; + + //ignore link and button clicks + var els = ["INPUT", "A", "BUTTON"]; + if (els.indexOf(el) >= 0) { return; } // ignore clicks on new overlay - parents = $(event.target).parents(".umb-overlay,.umb-tour"); + var parents = $(event.target).parents("a,button,.umb-overlay,.umb-tour"); if (parents.length > 0) { return; } @@ -131,12 +131,6 @@ angular.module('umbraco.directives') return; } - // ignore clicks on dialog actions - var actions = $(event.target).parents(".umb-action"); - if (actions.length === 1) { - return; - } - //ignore clicks inside this element if ($(element).has($(event.target)).length > 0) { return; From 2252db0d55ae0c7f4ddba19bde6f408dbfb9eb93 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 12 Jul 2019 13:40:21 +1000 Subject: [PATCH 129/776] Fixes 5822 by reinstating optional method overloads of loadBaseType --- src/Umbraco.Core/Services/EntityService.cs | 28 +++++++++++++++++++ src/Umbraco.Core/Services/IEntityService.cs | 26 ++++++++++++++++- src/Umbraco.Core/Services/IRelationService.cs | 27 ++++++++++++++++++ src/Umbraco.Core/Services/RelationService.cs | 27 ++++++++++++++++++ 4 files changed, 107 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index f4b1b71732..84f5e2d9fd 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using Umbraco.Core.CodeAnnotations; @@ -717,5 +718,32 @@ namespace Umbraco.Core.Services } return node.NodeId; } + + #region Obsolete - only here for compat + + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IUmbracoEntity GetByKey(Guid key, bool loadBaseType = true) => GetByKey(key); + + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IUmbracoEntity Get(int id, bool loadBaseType = true) => Get(id); + + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IUmbracoEntity GetByKey(Guid key, UmbracoObjectTypes umbracoObjectType, bool loadBaseType = true) => GetByKey(key, umbracoObjectType); + + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IUmbracoEntity GetByKey(Guid key, bool loadBaseType = true) where T : IUmbracoEntity => GetByKey(key); + + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IUmbracoEntity Get(int id, UmbracoObjectTypes umbracoObjectType, bool loadBaseType = true) => Get(id, umbracoObjectType); + + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IUmbracoEntity Get(int id, bool loadBaseType = true) where T : IUmbracoEntity => Get(id); + #endregion } } diff --git a/src/Umbraco.Core/Services/IEntityService.cs b/src/Umbraco.Core/Services/IEntityService.cs index a6a2254138..a0c7363c60 100644 --- a/src/Umbraco.Core/Services/IEntityService.cs +++ b/src/Umbraco.Core/Services/IEntityService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -55,6 +56,10 @@ namespace Umbraco.Core.Services /// An IUmbracoEntity GetByKey(Guid key); + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + IUmbracoEntity GetByKey(Guid key, bool loadBaseType = true); + /// /// Gets an UmbracoEntity by its Id, and optionally loads the complete object graph. /// @@ -62,9 +67,13 @@ namespace Umbraco.Core.Services /// By default this will load the base type with a minimum set of properties. /// /// Id of the object to retrieve - /// An + /// An IUmbracoEntity Get(int id); + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + IUmbracoEntity Get(int id, bool loadBaseType = true); + /// /// Gets an UmbracoEntity by its Id and UmbracoObjectType, and optionally loads the complete object graph. /// @@ -76,6 +85,14 @@ namespace Umbraco.Core.Services /// An IUmbracoEntity GetByKey(Guid key, UmbracoObjectTypes umbracoObjectType); + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + IUmbracoEntity GetByKey(Guid key, UmbracoObjectTypes umbracoObjectType, bool loadBaseType = true); + + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + IUmbracoEntity GetByKey(Guid key, bool loadBaseType = true) where T : IUmbracoEntity; + /// /// Gets an UmbracoEntity by its Id and UmbracoObjectType, and optionally loads the complete object graph. /// @@ -87,6 +104,9 @@ namespace Umbraco.Core.Services /// An IUmbracoEntity Get(int id, UmbracoObjectTypes umbracoObjectType); + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + IUmbracoEntity Get(int id, UmbracoObjectTypes umbracoObjectType, bool loadBaseType = true); /// /// Gets an UmbracoEntity by its Id and specified Type. Optionally loads the complete object graph. @@ -99,6 +119,10 @@ namespace Umbraco.Core.Services /// An IUmbracoEntity Get(int id) where T : IUmbracoEntity; + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + IUmbracoEntity Get(int id, bool loadBaseType = true) where T : IUmbracoEntity; + /// /// Gets the parent of entity by its id /// diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs index b5ca9ee019..819952b697 100644 --- a/src/Umbraco.Core/Services/IRelationService.cs +++ b/src/Umbraco.Core/Services/IRelationService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -145,6 +146,10 @@ namespace Umbraco.Core.Services /// An IUmbracoEntity GetChildEntityFromRelation(IRelation relation); + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + IUmbracoEntity GetChildEntityFromRelation(IRelation relation, bool loadBaseType = false); + /// /// Gets the Parent object from a Relation as an /// @@ -152,6 +157,10 @@ namespace Umbraco.Core.Services /// An IUmbracoEntity GetParentEntityFromRelation(IRelation relation); + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + IUmbracoEntity GetParentEntityFromRelation(IRelation relation, bool loadBaseType = false); + /// /// Gets the Parent and Child objects from a Relation as a "/> with . /// @@ -159,6 +168,10 @@ namespace Umbraco.Core.Services /// Returns a Tuple with Parent (item1) and Child (item2) Tuple GetEntitiesFromRelation(IRelation relation); + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + Tuple GetEntitiesFromRelation(IRelation relation, bool loadBaseType = false); + /// /// Gets the Child objects from a list of Relations as a list of objects. /// @@ -166,6 +179,10 @@ namespace Umbraco.Core.Services /// An enumerable list of IEnumerable GetChildEntitiesFromRelations(IEnumerable relations); + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + IEnumerable GetChildEntitiesFromRelations(IEnumerable relations, bool loadBaseType = false); + /// /// Gets the Parent objects from a list of Relations as a list of objects. /// @@ -173,6 +190,10 @@ namespace Umbraco.Core.Services /// An enumerable list of IEnumerable GetParentEntitiesFromRelations(IEnumerable relations); + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + IEnumerable GetParentEntitiesFromRelations(IEnumerable relations, bool loadBaseType = false); + /// /// Gets the Parent and Child objects from a list of Relations as a list of objects. /// @@ -181,6 +202,12 @@ namespace Umbraco.Core.Services IEnumerable> GetEntitiesFromRelations( IEnumerable relations); + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + IEnumerable> GetEntitiesFromRelations( + IEnumerable relations, + bool loadBaseType = false); + /// /// Relates two objects by their entity Ids. /// diff --git a/src/Umbraco.Core/Services/RelationService.cs b/src/Umbraco.Core/Services/RelationService.cs index a940096ded..7ddd2ecf12 100644 --- a/src/Umbraco.Core/Services/RelationService.cs +++ b/src/Umbraco.Core/Services/RelationService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using Umbraco.Core.Events; using Umbraco.Core.Logging; @@ -736,5 +737,31 @@ namespace Umbraco.Core.Services /// public static event TypedEventHandler> SavedRelationType; #endregion + + #region Obsolete - only here for compat + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IUmbracoEntity GetChildEntityFromRelation(IRelation relation, bool loadBaseType = false) => GetChildEntityFromRelation(relation); + + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IUmbracoEntity GetParentEntityFromRelation(IRelation relation, bool loadBaseType = false) => GetParentEntityFromRelation(relation); + + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + public Tuple GetEntitiesFromRelation(IRelation relation, bool loadBaseType = false) => GetEntitiesFromRelation(relation); + + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetChildEntitiesFromRelations(IEnumerable relations, bool loadBaseType = false) => GetChildEntitiesFromRelations(relations); + + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable GetParentEntitiesFromRelations(IEnumerable relations, bool loadBaseType = false) => GetParentEntitiesFromRelations(relations); + + [Obsolete("Use the overload that doesn't specify loadBaseType instead, loadBaseType will not affect any results")] + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable> GetEntitiesFromRelations(IEnumerable relations, bool loadBaseType = false) => GetEntitiesFromRelations(relations); + #endregion } } From cf49e6160a691e1646cbebc139950f03b554dffe Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Fri, 12 Jul 2019 14:20:28 +1000 Subject: [PATCH 130/776] critical => fatal --- src/Umbraco.Web.UI/config/logviewer.searches.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/config/logviewer.searches.config.js b/src/Umbraco.Web.UI/config/logviewer.searches.config.js index 25ee9b2242..345fe23764 100644 --- a/src/Umbraco.Web.UI/config/logviewer.searches.config.js +++ b/src/Umbraco.Web.UI/config/logviewer.searches.config.js @@ -4,7 +4,7 @@ "query": "Not(@Level='Verbose') and Not(@Level='Debug')" }, { - "name": "Find all logs that has an exception property (Warning, Error & Critical with Exceptions)", + "name": "Find all logs that has an exception property (Warning, Error & Fatal with Exceptions)", "query": "Has(@Exception)" }, { From 3c2a92d3b630bdaf23ce20331b2501c21820adb0 Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Fri, 12 Jul 2019 14:21:28 +1000 Subject: [PATCH 131/776] map log values into logtypedata array - removes need to add these in the correct order. --- .../views/logviewer/overview.controller.js | 82 +++++++++++-------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js index 830a9f5c39..86d72feffd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js @@ -71,37 +71,37 @@ vm.loading = true; var savedSearches = logViewerResource.getSavedSearches().then(function (data) { - vm.searches = data; - }, - // fallback to some defaults if error from API response - function () { - vm.searches = [ - { - "name": "Find all logs where the Level is NOT Verbose and NOT Debug", - "query": "Not(@Level='Verbose') and Not(@Level='Debug')" + vm.searches = data; + }, + // fallback to some defaults if error from API response + function () { + vm.searches = [ + { + "name": "Find all logs where the Level is NOT Verbose and NOT Debug", + "query": "Not(@Level='Verbose') and Not(@Level='Debug')" }, - { - "name": "Find all logs that has an exception property (Warning, Error & Fatal with Exceptions)", - "query": "Has(@Exception)" + { + "name": "Find all logs that has an exception property (Warning, Error & Fatal with Exceptions)", + "query": "Has(@Exception)" }, - { - "name": "Find all logs that have the property 'Duration'", - "query": "Has(Duration)" + { + "name": "Find all logs that have the property 'Duration'", + "query": "Has(Duration)" }, - { - "name": "Find all logs that have the property 'Duration' and the duration is greater than 1000ms", - "query": "Has(Duration) and Duration > 1000" + { + "name": "Find all logs that have the property 'Duration' and the duration is greater than 1000ms", + "query": "Has(Duration) and Duration > 1000" }, - { - "name": "Find all logs that are from the namespace 'Umbraco.Core'", - "query": "StartsWith(SourceContext, 'Umbraco.Core')" + { + "name": "Find all logs that are from the namespace 'Umbraco.Core'", + "query": "StartsWith(SourceContext, 'Umbraco.Core')" }, - { - "name": "Find all logs that use a specific log message template", - "query": "@MessageTemplate = '[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)'" + { + "name": "Find all logs that use a specific log message template", + "query": "@MessageTemplate = '[Timing {TimingId}] {EndMessage} ({TimingDuration}ms)'" } ] - }); + }); var numOfErrors = logViewerResource.getNumberOfErrors(vm.startDate, vm.endDate).then(function (data) { vm.numberOfErrors = data; @@ -109,11 +109,13 @@ var logCounts = logViewerResource.getLogLevelCounts(vm.startDate, vm.endDate).then(function (data) { vm.logTypeData = []; - vm.logTypeData.push(data.Information); - vm.logTypeData.push(data.Debug); - vm.logTypeData.push(data.Warning); - vm.logTypeData.push(data.Error); - vm.logTypeData.push(data.Fatal); + + for (let [key, value] of Object.entries(data)) { + const index = vm.logTypeLabels.findIndex(x => key.startsWith(x)); + if (index > -1 && index < vm.logTypeData.length) { + vm.logTypeData[index] = value; + } + } }); var commonMsgs = logViewerResource.getMessageTemplates(vm.startDate, vm.endDate).then(function (data) { @@ -121,17 +123,24 @@ }); //Set loading indicator to false when these 3 queries complete - $q.all([savedSearches, numOfErrors, logCounts, commonMsgs]).then(function() { + $q.all([savedSearches, numOfErrors, logCounts, commonMsgs]).then(function () { vm.loading = false; }); - + $timeout(function () { - navigationService.syncTree({ tree: "logViewer", path: "-1" }); + navigationService.syncTree({ + tree: "logViewer", + path: "-1" + }); }); } function searchLogQuery(logQuery) { - $location.path("/settings/logViewer/search").search({ lq: logQuery, startDate: vm.startDate, endDate: vm.endDate }); + $location.path("/settings/logViewer/search").search({ + lq: logQuery, + startDate: vm.startDate, + endDate: vm.endDate + }); } function findMessageTemplate(template) { @@ -142,7 +151,7 @@ function getDateRangeLabel(suffix) { return "Log Overview for " + suffix; } - + preFlightCheck(); ///////////////////// @@ -166,7 +175,10 @@ // is collapsed to a comma. const startDate = selectedDates[0].toIsoDateString(); const endDate = selectedDates[selectedDates.length - 1].toIsoDateString(); // Take the last date as end - $location.path("/settings/logViewer/overview").search({ startDate: startDate, endDate: endDate }); + $location.path("/settings/logViewer/overview").search({ + startDate: startDate, + endDate: endDate + }); } } From d31e71a838abbbe5391049ad9cfc331ec31e895b Mon Sep 17 00:00:00 2001 From: skttl Date: Fri, 12 Jul 2019 07:33:17 +0200 Subject: [PATCH 132/776] changes innerjoin to left join, to allow folders in sql query --- src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index 7b10b19e1e..c8fdd8da57 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -370,7 +370,7 @@ namespace Umbraco.Core.Persistence.Repositories if (isMedia) { - entitySql.InnerJoin("cmsMedia media").On("media.nodeId = umbracoNode.id"); + entitySql.LeftJoin("cmsMedia media").On("media.nodeId = umbracoNode.id"); } entitySql.LeftJoin("cmsContentType contenttype").On("contenttype.nodeId = content.contentType"); From 59bf24c46110730ff59b070e47545316f9fb48a0 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 12 Jul 2019 11:22:31 +0200 Subject: [PATCH 133/776] Added null checks when *.dataTypeId is used --- .../src/common/resources/entity.resource.js | 4 +++- .../common/dialogs/linkpicker.controller.js | 6 +++++- .../linkpicker/linkpicker.controller.js | 15 ++++++++++++-- .../mediaPicker/mediapicker.controller.js | 20 ++++++++++++++++--- .../treepicker/treepicker.controller.js | 7 ++++++- .../contentpicker/contentpicker.controller.js | 7 ++++++- .../grid/editors/media.controller.js | 7 ++++++- .../grid/editors/rte.controller.js | 14 +++++++++++-- .../mediapicker/mediapicker.controller.js | 6 +++++- .../multiurlpicker.controller.js | 7 ++++++- .../relatedlinks/relatedlinks.controller.js | 14 +++++++++++-- .../propertyeditors/rte/rte.controller.js | 13 ++++++++++-- 12 files changed, 102 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index c85a85bb57..4e164cacf6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -409,7 +409,8 @@ function entityResource($q, $http, umbRequestHelper) { pageNumber: 100, filter: '', orderDirection: "Ascending", - orderBy: "SortOrder" + orderBy: "SortOrder", + dataTypeId: null }; if (options === undefined) { options = {}; @@ -426,6 +427,7 @@ function entityResource($q, $http, umbRequestHelper) { options.orderDirection = "Descending"; } + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js index e08178ec93..876a9f9426 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js @@ -8,13 +8,17 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", searchText = value + "..."; }); + var dataTypeId = null; + if($scope.model && $scope.model.dataTypeId) { + dataTypeId = $scope.model.dataTypeId; + } $scope.dialogTreeEventHandler = $({}); $scope.target = {}; $scope.searchInfo = { searchFromId: null, searchFromName: null, showSearch: false, - dataTypeId: $scope.model.dataTypeId, + dataTypeId: dataTypeId, results: [], selectedSearchResults: [] } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js index 2b6b77ed41..0c5641ba0a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js @@ -12,13 +12,19 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", $scope.model.title = localizationService.localize("defaultdialogs_selectLink"); } + var dataTypeId = null; + if(dialogOptions && dialogOptions.dataTypeId){ + dataTypeId = dialogOptions.dataTypeId; + } + + $scope.dialogTreeEventHandler = $({}); $scope.model.target = {}; $scope.searchInfo = { searchFromId: null, searchFromName: null, showSearch: false, - dataTypeId: dialogOptions.dataTypeId, + dataTypeId: dataTypeId, results: [], selectedSearchResults: [] }; @@ -115,12 +121,17 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", startNodeId = -1; startNodeIsVirtual = true; } + var dataTypeId = null; + if(dialogOptions && dialogOptions.dataTypeId){ + dataTypeId = dialogOptions.dataTypeId; + } + $scope.mediaPickerOverlay = { view: "mediapicker", startNodeId: startNodeId, startNodeIsVirtual: startNodeIsVirtual, show: true, - dataTypeId: dialogOptions.dataTypeId, + dataTypeId: dataTypeId, submit: function (model) { var media = model.selectedImages[0]; 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 bb34ba379c..61f9619a52 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 @@ -44,13 +44,17 @@ angular.module("umbraco") $scope.acceptedMediatypes = types; }); + var dataTypeId = null; + if($scope.model && $scope.model.dataTypeId) { + dataTypeId = $scope.model.dataTypeId; + } $scope.searchOptions = { pageNumber: 1, pageSize: 100, totalItems: 0, totalPages: 0, filter: '', - dataTypeId: $scope.model.dataTypeId + dataTypeId: dataTypeId }; //preload selected item @@ -157,7 +161,12 @@ angular.module("umbraco") } if (folder.id > 0) { - entityResource.getAncestors(folder.id, "media", { dataTypeId: $scope.model.dataTypeId }) + var dataTypeId = null; + if($scope.model && $scope.model.dataTypeId) { + dataTypeId = $scope.model.dataTypeId; + } + + entityResource.getAncestors(folder.id, "media", { dataTypeId: dataTypeId }) .then(function (anc) { $scope.path = _.filter(anc, function (f) { @@ -309,6 +318,11 @@ angular.module("umbraco") if ($scope.searchOptions.filter) { searchMedia(); } else { + var dataTypeId = null; + if($scope.model && $scope.model.dataTypeId) { + dataTypeId = $scope.model.dataTypeId; + } + // reset pagination $scope.searchOptions = { pageNumber: 1, @@ -316,7 +330,7 @@ angular.module("umbraco") totalItems: 0, totalPages: 0, filter: '', - dataTypeId: $scope.model.dataTypeId + dataTypeId: dataTypeId }; getChildren($scope.currentFolder.id); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js index b4dd34dfff..851d270789 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -4,6 +4,11 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", var tree = null; var dialogOptions = $scope.model; + + var dataTypeId = null; + if(dialogOptions && dialogOptions.dataTypeId){ + dataTypeId = dialogOptions.dataTypeId; + } $scope.treeReady = false; $scope.dialogTreeEventHandler = $({}); $scope.section = dialogOptions.section; @@ -16,7 +21,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", searchFromId: dialogOptions.startNodeId, searchFromName: null, showSearch: false, - dataTypeId: dialogOptions.dataTypeId, + dataTypeId: dataTypeId, results: [], selectedSearchResults: [] } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 187b86a811..37a372a5b1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -180,10 +180,15 @@ function contentPickerController($scope, entityResource, editorState, iconHelper //dialog $scope.openContentPicker = function() { + var dataTypeId = null; + if($scope.model && $scope.model.dataTypeId) { + dataTypeId = $scope.model.dataTypeId; + } + $scope.contentPickerOverlay = dialogOptions; $scope.contentPickerOverlay.view = "treepicker"; $scope.contentPickerOverlay.show = true; - $scope.contentPickerOverlay.dataTypeId = $scope.model.dataTypeId; + $scope.contentPickerOverlay.dataTypeId = dataTypeId; $scope.contentPickerOverlay.submit = function(model) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index c61f5419ca..e9c095c1b9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -16,11 +16,16 @@ angular.module("umbraco") } $scope.setImage = function(){ + var dataTypeId = null; + if($scope.model && $scope.model.dataTypeId) { + dataTypeId = $scope.model.dataTypeId; + } + $scope.mediaPickerOverlay = {}; $scope.mediaPickerOverlay.view = "mediapicker"; $scope.mediaPickerOverlay.startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined; $scope.mediaPickerOverlay.startNodeIsVirtual = $scope.mediaPickerOverlay.startNodeId ? $scope.model.config.startNodeIsVirtual : undefined; - $scope.mediaPickerOverlay.dataTypeId = $scope.model.dataTypeId; + $scope.mediaPickerOverlay.dataTypeId = dataTypeId; $scope.mediaPickerOverlay.cropSize = $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined; $scope.mediaPickerOverlay.showDetails = true; $scope.mediaPickerOverlay.disableFolderSelect = true; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js index da869dbf01..580d231de8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js @@ -13,11 +13,16 @@ function openLinkPicker(editor, currentTarget, anchorElement) { entityResource.getAnchors(JSON.stringify($scope.model.value)).then(function(anchorValues) { + var dataTypeId = null; + if($scope.model && $scope.model.dataTypeId) { + dataTypeId = $scope.model.dataTypeId; + } + vm.linkPickerOverlay = { view: "linkpicker", currentTarget: currentTarget, anchors: anchorValues, - dataTypeId: $scope.model.dataTypeId, + dataTypeId: dataTypeId, ignoreUserStartNodes : $scope.model.config.ignoreUserStartNodes, show: true, submit: function(model) { @@ -40,13 +45,18 @@ startNodeIsVirtual = true; } + var dataTypeId = null; + if($scope.model && $scope.model.dataTypeId) { + dataTypeId = $scope.model.dataTypeId; + } + vm.mediaPickerOverlay = { currentTarget: currentTarget, onlyImages: true, showDetails: true, startNodeId: startNodeId, startNodeIsVirtual: startNodeIsVirtual, - dataTypeId: $scope.model.dataTypeId, + dataTypeId: dataTypeId, view: "mediapicker", show: true, submit: function(model) { 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 138bb386be..63beddb49c 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 @@ -107,13 +107,17 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl }; $scope.add = function() { + var dataTypeId = null; + if($scope.model && $scope.model.dataTypeId) { + dataTypeId = $scope.model.dataTypeId; + } $scope.mediaPickerOverlay = { view: "mediapicker", title: "Select media", startNodeId: $scope.model.config.startNodeId, startNodeIsVirtual: $scope.model.config.startNodeIsVirtual, - dataTypeId: $scope.model.dataTypeId, + dataTypeId: dataTypeId, multiPicker: multiPicker, onlyImages: onlyImages, disableFolderSelect: disableFolderSelect, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js index 124f342741..cab124ad23 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js @@ -68,10 +68,15 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en target: link.target } : null; + var dataTypeId = null; + if($scope.model && $scope.model.dataTypeId) { + dataTypeId = $scope.model.dataTypeId; + } + $scope.linkPickerOverlay = { view: "linkpicker", currentTarget: target, - dataTypeId: $scope.model.dataTypeId, + dataTypeId: dataTypeId, ignoreUserStartNodes : $scope.model.config.ignoreUserStartNodes, show: true, submit: function (model) { 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 26c6a0c051..fa92442eac 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 @@ -19,13 +19,18 @@ $scope.hasError = false; $scope.internal = function($event) { + var dataTypeId = null; + if($scope.model && $scope.model.dataTypeId) { + dataTypeId = $scope.model.dataTypeId; + } + $scope.currentEditLink = null; $scope.contentPickerOverlay = {}; $scope.contentPickerOverlay.view = "contentpicker"; $scope.contentPickerOverlay.multiPicker = false; $scope.contentPickerOverlay.show = true; - $scope.contentPickerOverlay.dataTypeId = $scope.model.dataTypeId; + $scope.contentPickerOverlay.dataTypeId = dataTypeId; $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; $scope.contentPickerOverlay.submit = function(model) { @@ -45,13 +50,18 @@ }; $scope.selectInternal = function ($event, link) { + var dataTypeId = null; + if($scope.model && $scope.model.dataTypeId) { + dataTypeId = $scope.model.dataTypeId; + } + $scope.currentEditLink = link; $scope.contentPickerOverlay = {}; $scope.contentPickerOverlay.view = "contentpicker"; $scope.contentPickerOverlay.multiPicker = false; $scope.contentPickerOverlay.show = true; - $scope.contentPickerOverlay.dataTypeId = $scope.model.dataTypeId; + $scope.contentPickerOverlay.dataTypeId = dataTypeId; $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int"; $scope.contentPickerOverlay.submit = function(model) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index dd9fb404c1..2b3ee930d9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -272,11 +272,16 @@ angular.module("umbraco") tinyMceService.createLinkPicker(editor, $scope, function(currentTarget, anchorElement) { entityResource.getAnchors($scope.model.value).then(function(anchorValues){ + var dataTypeId = null; + if($scope.model && $scope.model.dataTypeId) { + dataTypeId = $scope.model.dataTypeId; + } + $scope.linkPickerOverlay = { view: "linkpicker", currentTarget: currentTarget, anchors: anchorValues, - dataTypeId: $scope.model.dataTypeId, + dataTypeId: dataTypeId, ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes, show: true, submit: function(model) { @@ -300,6 +305,10 @@ angular.module("umbraco") startNodeId = -1; startNodeIsVirtual = true; } + var dataTypeId = null; + if($scope.model && $scope.model.dataTypeId) { + dataTypeId = $scope.model.dataTypeId; + } $scope.mediaPickerOverlay = { currentTarget: currentTarget, @@ -308,7 +317,7 @@ angular.module("umbraco") disableFolderSelect: true, startNodeId: startNodeId, startNodeIsVirtual: startNodeIsVirtual, - dataTypeId: $scope.model.dataTypeId, + dataTypeId: dataTypeId, view: "mediapicker", show: true, submit: function(model) { From fdd9aee7737bccfa7e70ee957a30b10c100d0863 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 12 Jul 2019 11:07:15 +0100 Subject: [PATCH 134/776] Fixes LogViewer Error Count search result clickthrough to include errors & fatal without exception as well --- .../src/views/logviewer/overview.controller.js | 7 ++++++- .../src/views/logviewer/overview.html | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js index fc24fbe1bc..ffc05e8146 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js @@ -42,7 +42,8 @@ //functions vm.searchLogQuery = searchLogQuery; vm.findMessageTemplate = findMessageTemplate; - + vm.searchErrors = searchErrors; + function preFlightCheck(){ vm.loading = true; //Do our pre-flight check (to see if we can view logs) @@ -132,6 +133,10 @@ searchLogQuery(logQuery); } + function searchErrors(){ + var logQuery = "@Level='Fatal' or @Level='Error' or Has(@Exception)"; + searchLogQuery(logQuery); + } diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html index 854bed755f..5c3cf13072 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.html @@ -84,7 +84,7 @@
- + {{ vm.numberOfErrors }} From 61a18297d96bd12ca7982863b06c207c93921912 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 12 Jul 2019 13:03:20 +0100 Subject: [PATCH 135/776] Make v8/dev now 8.2.0 --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 841e054aee..a4e859988e 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.1.1")] -[assembly: AssemblyInformationalVersion("8.1.1")] +[assembly: AssemblyFileVersion("8.2.0")] +[assembly: AssemblyInformationalVersion("8.2.0")] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index e58f44e1ae..147912ed41 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -345,9 +345,9 @@ False True - 8110 + 8200 / - http://localhost:8110 + http://localhost:8200 False False From 5d5bd54aee50a92fef3e096c1c97524c958b1c4f Mon Sep 17 00:00:00 2001 From: skttl Date: Fri, 12 Jul 2019 21:07:55 +0200 Subject: [PATCH 136/776] 5867 enables tabbing to field-lock-toggler, and adds tab-focus styling --- .../src/less/components/umb-locked-field.less | 19 ++++++++++++++++--- .../views/components/umb-locked-field.html | 8 ++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-locked-field.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-locked-field.less index 8d9ae86ce7..5a14c82210 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-locked-field.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-locked-field.less @@ -6,13 +6,26 @@ } .umb-locked-field__wrapper { - display: flex; - align-items: center; - margin-bottom: 5px; + display: flex; + align-items: center; + margin-bottom: 5px; } .umb-locked-field__toggle { margin-right: 3px; + padding: 0; + background: none; + border: 0; + font-size: inherit; + line-height: inherit; + + &:focus { + outline: none; + + .tabbing-active & { + outline: 2px solid @inputBorderTabFocus; + } + } } .umb-locked-field__toggle:hover, diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html index 624ca8e923..5d5f10218f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html @@ -2,13 +2,13 @@
- + - + Date: Sat, 13 Jul 2019 13:45:13 +1000 Subject: [PATCH 137/776] fix empty array - was checking index against empty array, so condition was never met --- .../src/views/logviewer/overview.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js index 86d72feffd..b622d7a517 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/overview.controller.js @@ -112,7 +112,7 @@ for (let [key, value] of Object.entries(data)) { const index = vm.logTypeLabels.findIndex(x => key.startsWith(x)); - if (index > -1 && index < vm.logTypeData.length) { + if (index > -1) { vm.logTypeData[index] = value; } } From 1073e6257c56f8cd118e09695468d43c4616be2a Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Sat, 13 Jul 2019 14:48:35 +0200 Subject: [PATCH 138/776] Adjust comments --- .../common/overlays/linkpicker/linkpicker.controller.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js index 2b6b77ed41..d9f907eacc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js @@ -28,24 +28,23 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", if (dialogOptions.currentTarget) { // clone the current target so we don't accidentally update the caller's model while manipulating $scope.model.target $scope.model.target = angular.copy(dialogOptions.currentTarget); - //if we have a node ID, we fetch the current node to build the form data + // if we have a node ID, we fetch the current node to build the form data if ($scope.model.target.id || $scope.model.target.udi) { - //will be either a udi or an int + // will be either a udi or an int var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id; // is it a content link? if (!$scope.model.target.isMedia) { // get the content path entityResource.getPath(id, "Document").then(function (path) { - //now sync the tree to this path + // now sync the tree to this path $scope.dialogTreeEventHandler.syncTree({ path: path, tree: "content" }); }); - entityResource.getUrlAndAnchors(id).then(function(resp){ $scope.anchorValues = resp.anchorValues; $scope.model.target.url = resp.url; From 82eb241119a39f4ee49283538c562241dca6e326 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Sat, 13 Jul 2019 14:49:05 +0200 Subject: [PATCH 139/776] Add overload method for Udi --- src/Umbraco.Web/Editors/EntityController.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 85d5b607b6..ada0206545 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -56,6 +56,7 @@ namespace Umbraco.Web.Editors //id is passed in eventually we'll probably want to support GUID + Udi too new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPagedChildren", "id", typeof(int), typeof(string)), new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPath", "id", typeof(int), typeof(Guid), typeof(Udi)), + new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetUrlAndAnchors", "id", typeof(int), typeof(Guid), typeof(Udi)), new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)), new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetByIds", "ids", typeof(int[]), typeof(Guid[]), typeof(Udi[])))); } @@ -281,9 +282,17 @@ namespace Umbraco.Web.Editors publishedContentExists: i => Umbraco.TypedContent(i) != null); } + [HttpGet] + public UrlAndAnchors GetUrlAndAnchors(Udi id) + { + var nodeId = Umbraco.GetIdForUdi(id); + var url = Umbraco.Url(nodeId); + var anchorValues = Services.ContentService.GetAnchorValuesFromRTEs(nodeId); + return new UrlAndAnchors(url, anchorValues); + } [HttpGet] - public UrlAndAnchors GetUrlAndAnchors([FromUri]int id) + public UrlAndAnchors GetUrlAndAnchors(int id) { var url = Umbraco.Url(id); var anchorValues = Services.ContentService.GetAnchorValuesFromRTEs(id); From 7c03fe3c1f4e963f1e68e32968aef39ca577beeb Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Sat, 13 Jul 2019 14:49:35 +0200 Subject: [PATCH 140/776] Adjust parameter --- .../src/common/resources/entity.resource.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index c85a85bb57..9453ca8c41 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -172,7 +172,7 @@ function entityResource($q, $http, umbRequestHelper) { umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetUrlAndAnchors", - { id: id })), + [{ id: id }])), 'Failed to retrieve url and anchors data for id ' + id); }, From de9241bcf592ca9994810d43232545844fc06848 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Sat, 13 Jul 2019 14:50:25 +0200 Subject: [PATCH 141/776] Add UmbracoHelper overload methods to get url by guid --- src/Umbraco.Web/UmbracoHelper.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index c00d37f216..2c7cafb298 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -502,6 +502,16 @@ namespace Umbraco.Web return UrlProvider.GetUrl(contentId); } + /// + /// Gets the url of a content identified by its identifier. + /// + /// The content identifier. + /// The url for the content. + public string Url(Guid contentGuid) + { + return UrlProvider.GetUrl(contentGuid); + } + /// /// Gets the url of a content identified by its identifier, in a specified mode. /// @@ -513,6 +523,17 @@ namespace Umbraco.Web return UrlProvider.GetUrl(contentId, mode); } + /// + /// Gets the url of a content identified by its identifier, in a specified mode. + /// + /// The content identifier. + /// The mode. + /// The url for the content. + public string Url(Guid contentGuid, UrlProviderMode mode) + { + return UrlProvider.GetUrl(contentGuid, mode); + } + /// /// This method will always add the domain to the path if the hostnames are set up correctly. /// From c47a08b5532aa3b858ec2cfbe3daa2f09b7bf4f6 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Sat, 13 Jul 2019 16:40:01 +0200 Subject: [PATCH 142/776] Add overload for NiceUrl --- src/Umbraco.Web/UmbracoHelper.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 2c7cafb298..46ae098e2e 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -492,6 +492,18 @@ namespace Umbraco.Web return Url(nodeId); } + /// + /// Returns a string with a friendly url from a node. + /// IE.: Instead of having /482 (id) as an url, you can have + /// /screenshots/developer/macros (spoken url) + /// + /// Identifier for the node that should be returned + /// String with a friendly url from a node + public string NiceUrl(Guid guid) + { + return Url(guid); + } + /// /// Gets the url of a content identified by its identifier. /// From 62f924a7572b92c84398b994c83ff1a5da961b07 Mon Sep 17 00:00:00 2001 From: hifi-phil Date: Sun, 14 Jul 2019 09:45:12 +0100 Subject: [PATCH 143/776] Change PropertyType.Alias to virtual When unit testing PropertyType.Alias needs to be virtual to make it mockable. The underlying code on the set triggers IOC logic and makes it hard to test anything that uses Property Types. --- src/Umbraco.Core/Models/PropertyType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 34775ccf89..61736607c6 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -114,7 +114,7 @@ namespace Umbraco.Core.Models /// Gets of sets the alias of the property type. ///
[DataMember] - public string Alias + public virtual string Alias { get => _alias; set => SetPropertyValueAndDetectChanges(SanitizeAlias(value), ref _alias, nameof(Alias)); From 320ccf810556f6381b35eba6943ffa824f5a7985 Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Sun, 14 Jul 2019 16:46:01 +0100 Subject: [PATCH 144/776] Fixes #5852 - TablesForScheduledPublishing --- .../V_8_0_0/TablesForScheduledPublishing.cs | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs index 70dbe3d29e..2f7ffe8679 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs @@ -14,11 +14,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { //Get anything currently scheduled - var scheduleSql = new Sql() - .Select("nodeId", "releaseDate", "expireDate") + var releaseSql = new Sql() + .Select("nodeId", "releaseDate") .From("umbracoDocument") - .Where("releaseDate IS NOT NULL OR expireDate IS NOT NULL"); - var schedules = Database.Dictionary (scheduleSql); + .Where("releaseDate IS NOT NULL"); + var releases = Database.Dictionary (releaseSql); + + var expireSql = new Sql() + .Select("nodeId", "expireDate") + .From("umbracoDocument") + .Where("expireDate IS NOT NULL"); + var expires = Database.Dictionary(expireSql); + //drop old cols Delete.Column("releaseDate").FromTable("umbracoDocument").Do(); @@ -27,20 +34,25 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 Create.Table(true).Do(); //migrate the schedule - foreach(var s in schedules) + foreach(var s in releases) { - var date = s.Value.releaseDate; + var date = s.Value; var action = ContentScheduleAction.Release.ToString(); - if (!date.HasValue) - { - date = s.Value.expireDate; - action = ContentScheduleAction.Expire.ToString(); - } Insert.IntoTable(ContentScheduleDto.TableName) - .Row(new { nodeId = s.Key, date = date.Value, action = action }) + .Row(new { id = Guid.NewGuid(), nodeId = s.Key, date = date, action = action }) .Do(); } + + foreach (var s in expires) + { + var date = s.Value; + var action = ContentScheduleAction.Expire.ToString(); + + Insert.IntoTable(ContentScheduleDto.TableName) + .Row(new { id = Guid.NewGuid(), nodeId = s.Key, date = date, action = action }) + .Do(); + } } } } From 4e0dd728a82a20a25b6500a6e450d0e4ecac376d Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Sun, 14 Jul 2019 16:48:33 +0100 Subject: [PATCH 145/776] Fixes #5864 - migrate prevalues for Umbraco.DropDown.Flexible --- .../DropDownFlexiblePreValueMigrator.cs | 32 +++++++++++++++++++ .../DataTypes/PreValueMigratorComposer.cs | 1 + src/Umbraco.Core/Umbraco.Core.csproj | 1 + 3 files changed, 34 insertions(+) create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DropDownFlexiblePreValueMigrator.cs diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DropDownFlexiblePreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DropDownFlexiblePreValueMigrator.cs new file mode 100644 index 0000000000..35ca574bab --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DropDownFlexiblePreValueMigrator.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class DropDownFlexiblePreValueMigrator : IPreValueMigrator + { + public bool CanMigrate(string editorAlias) + => editorAlias == "Umbraco.DropDown.Flexible"; + + public virtual string GetNewAlias(string editorAlias) + => null; + + public object GetConfiguration(int dataTypeId, string editorAlias, Dictionary preValues) + { + var config = new DropDownFlexibleConfiguration(); + foreach (var preValue in preValues.Values) + { + if (preValue.Alias == "multiple") + { + config.Multiple = (preValue.Value == "1"); + } + else + { + config.Items.Add(new ValueListConfiguration.ValueListItem { Id = preValue.Id, Value = preValue.Value }); + } + } + return config; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs index 4c3f8534e7..8dfa464508 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs @@ -19,6 +19,7 @@ public class PreValueMigratorComposer : ICoreComposer .Append() .Append() .Append() + .Append() .Append(); } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index ca9b7d4034..6adce1944f 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -241,6 +241,7 @@ + From f7a10e3a15d89ac57e457b8c936597f31d72b7c7 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Sat, 13 Jul 2019 23:55:50 +0200 Subject: [PATCH 146/776] Added attribute to validate Umbraco form route strings (ufprt) --- .../HttpUmbracoFormRouteStringException.cs | 46 +++++++++++++++++ ...ValidateUmbracoFormRouteStringAttribute.cs | 51 +++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 2 + 3 files changed, 99 insertions(+) create mode 100644 src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs create mode 100644 src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs diff --git a/src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs b/src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs new file mode 100644 index 0000000000..8e0b50b11c --- /dev/null +++ b/src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs @@ -0,0 +1,46 @@ +using System; +using System.Runtime.Serialization; +using System.Web; + +namespace Umbraco.Web.Mvc +{ + /// + /// Describes an exception that occurred during the processing of an Umbraco form route string. + /// + /// + [Serializable] + public sealed class HttpUmbracoFormRouteStringException : HttpException + { + /// + /// Initializes a new instance of the class. + /// + public HttpUmbracoFormRouteStringException() + { } + + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that holds the contextual information about the source or destination. + private HttpUmbracoFormRouteStringException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The error message displayed to the client when the exception is thrown. + public HttpUmbracoFormRouteStringException(string message) + : base(message) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The error message displayed to the client when the exception is thrown. + /// The , if any, that threw the current exception. + public HttpUmbracoFormRouteStringException(string message, Exception innerException) + : base(message, innerException) + { } + } +} diff --git a/src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs b/src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs new file mode 100644 index 0000000000..2be89f05a6 --- /dev/null +++ b/src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs @@ -0,0 +1,51 @@ +using System; +using System.Web.Mvc; +using Umbraco.Core; + +namespace Umbraco.Web.Mvc +{ + /// + /// Represents an attribute that is used to prevent an invalid Umbraco form request route string on a request. + /// + /// + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class ValidateUmbracoFormRouteStringAttribute : FilterAttribute, IAuthorizationFilter + { + /// + /// Called when authorization is required. + /// + /// The filter context. + /// filterContext + /// The required request field \"ufprt\" is not present. + /// or + /// The Umbraco form request route string could not be decrypted. + /// or + /// The provided Umbraco form request route string was meant for a different controller and action. + public void OnAuthorization(AuthorizationContext filterContext) + { + if (filterContext == null) + { + throw new ArgumentNullException(nameof(filterContext)); + } + + var ufprt = filterContext.HttpContext.Request["ufprt"]; + if (ufprt.IsNullOrWhiteSpace()) + { + throw new HttpUmbracoFormRouteStringException("The required request field \"ufprt\" is not present."); + } + + if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(ufprt, out var additionalDataParts)) + { + throw new HttpUmbracoFormRouteStringException("The Umbraco form request route string could not be decrypted."); + } + + if (additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Controller] != filterContext.ActionDescriptor.ControllerDescriptor.ControllerName || + additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Action] != filterContext.ActionDescriptor.ActionName || + additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area].NullOrWhiteSpaceAsNull() != filterContext.RouteData.DataTokens["area"]?.ToString().NullOrWhiteSpaceAsNull()) + { + throw new HttpUmbracoFormRouteStringException("The provided Umbraco form request route string was meant for a different controller and action."); + } + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 105a40b4a7..39a05ddbc4 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -219,8 +219,10 @@ + + From 101a8b6c782db20c6018ad8f3120a1a0055d7a75 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Sat, 13 Jul 2019 23:56:29 +0200 Subject: [PATCH 147/776] Added ValidateUmbracoFormRouteStringAttribute to public controllers --- src/Umbraco.Web/Controllers/UmbLoginController.cs | 1 + src/Umbraco.Web/Controllers/UmbLoginStatusController.cs | 1 + src/Umbraco.Web/Controllers/UmbProfileController.cs | 1 + src/Umbraco.Web/Controllers/UmbRegisterController.cs | 1 + 4 files changed, 4 insertions(+) diff --git a/src/Umbraco.Web/Controllers/UmbLoginController.cs b/src/Umbraco.Web/Controllers/UmbLoginController.cs index 2980b4a4c0..2ff80e2668 100644 --- a/src/Umbraco.Web/Controllers/UmbLoginController.cs +++ b/src/Umbraco.Web/Controllers/UmbLoginController.cs @@ -22,6 +22,7 @@ namespace Umbraco.Web.Controllers [HttpPost] [ValidateAntiForgeryToken] + [ValidateUmbracoFormRouteString] public ActionResult HandleLogin([Bind(Prefix = "loginModel")]LoginModel model) { if (ModelState.IsValid == false) diff --git a/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs b/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs index fdc2de8c5f..8f572404fc 100644 --- a/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs +++ b/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs @@ -24,6 +24,7 @@ namespace Umbraco.Web.Controllers [HttpPost] [ValidateAntiForgeryToken] + [ValidateUmbracoFormRouteString] public ActionResult HandleLogout([Bind(Prefix = "logoutModel")]PostRedirectModel model) { if (ModelState.IsValid == false) diff --git a/src/Umbraco.Web/Controllers/UmbProfileController.cs b/src/Umbraco.Web/Controllers/UmbProfileController.cs index b14652ad2e..d9333e8e65 100644 --- a/src/Umbraco.Web/Controllers/UmbProfileController.cs +++ b/src/Umbraco.Web/Controllers/UmbProfileController.cs @@ -23,6 +23,7 @@ namespace Umbraco.Web.Controllers [HttpPost] [ValidateAntiForgeryToken] + [ValidateUmbracoFormRouteString] public ActionResult HandleUpdateProfile([Bind(Prefix = "profileModel")] ProfileModel model) { var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); diff --git a/src/Umbraco.Web/Controllers/UmbRegisterController.cs b/src/Umbraco.Web/Controllers/UmbRegisterController.cs index b0187d6127..4f4173a67d 100644 --- a/src/Umbraco.Web/Controllers/UmbRegisterController.cs +++ b/src/Umbraco.Web/Controllers/UmbRegisterController.cs @@ -24,6 +24,7 @@ namespace Umbraco.Web.Controllers [HttpPost] [ValidateAntiForgeryToken] + [ValidateUmbracoFormRouteString] public ActionResult HandleRegisterMember([Bind(Prefix = "registerModel")]RegisterModel model) { if (ModelState.IsValid == false) From b0c4042e8748baff647e9e12d0d6b9aeaf3f1636 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Mon, 15 Jul 2019 10:16:09 +0200 Subject: [PATCH 148/776] Removed UmbracoAntiForgeryAdditionalDataProvider --- ...oAntiForgeryAdditionalDataProviderTests.cs | 157 ------------------ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 8 - src/Umbraco.Web/Runtime/WebFinalComponent.cs | 2 - ...mbracoAntiForgeryAdditionalDataProvider.cs | 92 ---------- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 6 files changed, 261 deletions(-) delete mode 100644 src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs delete mode 100644 src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs diff --git a/src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs b/src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs deleted file mode 100644 index c81c108e0d..0000000000 --- a/src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System.Collections.Specialized; -using System.Web; -using System.Web.Helpers; -using Moq; -using Newtonsoft.Json; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web.Mvc; -using Umbraco.Web.Security; - -namespace Umbraco.Tests.Security -{ - [TestFixture] - public class UmbracoAntiForgeryAdditionalDataProviderTests - { - [Test] - public void Test_Wrapped_Non_BeginUmbracoForm() - { - var wrapped = Mock.Of(x => x.GetAdditionalData(It.IsAny()) == "custom"); - var provider = new UmbracoAntiForgeryAdditionalDataProvider(wrapped); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var data = provider.GetAdditionalData(httpContextFactory.HttpContext); - - Assert.IsTrue(data.DetectIsJson()); - var json = JsonConvert.DeserializeObject(data); - Assert.AreEqual(null, json.Ufprt); - Assert.IsTrue(json.Stamp != default); - Assert.AreEqual("custom", json.WrappedValue); - } - - [Test] - public void Null_Wrapped_Non_BeginUmbracoForm() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var data = provider.GetAdditionalData(httpContextFactory.HttpContext); - - Assert.IsTrue(data.DetectIsJson()); - var json = JsonConvert.DeserializeObject(data); - Assert.AreEqual(null, json.Ufprt); - Assert.IsTrue(json.Stamp != default); - Assert.AreEqual("default", json.WrappedValue); - } - - [Test] - public void Validate_Non_Json() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "hello"); - - Assert.IsFalse(isValid); - } - - [Test] - public void Validate_Invalid_Json() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '0'}"); - Assert.IsFalse(isValid); - - isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': ''}"); - Assert.IsFalse(isValid); - - isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'hello': 'world'}"); - Assert.IsFalse(isValid); - - } - - [Test] - public void Validate_No_Request_Ufprt() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - //there is a ufprt in the additional data, but not in the request - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': 'ASBVDFDFDFDF'}"); - Assert.IsFalse(isValid); - } - - [Test] - public void Validate_No_AdditionalData_Ufprt() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var requestMock = Mock.Get(httpContextFactory.HttpContext.Request); - requestMock.SetupGet(x => x["ufprt"]).Returns("ABCDEFG"); - - //there is a ufprt in the additional data, but not in the request - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': ''}"); - Assert.IsFalse(isValid); - } - - [Test] - public void Validate_No_AdditionalData_Or_Request_Ufprt() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - - //there is a ufprt in the additional data, but not in the request - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': ''}"); - Assert.IsTrue(isValid); - } - - [Test] - public void Validate_Request_And_AdditionalData_Ufprt() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var routeParams1 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; - var routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var requestMock = Mock.Get(httpContextFactory.HttpContext.Request); - requestMock.SetupGet(x => x["ufprt"]).Returns(routeParams1.EncryptWithMachineKey()); - - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); - Assert.IsTrue(isValid); - - routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Invalid")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; - isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); - Assert.IsFalse(isValid); - } - - [Test] - public void Validate_Wrapped_Request_And_AdditionalData_Ufprt() - { - var wrapped = Mock.Of(x => x.ValidateAdditionalData(It.IsAny(), "custom") == true); - var provider = new UmbracoAntiForgeryAdditionalDataProvider(wrapped); - - var routeParams1 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; - var routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var requestMock = Mock.Get(httpContextFactory.HttpContext.Request); - requestMock.SetupGet(x => x["ufprt"]).Returns(routeParams1.EncryptWithMachineKey()); - - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); - Assert.IsFalse(isValid); - - isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'custom', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); - Assert.IsTrue(isValid); - - routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Invalid")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; - isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); - Assert.IsFalse(isValid); - } - } -} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index cc569d6989..4f4a83dc26 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -147,7 +147,6 @@ - diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index e9b731348d..7249500441 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -223,14 +223,6 @@ namespace Umbraco.Web _method = method; _controllerName = controllerName; _encryptedString = UrlHelperRenderExtensions.CreateEncryptedRouteString(controllerName, controllerAction, area, additionalRouteVals); - - //For UmbracoForm's we want to add our routing string to the httpcontext items in the case where anti-forgery tokens are used. - //In which case our custom UmbracoAntiForgeryAdditionalDataProvider will kick in and validate the values in the request against - //the values that will be appended to the token. This essentially means that when anti-forgery tokens are used with UmbracoForm's forms, - //that each token is unique to the controller/action/area instead of the default ASP.Net implementation which is that the token is unique - //per user. - _viewContext.HttpContext.Items["ufprt"] = _encryptedString; - } diff --git a/src/Umbraco.Web/Runtime/WebFinalComponent.cs b/src/Umbraco.Web/Runtime/WebFinalComponent.cs index ba606e8d5e..6177d9b868 100644 --- a/src/Umbraco.Web/Runtime/WebFinalComponent.cs +++ b/src/Umbraco.Web/Runtime/WebFinalComponent.cs @@ -36,8 +36,6 @@ namespace Umbraco.Web.Runtime // ensure WebAPI is initialized, after everything GlobalConfiguration.Configuration.EnsureInitialized(); - - AntiForgeryConfig.AdditionalDataProvider = new UmbracoAntiForgeryAdditionalDataProvider(AntiForgeryConfig.AdditionalDataProvider); } public void Terminate() diff --git a/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs b/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs deleted file mode 100644 index c6ad4c6901..0000000000 --- a/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using Umbraco.Web.Mvc; -using Umbraco.Core; -using System.Web.Helpers; -using System.Web; -using Newtonsoft.Json; -using Umbraco.Web.Composing; - -namespace Umbraco.Web.Security -{ - /// - /// A custom to create a unique antiforgery token/validator per form created with BeginUmbracoForm - /// - public class UmbracoAntiForgeryAdditionalDataProvider : IAntiForgeryAdditionalDataProvider - { - private readonly IAntiForgeryAdditionalDataProvider _defaultProvider; - - /// - /// Constructor, allows wrapping a default provider - /// - /// - public UmbracoAntiForgeryAdditionalDataProvider(IAntiForgeryAdditionalDataProvider defaultProvider) - { - _defaultProvider = defaultProvider; - } - - public string GetAdditionalData(HttpContextBase context) - { - return JsonConvert.SerializeObject(new AdditionalData - { - Stamp = DateTime.UtcNow.Ticks, - //this value will be here if this is a BeginUmbracoForms form - Ufprt = context.Items["ufprt"]?.ToString(), - //if there was a wrapped provider, add it's value to the json, else just a static value - WrappedValue = _defaultProvider?.GetAdditionalData(context) ?? "default" - }); - } - - public bool ValidateAdditionalData(HttpContextBase context, string additionalData) - { - if (!additionalData.DetectIsJson()) - return false; //must be json - - AdditionalData json; - try - { - json = JsonConvert.DeserializeObject(additionalData); - } - catch - { - return false; //couldn't parse - } - - if (json.Stamp == default) return false; - - //if there was a wrapped provider, validate it, else validate the static value - var validateWrapped = _defaultProvider?.ValidateAdditionalData(context, json.WrappedValue) ?? json.WrappedValue == "default"; - if (!validateWrapped) - return false; - - var ufprtRequest = context.Request["ufprt"]?.ToString(); - - //if the custom BeginUmbracoForms route value is not there, then it's nothing more to validate - if (ufprtRequest.IsNullOrWhiteSpace() && json.Ufprt.IsNullOrWhiteSpace()) - return true; - - //if one or the other is null then something is wrong - if (!ufprtRequest.IsNullOrWhiteSpace() && json.Ufprt.IsNullOrWhiteSpace()) return false; - if (ufprtRequest.IsNullOrWhiteSpace() && !json.Ufprt.IsNullOrWhiteSpace()) return false; - - if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(json.Ufprt, out var additionalDataParts)) - return false; - - if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(ufprtRequest, out var requestParts)) - return false; - - //ensure they all match - return additionalDataParts.Count == requestParts.Count - && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Controller] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Controller] - && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Action] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Action] - && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Area]; - } - - internal class AdditionalData - { - public string Ufprt { get; set; } - public long Stamp { get; set; } - public string WrappedValue { get; set; } - } - - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 39a05ddbc4..41a3393fa4 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -232,7 +232,6 @@ - From d60fc63e789f01202d5c86544b2ed994666fb475 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 15 Jul 2019 12:23:09 +0200 Subject: [PATCH 149/776] Fix YSOD when editing media types --- .../PublishedCache/NuCache/PublishedSnapshotService.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index dad9811af8..f26122d4d0 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -1057,7 +1057,10 @@ namespace Umbraco.Web.PublishedCache.NuCache _mediaStore.UpdateContentTypes(removedIds, typesA, kits); _mediaStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray()); - _mediaStore.NewContentTypes(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray()); + if(newIds != null && newIds.Any()) + { + _mediaStore.NewContentTypes(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray()); + } scope.Complete(); } } From f6a8d487ccd6a29487f6ef5f8d9e9aad0e6a54f6 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 15 Jul 2019 13:06:47 +0200 Subject: [PATCH 150/776] AB#1695 - https://github.com/umbraco/Umbraco-CMS/issues/5846 Give better exception, if a json exception happens doing migration of grid properties in ConvertTinyMceAndGridMediaUrlsToLocalLink --- ...nvertTinyMceAndGridMediaUrlsToLocalLink.cs | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs index bf048bf2bd..5f49680d89 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using Newtonsoft.Json; @@ -36,6 +37,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 var properties = Database.Fetch(sqlPropertyData); + var exceptions = new List(); foreach (var property in properties) { var value = property.TextValue; @@ -43,19 +45,34 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 if (property.PropertyTypeDto.DataTypeDto.EditorAlias == Constants.PropertyEditors.Aliases.Grid) { - var obj = JsonConvert.DeserializeObject(value); - var allControls = obj.SelectTokens("$.sections..rows..areas..controls"); - - foreach (var control in allControls.SelectMany(c => c)) + try { - var controlValue = control["value"]; - if (controlValue.Type == JTokenType.String) + var obj = JsonConvert.DeserializeObject(value); + var allControls = obj.SelectTokens("$.sections..rows..areas..controls"); + + foreach (var control in allControls.SelectMany(c => c)) { - control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value()); + var controlValue = control["value"]; + if (controlValue.Type == JTokenType.String) + { + control["value"] = UpdateMediaUrls(mediaLinkPattern, controlValue.Value()); + } } + + property.TextValue = JsonConvert.SerializeObject(obj); + } + catch (JsonException e) + { + exceptions.Add(new InvalidOperationException( + "Cannot deserialize the value as json. This can be because the property editor " + + "type is changed from another type into a grid. Old versions of the value in this " + + "property can have the structure from the old property editor type. This needs to be " + + "changed manually before updating the database.\n" + + $"Property info: Id = {property.Id}, LanguageId = {property.LanguageId}, VersionId = {property.VersionId}, Value = {property.Value}" + , e)); + continue; } - property.TextValue = JsonConvert.SerializeObject(obj); } else { @@ -65,6 +82,12 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 Database.Update(property); } + + if (exceptions.Any()) + { + throw new AggregateException(exceptions); + } + Context.AddPostMigration(); } From 77dda5a70f139b272a0e442980f04d9978bf96d4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 12 Jul 2019 02:57:26 +0200 Subject: [PATCH 151/776] Fixes issue during upgrade where umbraco can't clear cdf log files --- .../JavaScript/ClientDependencyConfiguration.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web/JavaScript/ClientDependencyConfiguration.cs b/src/Umbraco.Web/JavaScript/ClientDependencyConfiguration.cs index 777786675f..2bf069b06d 100644 --- a/src/Umbraco.Web/JavaScript/ClientDependencyConfiguration.cs +++ b/src/Umbraco.Web/JavaScript/ClientDependencyConfiguration.cs @@ -106,8 +106,10 @@ namespace Umbraco.Web.JavaScript } try - { - var fullPath = currentHttpContext.Server.MapPath(XmlFileMapper.FileMapDefaultFolder); + { + var fullPath = XmlFileMapper.FileMapDefaultFolder.StartsWith("~/") + ? currentHttpContext.Server.MapPath(XmlFileMapper.FileMapDefaultFolder) + : XmlFileMapper.FileMapDefaultFolder; if (fullPath != null) { cdfTempDirectories.Add(fullPath); @@ -122,13 +124,12 @@ namespace Umbraco.Web.JavaScript var success = true; foreach (var directory in cdfTempDirectories) { - var directoryInfo = new DirectoryInfo(directory); - if (directoryInfo.Exists == false) - continue; - try { - directoryInfo.Delete(true); + if (!Directory.Exists(directory)) + continue; + + Directory.Delete(directory, true); } catch (Exception ex) { From 2aaca865e76ba45720b5b9954d81a83af6233dbb Mon Sep 17 00:00:00 2001 From: skttl Date: Fri, 12 Jul 2019 07:33:17 +0200 Subject: [PATCH 152/776] changes innerjoin to left join, to allow folders in sql query --- src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index 7b10b19e1e..c8fdd8da57 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -370,7 +370,7 @@ namespace Umbraco.Core.Persistence.Repositories if (isMedia) { - entitySql.InnerJoin("cmsMedia media").On("media.nodeId = umbracoNode.id"); + entitySql.LeftJoin("cmsMedia media").On("media.nodeId = umbracoNode.id"); } entitySql.LeftJoin("cmsContentType contenttype").On("contenttype.nodeId = content.contentType"); From da71a94c39fbd5403c114344a5d5627adacc88b0 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 16 Jul 2019 12:45:27 +0200 Subject: [PATCH 153/776] Use IsCollectionEmpty() as per review --- .../PublishedCache/NuCache/PublishedSnapshotService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index f26122d4d0..b78c806824 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -1057,7 +1057,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _mediaStore.UpdateContentTypes(removedIds, typesA, kits); _mediaStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray()); - if(newIds != null && newIds.Any()) + if(newIds.IsCollectionEmpty() == false) { _mediaStore.NewContentTypes(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray()); } From 09c8ae4e7ec9a3c8e321d8bdaa1f988a9ed62780 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 16 Jul 2019 13:36:16 +0200 Subject: [PATCH 154/776] Fixed null ref exception in related links migrations --- .../V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs index ed1c08f0f8..44f7affe8e 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs @@ -50,10 +50,10 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 var properties = Database.Fetch(sqlPropertyData); // Create a Multi URL Picker datatype for the converted RelatedLinks data - + foreach (var property in properties) { - var value = property.Value.ToString(); + var value = property.Value?.ToString(); if (string.IsNullOrWhiteSpace(value)) continue; @@ -89,7 +89,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 Name = relatedLink.Caption, Target = relatedLink.NewWindow ? "_blank" : null, Udi = udi, - // Should only have a URL if it's an external link otherwise it wil be a UDI + // Should only have a URL if it's an external link otherwise it wil be a UDI Url = relatedLink.IsInternal == false ? relatedLink.Link : null }; @@ -103,7 +103,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 Database.Update(property); } - + } } From 0feb053e517047bd8941f7234911e8284bd229cc Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Sun, 14 Jul 2019 17:46:01 +0200 Subject: [PATCH 155/776] Fixes #5852 - TablesForScheduledPublishing --- .../V_8_0_0/TablesForScheduledPublishing.cs | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs index 70dbe3d29e..2f7ffe8679 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs @@ -14,11 +14,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { //Get anything currently scheduled - var scheduleSql = new Sql() - .Select("nodeId", "releaseDate", "expireDate") + var releaseSql = new Sql() + .Select("nodeId", "releaseDate") .From("umbracoDocument") - .Where("releaseDate IS NOT NULL OR expireDate IS NOT NULL"); - var schedules = Database.Dictionary (scheduleSql); + .Where("releaseDate IS NOT NULL"); + var releases = Database.Dictionary (releaseSql); + + var expireSql = new Sql() + .Select("nodeId", "expireDate") + .From("umbracoDocument") + .Where("expireDate IS NOT NULL"); + var expires = Database.Dictionary(expireSql); + //drop old cols Delete.Column("releaseDate").FromTable("umbracoDocument").Do(); @@ -27,20 +34,25 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 Create.Table(true).Do(); //migrate the schedule - foreach(var s in schedules) + foreach(var s in releases) { - var date = s.Value.releaseDate; + var date = s.Value; var action = ContentScheduleAction.Release.ToString(); - if (!date.HasValue) - { - date = s.Value.expireDate; - action = ContentScheduleAction.Expire.ToString(); - } Insert.IntoTable(ContentScheduleDto.TableName) - .Row(new { nodeId = s.Key, date = date.Value, action = action }) + .Row(new { id = Guid.NewGuid(), nodeId = s.Key, date = date, action = action }) .Do(); } + + foreach (var s in expires) + { + var date = s.Value; + var action = ContentScheduleAction.Expire.ToString(); + + Insert.IntoTable(ContentScheduleDto.TableName) + .Row(new { id = Guid.NewGuid(), nodeId = s.Key, date = date, action = action }) + .Do(); + } } } } From 3062f40ef5dd8ccc50dfee7c6edd5401690da71a Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 16 Jul 2019 13:43:10 +0200 Subject: [PATCH 156/776] Missing file in project file --- src/Umbraco.Core/Umbraco.Core.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index ca9b7d4034..6adce1944f 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -241,6 +241,7 @@ + From 0cbe0b47967f7cc70f8d6291e983a1b706e37405 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 16 Jul 2019 22:34:34 +1000 Subject: [PATCH 157/776] Cherry picks ValidateUmbracoFormRouteStringAttribute implementation --- .../Controllers/UmbLoginController.cs | 1 + .../Controllers/UmbLoginStatusController.cs | 1 + .../Controllers/UmbProfileController.cs | 1 + .../Controllers/UmbRegisterController.cs | 1 + .../HttpUmbracoFormRouteStringException.cs | 23 ++++++++ ...ValidateUmbracoFormRouteStringAttribute.cs | 52 +++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 2 + 7 files changed, 81 insertions(+) create mode 100644 src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs create mode 100644 src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs diff --git a/src/Umbraco.Web/Controllers/UmbLoginController.cs b/src/Umbraco.Web/Controllers/UmbLoginController.cs index 2980b4a4c0..2ff80e2668 100644 --- a/src/Umbraco.Web/Controllers/UmbLoginController.cs +++ b/src/Umbraco.Web/Controllers/UmbLoginController.cs @@ -22,6 +22,7 @@ namespace Umbraco.Web.Controllers [HttpPost] [ValidateAntiForgeryToken] + [ValidateUmbracoFormRouteString] public ActionResult HandleLogin([Bind(Prefix = "loginModel")]LoginModel model) { if (ModelState.IsValid == false) diff --git a/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs b/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs index fdc2de8c5f..8f572404fc 100644 --- a/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs +++ b/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs @@ -24,6 +24,7 @@ namespace Umbraco.Web.Controllers [HttpPost] [ValidateAntiForgeryToken] + [ValidateUmbracoFormRouteString] public ActionResult HandleLogout([Bind(Prefix = "logoutModel")]PostRedirectModel model) { if (ModelState.IsValid == false) diff --git a/src/Umbraco.Web/Controllers/UmbProfileController.cs b/src/Umbraco.Web/Controllers/UmbProfileController.cs index b14652ad2e..d9333e8e65 100644 --- a/src/Umbraco.Web/Controllers/UmbProfileController.cs +++ b/src/Umbraco.Web/Controllers/UmbProfileController.cs @@ -23,6 +23,7 @@ namespace Umbraco.Web.Controllers [HttpPost] [ValidateAntiForgeryToken] + [ValidateUmbracoFormRouteString] public ActionResult HandleUpdateProfile([Bind(Prefix = "profileModel")] ProfileModel model) { var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); diff --git a/src/Umbraco.Web/Controllers/UmbRegisterController.cs b/src/Umbraco.Web/Controllers/UmbRegisterController.cs index b0187d6127..4f4173a67d 100644 --- a/src/Umbraco.Web/Controllers/UmbRegisterController.cs +++ b/src/Umbraco.Web/Controllers/UmbRegisterController.cs @@ -24,6 +24,7 @@ namespace Umbraco.Web.Controllers [HttpPost] [ValidateAntiForgeryToken] + [ValidateUmbracoFormRouteString] public ActionResult HandleRegisterMember([Bind(Prefix = "registerModel")]RegisterModel model) { if (ModelState.IsValid == false) diff --git a/src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs b/src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs new file mode 100644 index 0000000000..d4734e5d24 --- /dev/null +++ b/src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs @@ -0,0 +1,23 @@ +using System; +using System.Net; +using System.Web; + +namespace Umbraco.Web.Mvc +{ + /// + /// Exception that occurs when an Umbraco form route string is invalid + /// + /// + [Serializable] + public sealed class HttpUmbracoFormRouteStringException : HttpException + { + /// + /// Initializes a new instance of the class. + /// + /// The error message displayed to the client when the exception is thrown. + public HttpUmbracoFormRouteStringException(string message) + : base(message) + { } + + } +} diff --git a/src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs b/src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs new file mode 100644 index 0000000000..a57ce7e7f7 --- /dev/null +++ b/src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs @@ -0,0 +1,52 @@ +using System; +using System.Net; +using System.Web.Mvc; +using Umbraco.Core; + +namespace Umbraco.Web.Mvc +{ + /// + /// Represents an attribute that is used to prevent an invalid Umbraco form request route string on a request. + /// + /// + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class ValidateUmbracoFormRouteStringAttribute : FilterAttribute, IAuthorizationFilter + { + /// + /// Called when authorization is required. + /// + /// The filter context. + /// filterContext + /// The required request field \"ufprt\" is not present. + /// or + /// The Umbraco form request route string could not be decrypted. + /// or + /// The provided Umbraco form request route string was meant for a different controller and action. + public void OnAuthorization(AuthorizationContext filterContext) + { + if (filterContext == null) + { + throw new ArgumentNullException(nameof(filterContext)); + } + + var ufprt = filterContext.HttpContext.Request["ufprt"]; + if (ufprt.IsNullOrWhiteSpace()) + { + throw new HttpUmbracoFormRouteStringException("The required Umbraco request data is invalid."); + } + + if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(ufprt, out var additionalDataParts)) + { + throw new HttpUmbracoFormRouteStringException("The required Umbraco request data is invalid."); + } + + if (additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Controller] != filterContext.ActionDescriptor.ControllerDescriptor.ControllerName || + additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Action] != filterContext.ActionDescriptor.ActionName || + additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area].NullOrWhiteSpaceAsNull() != filterContext.RouteData.DataTokens["area"]?.ToString().NullOrWhiteSpaceAsNull()) + { + throw new HttpUmbracoFormRouteStringException("The required Umbraco request data is invalid."); + } + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 105a40b4a7..39a05ddbc4 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -219,8 +219,10 @@ + + From d52420183e85e8a94b70b2cb0c68ab46e6f4a681 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 16 Jul 2019 23:03:26 +1000 Subject: [PATCH 158/776] Cherry picks ValidateUmbracoFormRouteStringAttribute implementation and fixes up some logic --- ...oAntiForgeryAdditionalDataProviderTests.cs | 157 ------------------ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - .../Controllers/UmbLoginController.cs | 1 + .../Controllers/UmbLoginStatusController.cs | 1 + .../Controllers/UmbProfileController.cs | 1 + .../Controllers/UmbRegisterController.cs | 1 + src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 7 - .../HttpUmbracoFormRouteStringException.cs | 23 +++ ...ValidateUmbracoFormRouteStringAttribute.cs | 52 ++++++ ...mbracoAntiForgeryAdditionalDataProvider.cs | 91 ---------- src/Umbraco.Web/Umbraco.Web.csproj | 3 +- src/Umbraco.Web/WebBootManager.cs | 2 - 12 files changed, 81 insertions(+), 259 deletions(-) delete mode 100644 src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs create mode 100644 src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs create mode 100644 src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs delete mode 100644 src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs diff --git a/src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs b/src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs deleted file mode 100644 index c81c108e0d..0000000000 --- a/src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System.Collections.Specialized; -using System.Web; -using System.Web.Helpers; -using Moq; -using Newtonsoft.Json; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web.Mvc; -using Umbraco.Web.Security; - -namespace Umbraco.Tests.Security -{ - [TestFixture] - public class UmbracoAntiForgeryAdditionalDataProviderTests - { - [Test] - public void Test_Wrapped_Non_BeginUmbracoForm() - { - var wrapped = Mock.Of(x => x.GetAdditionalData(It.IsAny()) == "custom"); - var provider = new UmbracoAntiForgeryAdditionalDataProvider(wrapped); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var data = provider.GetAdditionalData(httpContextFactory.HttpContext); - - Assert.IsTrue(data.DetectIsJson()); - var json = JsonConvert.DeserializeObject(data); - Assert.AreEqual(null, json.Ufprt); - Assert.IsTrue(json.Stamp != default); - Assert.AreEqual("custom", json.WrappedValue); - } - - [Test] - public void Null_Wrapped_Non_BeginUmbracoForm() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var data = provider.GetAdditionalData(httpContextFactory.HttpContext); - - Assert.IsTrue(data.DetectIsJson()); - var json = JsonConvert.DeserializeObject(data); - Assert.AreEqual(null, json.Ufprt); - Assert.IsTrue(json.Stamp != default); - Assert.AreEqual("default", json.WrappedValue); - } - - [Test] - public void Validate_Non_Json() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "hello"); - - Assert.IsFalse(isValid); - } - - [Test] - public void Validate_Invalid_Json() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '0'}"); - Assert.IsFalse(isValid); - - isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': ''}"); - Assert.IsFalse(isValid); - - isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'hello': 'world'}"); - Assert.IsFalse(isValid); - - } - - [Test] - public void Validate_No_Request_Ufprt() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - //there is a ufprt in the additional data, but not in the request - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': 'ASBVDFDFDFDF'}"); - Assert.IsFalse(isValid); - } - - [Test] - public void Validate_No_AdditionalData_Ufprt() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var requestMock = Mock.Get(httpContextFactory.HttpContext.Request); - requestMock.SetupGet(x => x["ufprt"]).Returns("ABCDEFG"); - - //there is a ufprt in the additional data, but not in the request - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': ''}"); - Assert.IsFalse(isValid); - } - - [Test] - public void Validate_No_AdditionalData_Or_Request_Ufprt() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - - //there is a ufprt in the additional data, but not in the request - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': ''}"); - Assert.IsTrue(isValid); - } - - [Test] - public void Validate_Request_And_AdditionalData_Ufprt() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var routeParams1 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; - var routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var requestMock = Mock.Get(httpContextFactory.HttpContext.Request); - requestMock.SetupGet(x => x["ufprt"]).Returns(routeParams1.EncryptWithMachineKey()); - - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); - Assert.IsTrue(isValid); - - routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Invalid")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; - isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); - Assert.IsFalse(isValid); - } - - [Test] - public void Validate_Wrapped_Request_And_AdditionalData_Ufprt() - { - var wrapped = Mock.Of(x => x.ValidateAdditionalData(It.IsAny(), "custom") == true); - var provider = new UmbracoAntiForgeryAdditionalDataProvider(wrapped); - - var routeParams1 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; - var routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var requestMock = Mock.Get(httpContextFactory.HttpContext.Request); - requestMock.SetupGet(x => x["ufprt"]).Returns(routeParams1.EncryptWithMachineKey()); - - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); - Assert.IsFalse(isValid); - - isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'custom', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); - Assert.IsTrue(isValid); - - routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Invalid")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; - isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); - Assert.IsFalse(isValid); - } - } -} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 7e6fdf4823..72939c12d9 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -197,7 +197,6 @@ - diff --git a/src/Umbraco.Web/Controllers/UmbLoginController.cs b/src/Umbraco.Web/Controllers/UmbLoginController.cs index ba46d0a17e..a952a944bc 100644 --- a/src/Umbraco.Web/Controllers/UmbLoginController.cs +++ b/src/Umbraco.Web/Controllers/UmbLoginController.cs @@ -12,6 +12,7 @@ namespace Umbraco.Web.Controllers { [HttpPost] [ValidateAntiForgeryToken] + [ValidateUmbracoFormRouteString] public ActionResult HandleLogin([Bind(Prefix = "loginModel")]LoginModel model) { if (ModelState.IsValid == false) diff --git a/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs b/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs index 8e063bf2a3..e1c334bf0b 100644 --- a/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs +++ b/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs @@ -13,6 +13,7 @@ namespace Umbraco.Web.Controllers { [HttpPost] [ValidateAntiForgeryToken] + [ValidateUmbracoFormRouteString] public ActionResult HandleLogout([Bind(Prefix = "logoutModel")]PostRedirectModel model) { if (ModelState.IsValid == false) diff --git a/src/Umbraco.Web/Controllers/UmbProfileController.cs b/src/Umbraco.Web/Controllers/UmbProfileController.cs index 7def7af826..19d2905da1 100644 --- a/src/Umbraco.Web/Controllers/UmbProfileController.cs +++ b/src/Umbraco.Web/Controllers/UmbProfileController.cs @@ -16,6 +16,7 @@ namespace Umbraco.Web.Controllers { [HttpPost] [ValidateAntiForgeryToken] + [ValidateUmbracoFormRouteString] public ActionResult HandleUpdateProfile([Bind(Prefix = "profileModel")] ProfileModel model) { var provider = global::Umbraco.Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); diff --git a/src/Umbraco.Web/Controllers/UmbRegisterController.cs b/src/Umbraco.Web/Controllers/UmbRegisterController.cs index 7931565c47..3e20277537 100644 --- a/src/Umbraco.Web/Controllers/UmbRegisterController.cs +++ b/src/Umbraco.Web/Controllers/UmbRegisterController.cs @@ -11,6 +11,7 @@ namespace Umbraco.Web.Controllers { [HttpPost] [ValidateAntiForgeryToken] + [ValidateUmbracoFormRouteString] public ActionResult HandleRegisterMember([Bind(Prefix = "registerModel")]RegisterModel model) { if (ModelState.IsValid == false) diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 0e2a2a6450..180cf195aa 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -293,13 +293,6 @@ namespace Umbraco.Web _controllerName = controllerName; _encryptedString = UmbracoHelper.CreateEncryptedRouteString(controllerName, controllerAction, area, additionalRouteVals); - //For UmbracoForm's we want to add our routing string to the httpcontext items in the case where anti-forgery tokens are used. - //In which case our custom UmbracoAntiForgeryAdditionalDataProvider will kick in and validate the values in the request against - //the values that will be appended to the token. This essentially means that when anti-forgery tokens are used with UmbracoForm's forms, - //that each token is unique to the controller/action/area instead of the default ASP.Net implementation which is that the token is unique - //per user. - _viewContext.HttpContext.Items["ufprt"] = _encryptedString; - } diff --git a/src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs b/src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs new file mode 100644 index 0000000000..d4734e5d24 --- /dev/null +++ b/src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs @@ -0,0 +1,23 @@ +using System; +using System.Net; +using System.Web; + +namespace Umbraco.Web.Mvc +{ + /// + /// Exception that occurs when an Umbraco form route string is invalid + /// + /// + [Serializable] + public sealed class HttpUmbracoFormRouteStringException : HttpException + { + /// + /// Initializes a new instance of the class. + /// + /// The error message displayed to the client when the exception is thrown. + public HttpUmbracoFormRouteStringException(string message) + : base(message) + { } + + } +} diff --git a/src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs b/src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs new file mode 100644 index 0000000000..f3d0cc0e27 --- /dev/null +++ b/src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs @@ -0,0 +1,52 @@ +using System; +using System.Net; +using System.Web.Mvc; +using Umbraco.Core; + +namespace Umbraco.Web.Mvc +{ + /// + /// Represents an attribute that is used to prevent an invalid Umbraco form request route string on a request. + /// + /// + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class ValidateUmbracoFormRouteStringAttribute : FilterAttribute, IAuthorizationFilter + { + /// + /// Called when authorization is required. + /// + /// The filter context. + /// filterContext + /// The required request field \"ufprt\" is not present. + /// or + /// The Umbraco form request route string could not be decrypted. + /// or + /// The provided Umbraco form request route string was meant for a different controller and action. + public void OnAuthorization(AuthorizationContext filterContext) + { + if (filterContext == null) + { + throw new ArgumentNullException(nameof(filterContext)); + } + + var ufprt = filterContext.HttpContext.Request["ufprt"]; + if (ufprt.IsNullOrWhiteSpace()) + { + throw new HttpUmbracoFormRouteStringException("The required Umbraco request data is invalid."); + } + + if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(ufprt, out var additionalDataParts)) + { + throw new HttpUmbracoFormRouteStringException("The required Umbraco request data is invalid."); + } + + if (!additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Controller].InvariantEquals(filterContext.ActionDescriptor.ControllerDescriptor.ControllerName) || + !additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Action].InvariantEquals(filterContext.ActionDescriptor.ActionName) || + (!additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area].IsNullOrWhiteSpace() && !additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area].InvariantEquals(filterContext.RouteData.DataTokens["area"]?.ToString()))) + { + throw new HttpUmbracoFormRouteStringException("The required Umbraco request data is invalid."); + } + } + } +} diff --git a/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs b/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs deleted file mode 100644 index 660f7e58fc..0000000000 --- a/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using Umbraco.Web.Mvc; -using Umbraco.Core; -using System.Web.Helpers; -using System.Web; -using Newtonsoft.Json; - -namespace Umbraco.Web.Security -{ - /// - /// A custom to create a unique antiforgery token/validator per form created with BeginUmbracoForm - /// - public class UmbracoAntiForgeryAdditionalDataProvider : IAntiForgeryAdditionalDataProvider - { - private readonly IAntiForgeryAdditionalDataProvider _defaultProvider; - - /// - /// Constructor, allows wrapping a default provider - /// - /// - public UmbracoAntiForgeryAdditionalDataProvider(IAntiForgeryAdditionalDataProvider defaultProvider) - { - _defaultProvider = defaultProvider; - } - - public string GetAdditionalData(HttpContextBase context) - { - return JsonConvert.SerializeObject(new AdditionalData - { - Stamp = DateTime.UtcNow.Ticks, - //this value will be here if this is a BeginUmbracoForms form - Ufprt = context.Items["ufprt"]?.ToString(), - //if there was a wrapped provider, add it's value to the json, else just a static value - WrappedValue = _defaultProvider?.GetAdditionalData(context) ?? "default" - }); - } - - public bool ValidateAdditionalData(HttpContextBase context, string additionalData) - { - if (!additionalData.DetectIsJson()) - return false; //must be json - - AdditionalData json; - try - { - json = JsonConvert.DeserializeObject(additionalData); - } - catch - { - return false; //couldn't parse - } - - if (json.Stamp == default) return false; - - //if there was a wrapped provider, validate it, else validate the static value - var validateWrapped = _defaultProvider?.ValidateAdditionalData(context, json.WrappedValue) ?? json.WrappedValue == "default"; - if (!validateWrapped) - return false; - - var ufprtRequest = context.Request["ufprt"]?.ToString(); - - //if the custom BeginUmbracoForms route value is not there, then it's nothing more to validate - if (ufprtRequest.IsNullOrWhiteSpace() && json.Ufprt.IsNullOrWhiteSpace()) - return true; - - //if one or the other is null then something is wrong - if (!ufprtRequest.IsNullOrWhiteSpace() && json.Ufprt.IsNullOrWhiteSpace()) return false; - if (ufprtRequest.IsNullOrWhiteSpace() && !json.Ufprt.IsNullOrWhiteSpace()) return false; - - if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(json.Ufprt, out var additionalDataParts)) - return false; - - if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(ufprtRequest, out var requestParts)) - return false; - - //ensure they all match - return additionalDataParts.Count == requestParts.Count - && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Controller] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Controller] - && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Action] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Action] - && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Area]; - } - - internal class AdditionalData - { - public string Ufprt { get; set; } - public long Stamp { get; set; } - public string WrappedValue { get; set; } - } - - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 167c99b9b9..2fb54a2c80 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -307,7 +307,8 @@ - + + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index ff6fcff164..46b67a60c7 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -190,8 +190,6 @@ namespace Umbraco.Web base.Complete(afterComplete); - AntiForgeryConfig.AdditionalDataProvider = new UmbracoAntiForgeryAdditionalDataProvider(AntiForgeryConfig.AdditionalDataProvider); - //Now, startup all of our legacy startup handler ApplicationEventsResolver.Current.InstantiateLegacyStartupHandlers(); From b675f6252c1bb471e5a76c01329844905c379e4a Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 16 Jul 2019 23:07:31 +1000 Subject: [PATCH 159/776] removes files (should have been part of cherry pick?), fixes logic --- ...oAntiForgeryAdditionalDataProviderTests.cs | 157 ------------------ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - ...ValidateUmbracoFormRouteStringAttribute.cs | 6 +- src/Umbraco.Web/Runtime/WebFinalComponent.cs | 2 - ...mbracoAntiForgeryAdditionalDataProvider.cs | 92 ---------- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 6 files changed, 3 insertions(+), 256 deletions(-) delete mode 100644 src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs delete mode 100644 src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs diff --git a/src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs b/src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs deleted file mode 100644 index c81c108e0d..0000000000 --- a/src/Umbraco.Tests/Security/UmbracoAntiForgeryAdditionalDataProviderTests.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System.Collections.Specialized; -using System.Web; -using System.Web.Helpers; -using Moq; -using Newtonsoft.Json; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web.Mvc; -using Umbraco.Web.Security; - -namespace Umbraco.Tests.Security -{ - [TestFixture] - public class UmbracoAntiForgeryAdditionalDataProviderTests - { - [Test] - public void Test_Wrapped_Non_BeginUmbracoForm() - { - var wrapped = Mock.Of(x => x.GetAdditionalData(It.IsAny()) == "custom"); - var provider = new UmbracoAntiForgeryAdditionalDataProvider(wrapped); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var data = provider.GetAdditionalData(httpContextFactory.HttpContext); - - Assert.IsTrue(data.DetectIsJson()); - var json = JsonConvert.DeserializeObject(data); - Assert.AreEqual(null, json.Ufprt); - Assert.IsTrue(json.Stamp != default); - Assert.AreEqual("custom", json.WrappedValue); - } - - [Test] - public void Null_Wrapped_Non_BeginUmbracoForm() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var data = provider.GetAdditionalData(httpContextFactory.HttpContext); - - Assert.IsTrue(data.DetectIsJson()); - var json = JsonConvert.DeserializeObject(data); - Assert.AreEqual(null, json.Ufprt); - Assert.IsTrue(json.Stamp != default); - Assert.AreEqual("default", json.WrappedValue); - } - - [Test] - public void Validate_Non_Json() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "hello"); - - Assert.IsFalse(isValid); - } - - [Test] - public void Validate_Invalid_Json() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '0'}"); - Assert.IsFalse(isValid); - - isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': ''}"); - Assert.IsFalse(isValid); - - isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'hello': 'world'}"); - Assert.IsFalse(isValid); - - } - - [Test] - public void Validate_No_Request_Ufprt() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - //there is a ufprt in the additional data, but not in the request - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': 'ASBVDFDFDFDF'}"); - Assert.IsFalse(isValid); - } - - [Test] - public void Validate_No_AdditionalData_Ufprt() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var requestMock = Mock.Get(httpContextFactory.HttpContext.Request); - requestMock.SetupGet(x => x["ufprt"]).Returns("ABCDEFG"); - - //there is a ufprt in the additional data, but not in the request - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': ''}"); - Assert.IsFalse(isValid); - } - - [Test] - public void Validate_No_AdditionalData_Or_Request_Ufprt() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - - //there is a ufprt in the additional data, but not in the request - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': ''}"); - Assert.IsTrue(isValid); - } - - [Test] - public void Validate_Request_And_AdditionalData_Ufprt() - { - var provider = new UmbracoAntiForgeryAdditionalDataProvider(null); - - var routeParams1 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; - var routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var requestMock = Mock.Get(httpContextFactory.HttpContext.Request); - requestMock.SetupGet(x => x["ufprt"]).Returns(routeParams1.EncryptWithMachineKey()); - - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); - Assert.IsTrue(isValid); - - routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Invalid")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; - isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); - Assert.IsFalse(isValid); - } - - [Test] - public void Validate_Wrapped_Request_And_AdditionalData_Ufprt() - { - var wrapped = Mock.Of(x => x.ValidateAdditionalData(It.IsAny(), "custom") == true); - var provider = new UmbracoAntiForgeryAdditionalDataProvider(wrapped); - - var routeParams1 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; - var routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Test")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; - - var httpContextFactory = new FakeHttpContextFactory("/hello/world"); - var requestMock = Mock.Get(httpContextFactory.HttpContext.Request); - requestMock.SetupGet(x => x["ufprt"]).Returns(routeParams1.EncryptWithMachineKey()); - - var isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); - Assert.IsFalse(isValid); - - isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'custom', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); - Assert.IsTrue(isValid); - - routeParams2 = $"{RenderRouteHandler.ReservedAdditionalKeys.Controller}={HttpUtility.UrlEncode("Invalid")}&{RenderRouteHandler.ReservedAdditionalKeys.Action}={HttpUtility.UrlEncode("Index")}&{RenderRouteHandler.ReservedAdditionalKeys.Area}=Umbraco"; - isValid = provider.ValidateAdditionalData(httpContextFactory.HttpContext, "{'Stamp': '636970328040070330', 'WrappedValue': 'default', 'Ufprt': '" + routeParams2.EncryptWithMachineKey() + "'}"); - Assert.IsFalse(isValid); - } - } -} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index cc569d6989..4f4a83dc26 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -147,7 +147,6 @@ - diff --git a/src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs b/src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs index a57ce7e7f7..f3d0cc0e27 100644 --- a/src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs +++ b/src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs @@ -41,9 +41,9 @@ namespace Umbraco.Web.Mvc throw new HttpUmbracoFormRouteStringException("The required Umbraco request data is invalid."); } - if (additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Controller] != filterContext.ActionDescriptor.ControllerDescriptor.ControllerName || - additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Action] != filterContext.ActionDescriptor.ActionName || - additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area].NullOrWhiteSpaceAsNull() != filterContext.RouteData.DataTokens["area"]?.ToString().NullOrWhiteSpaceAsNull()) + if (!additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Controller].InvariantEquals(filterContext.ActionDescriptor.ControllerDescriptor.ControllerName) || + !additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Action].InvariantEquals(filterContext.ActionDescriptor.ActionName) || + (!additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area].IsNullOrWhiteSpace() && !additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area].InvariantEquals(filterContext.RouteData.DataTokens["area"]?.ToString()))) { throw new HttpUmbracoFormRouteStringException("The required Umbraco request data is invalid."); } diff --git a/src/Umbraco.Web/Runtime/WebFinalComponent.cs b/src/Umbraco.Web/Runtime/WebFinalComponent.cs index ba606e8d5e..6177d9b868 100644 --- a/src/Umbraco.Web/Runtime/WebFinalComponent.cs +++ b/src/Umbraco.Web/Runtime/WebFinalComponent.cs @@ -36,8 +36,6 @@ namespace Umbraco.Web.Runtime // ensure WebAPI is initialized, after everything GlobalConfiguration.Configuration.EnsureInitialized(); - - AntiForgeryConfig.AdditionalDataProvider = new UmbracoAntiForgeryAdditionalDataProvider(AntiForgeryConfig.AdditionalDataProvider); } public void Terminate() diff --git a/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs b/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs deleted file mode 100644 index c6ad4c6901..0000000000 --- a/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using Umbraco.Web.Mvc; -using Umbraco.Core; -using System.Web.Helpers; -using System.Web; -using Newtonsoft.Json; -using Umbraco.Web.Composing; - -namespace Umbraco.Web.Security -{ - /// - /// A custom to create a unique antiforgery token/validator per form created with BeginUmbracoForm - /// - public class UmbracoAntiForgeryAdditionalDataProvider : IAntiForgeryAdditionalDataProvider - { - private readonly IAntiForgeryAdditionalDataProvider _defaultProvider; - - /// - /// Constructor, allows wrapping a default provider - /// - /// - public UmbracoAntiForgeryAdditionalDataProvider(IAntiForgeryAdditionalDataProvider defaultProvider) - { - _defaultProvider = defaultProvider; - } - - public string GetAdditionalData(HttpContextBase context) - { - return JsonConvert.SerializeObject(new AdditionalData - { - Stamp = DateTime.UtcNow.Ticks, - //this value will be here if this is a BeginUmbracoForms form - Ufprt = context.Items["ufprt"]?.ToString(), - //if there was a wrapped provider, add it's value to the json, else just a static value - WrappedValue = _defaultProvider?.GetAdditionalData(context) ?? "default" - }); - } - - public bool ValidateAdditionalData(HttpContextBase context, string additionalData) - { - if (!additionalData.DetectIsJson()) - return false; //must be json - - AdditionalData json; - try - { - json = JsonConvert.DeserializeObject(additionalData); - } - catch - { - return false; //couldn't parse - } - - if (json.Stamp == default) return false; - - //if there was a wrapped provider, validate it, else validate the static value - var validateWrapped = _defaultProvider?.ValidateAdditionalData(context, json.WrappedValue) ?? json.WrappedValue == "default"; - if (!validateWrapped) - return false; - - var ufprtRequest = context.Request["ufprt"]?.ToString(); - - //if the custom BeginUmbracoForms route value is not there, then it's nothing more to validate - if (ufprtRequest.IsNullOrWhiteSpace() && json.Ufprt.IsNullOrWhiteSpace()) - return true; - - //if one or the other is null then something is wrong - if (!ufprtRequest.IsNullOrWhiteSpace() && json.Ufprt.IsNullOrWhiteSpace()) return false; - if (ufprtRequest.IsNullOrWhiteSpace() && !json.Ufprt.IsNullOrWhiteSpace()) return false; - - if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(json.Ufprt, out var additionalDataParts)) - return false; - - if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(ufprtRequest, out var requestParts)) - return false; - - //ensure they all match - return additionalDataParts.Count == requestParts.Count - && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Controller] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Controller] - && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Action] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Action] - && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Area]; - } - - internal class AdditionalData - { - public string Ufprt { get; set; } - public long Stamp { get; set; } - public string WrappedValue { get; set; } - } - - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 39a05ddbc4..41a3393fa4 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -232,7 +232,6 @@ - From bfb69a34ef2353229d390b9129103e61f6b4c704 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 17 Jul 2019 21:15:18 +1000 Subject: [PATCH 160/776] re-adds back in the serialization overloads for the custom exception, re-adds detailed error messages, adds more documentation. Adds unit tests. --- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../Mvc/HtmlHelperExtensionMethodsTests.cs | 3 +- ...ateUmbracoFormRouteStringAttributeTests.cs | 37 +++++++++++++++++++ .../HttpUmbracoFormRouteStringException.cs | 18 +++++++++ ...ValidateUmbracoFormRouteStringAttribute.cs | 28 +++++++++----- src/Umbraco.Web/UmbracoHelper.cs | 2 +- 6 files changed, 78 insertions(+), 11 deletions(-) create mode 100644 src/Umbraco.Tests/Web/Mvc/ValidateUmbracoFormRouteStringAttributeTests.cs diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 72939c12d9..d45c556d37 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -398,6 +398,7 @@ + diff --git a/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs b/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs index e7e4f363c1..7be0717c7e 100644 --- a/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/HtmlHelperExtensionMethodsTests.cs @@ -4,7 +4,8 @@ using Umbraco.Web; namespace Umbraco.Tests.Web.Mvc { - [TestFixture] + + [TestFixture] public class HtmlHelperExtensionMethodsTests { [SetUp] diff --git a/src/Umbraco.Tests/Web/Mvc/ValidateUmbracoFormRouteStringAttributeTests.cs b/src/Umbraco.Tests/Web/Mvc/ValidateUmbracoFormRouteStringAttributeTests.cs new file mode 100644 index 0000000000..a942f846a0 --- /dev/null +++ b/src/Umbraco.Tests/Web/Mvc/ValidateUmbracoFormRouteStringAttributeTests.cs @@ -0,0 +1,37 @@ +using NUnit.Framework; +using Umbraco.Web; +using Umbraco.Web.Mvc; + +namespace Umbraco.Tests.Web.Mvc +{ + [TestFixture] + public class ValidateUmbracoFormRouteStringAttributeTests + { + [Test] + public void Validate_Route_String() + { + var attribute = new ValidateUmbracoFormRouteStringAttribute(); + + Assert.Throws(() => attribute.ValidateRouteString(null, null, null, null)); + + const string ControllerName = "Test"; + const string ControllerAction = "Index"; + const string Area = "MyArea"; + var validUfprt = UmbracoHelper.CreateEncryptedRouteString(ControllerName, ControllerAction, Area); + + var invalidUfprt = validUfprt + "z"; + Assert.Throws(() => attribute.ValidateRouteString(invalidUfprt, null, null, null)); + + Assert.Throws(() => attribute.ValidateRouteString(validUfprt, ControllerName, ControllerAction, "doesntMatch")); + Assert.Throws(() => attribute.ValidateRouteString(validUfprt, ControllerName, ControllerAction, null)); + Assert.Throws(() => attribute.ValidateRouteString(validUfprt, ControllerName, "doesntMatch", Area)); + Assert.Throws(() => attribute.ValidateRouteString(validUfprt, ControllerName, null, Area)); + Assert.Throws(() => attribute.ValidateRouteString(validUfprt, "doesntMatch", ControllerAction, Area)); + Assert.Throws(() => attribute.ValidateRouteString(validUfprt, null, ControllerAction, Area)); + + Assert.DoesNotThrow(() => attribute.ValidateRouteString(validUfprt, ControllerName, ControllerAction, Area)); + Assert.DoesNotThrow(() => attribute.ValidateRouteString(validUfprt, ControllerName.ToLowerInvariant(), ControllerAction.ToLowerInvariant(), Area.ToLowerInvariant())); + } + + } +} diff --git a/src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs b/src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs index d4734e5d24..b08fde081a 100644 --- a/src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs +++ b/src/Umbraco.Web/Mvc/HttpUmbracoFormRouteStringException.cs @@ -1,5 +1,6 @@ using System; using System.Net; +using System.Runtime.Serialization; using System.Web; namespace Umbraco.Web.Mvc @@ -11,6 +12,14 @@ namespace Umbraco.Web.Mvc [Serializable] public sealed class HttpUmbracoFormRouteStringException : HttpException { + /// Initializes a new instance of the class. + ///
+ /// The that holds the serialized object data about the exception being thrown. + /// The that holds the contextual information about the source or destination. + private HttpUmbracoFormRouteStringException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + /// /// Initializes a new instance of the class. /// @@ -19,5 +28,14 @@ namespace Umbraco.Web.Mvc : base(message) { } + /// + /// Initializes a new instance of the class. + /// + /// The error message displayed to the client when the exception is thrown. + /// The , if any, that threw the current exception. + public HttpUmbracoFormRouteStringException(string message, Exception innerException) + : base(message, innerException) + { } + } } diff --git a/src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs b/src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs index f3d0cc0e27..8d929197e1 100644 --- a/src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs +++ b/src/Umbraco.Web/Mvc/ValidateUmbracoFormRouteStringAttribute.cs @@ -1,15 +1,21 @@ using System; using System.Net; +using System.Net.Http; using System.Web.Mvc; using Umbraco.Core; namespace Umbraco.Web.Mvc { /// - /// Represents an attribute that is used to prevent an invalid Umbraco form request route string on a request. + /// Attribute used to check that the request contains a valid Umbraco form request string. /// /// /// + /// + /// Applying this attribute/filter to a or SurfaceController Action will ensure that the Action can only be executed + /// when it is routed to from within Umbraco, typically when rendering a form with BegingUmbracoForm. It will mean that the natural MVC route for this Action + /// will fail with a . + /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public sealed class ValidateUmbracoFormRouteStringAttribute : FilterAttribute, IAuthorizationFilter { @@ -26,27 +32,31 @@ namespace Umbraco.Web.Mvc public void OnAuthorization(AuthorizationContext filterContext) { if (filterContext == null) - { throw new ArgumentNullException(nameof(filterContext)); - } var ufprt = filterContext.HttpContext.Request["ufprt"]; + ValidateRouteString(ufprt, filterContext.ActionDescriptor?.ControllerDescriptor.ControllerName, filterContext.ActionDescriptor?.ActionName, filterContext.RouteData?.DataTokens["area"]?.ToString()); + } + + public void ValidateRouteString(string ufprt, string currentController, string currentAction, string currentArea) + { if (ufprt.IsNullOrWhiteSpace()) { - throw new HttpUmbracoFormRouteStringException("The required Umbraco request data is invalid."); + throw new HttpUmbracoFormRouteStringException("The required request field \"ufprt\" is not present."); } if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(ufprt, out var additionalDataParts)) { - throw new HttpUmbracoFormRouteStringException("The required Umbraco request data is invalid."); + throw new HttpUmbracoFormRouteStringException("The Umbraco form request route string could not be decrypted."); } - if (!additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Controller].InvariantEquals(filterContext.ActionDescriptor.ControllerDescriptor.ControllerName) || - !additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Action].InvariantEquals(filterContext.ActionDescriptor.ActionName) || - (!additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area].IsNullOrWhiteSpace() && !additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area].InvariantEquals(filterContext.RouteData.DataTokens["area"]?.ToString()))) + if (!additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Controller].InvariantEquals(currentController) || + !additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Action].InvariantEquals(currentAction) || + (!additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area].IsNullOrWhiteSpace() && !additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area].InvariantEquals(currentArea))) { - throw new HttpUmbracoFormRouteStringException("The required Umbraco request data is invalid."); + throw new HttpUmbracoFormRouteStringException("The provided Umbraco form request route string was meant for a different controller and action."); } + } } } diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index c00d37f216..5320b6085a 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -1656,7 +1656,7 @@ namespace Umbraco.Web { decryptedString = ufprt.DecryptWithMachineKey(); } - catch (FormatException) + catch (Exception ex) when (ex is FormatException || ex is ArgumentException) { LogHelper.Warn("A value was detected in the ufprt parameter but Umbraco could not decrypt the string"); parts = null; From 14c4c4815d20de5d2cc69868d303ce58309bd7ba Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 17 Jul 2019 21:35:43 +1000 Subject: [PATCH 161/776] bumps version --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 0d6b3bcc6b..3a85915cf5 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -11,5 +11,5 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("7.15.0")] -[assembly: AssemblyInformationalVersion("7.15.0")] +[assembly: AssemblyFileVersion("7.15.1")] +[assembly: AssemblyInformationalVersion("7.15.1")] diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 6bb5dbf78e..f23078dbd0 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("7.15.0"); + private static readonly Version Version = new Version("7.15.1"); /// /// Gets the current version of Umbraco. diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index a0624a2dd7..6884f21bcf 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -1027,9 +1027,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\" True True - 7150 + 7151 / - http://localhost:7150 + http://localhost:7151 False False From 26d4d056be662a3e91e33740c9813b1604c5be96 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 17 Jul 2019 22:28:06 +1000 Subject: [PATCH 162/776] bumps version --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 9ed398d52f..841e054aee 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.1.0")] -[assembly: AssemblyInformationalVersion("8.1.0")] +[assembly: AssemblyFileVersion("8.1.1")] +[assembly: AssemblyInformationalVersion("8.1.1")] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index b27f6aa335..e58f44e1ae 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -345,9 +345,9 @@ False True - 8100 + 8110 / - http://localhost:8100 + http://localhost:8110 False False From 79159b684dabcbe8b54597691380bb17982285cf Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 17 Jul 2019 14:30:02 +0200 Subject: [PATCH 163/776] https://github.com/umbraco/Umbraco-CMS/issues/5902 - Fixed issue with migration of Umbraco.Date if it was never saved in v7, the default format was added the time-part. - Fixed issue with warnings of migrations of build in types, that was renamed between v7 and v8. These are handled correct. - Added constants for some of the legacy property editor aliases. --- src/Umbraco.Core/Constants-PropertyEditors.cs | 17 ++++++++++ .../ConvertRelatedLinksToMultiUrlPicker.cs | 4 +-- .../Upgrade/V_8_0_0/DataTypeMigration.cs | 34 +++++++++++++++---- .../ContentPickerPreValueMigrator.cs | 2 +- .../DataTypes/MediaPickerPreValueMigrator.cs | 6 ++-- .../MergeDateAndDateTimePropertyEditor.cs | 10 +++++- .../V_8_0_0/PropertyEditorsMigration.cs | 14 ++++---- 7 files changed, 66 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index 0c2e246721..b48286f197 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -14,6 +14,23 @@ namespace Umbraco.Core /// public const string InternalGenericPropertiesPrefix = "_umb_"; + public static class Legacy + { + public static class Aliases + { + public const string Textbox = "Umbraco.Textbox"; + public const string Date = "Umbraco.Date"; + public const string ContentPicker2 = "Umbraco.ContentPicker2"; + public const string MediaPicker2 = "Umbraco.MediaPicker2"; + public const string MemberPicker2 = "Umbraco.MemberPicker2"; + public const string MultiNodeTreePicker2 = "Umbraco.MultiNodeTreePicker2"; + public const string TextboxMultiple = "Umbraco.TextboxMultiple"; + public const string RelatedLinks2 = "Umbraco.RelatedLinks2"; + public const string RelatedLinks = "Umbraco.RelatedLinks"; + + } + } + /// /// Defines Umbraco built-in property editor aliases. /// diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs index 44f7affe8e..4802750f4b 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ConvertRelatedLinksToMultiUrlPicker.cs @@ -19,8 +19,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 var sqlDataTypes = Sql() .Select() .From() - .Where(x => x.EditorAlias == "Umbraco.RelatedLinks" - || x.EditorAlias == "Umbraco.RelatedLinks2"); + .Where(x => x.EditorAlias == Constants.PropertyEditors.Legacy.Aliases.RelatedLinks + || x.EditorAlias == Constants.PropertyEditors.Legacy.Aliases.RelatedLinks2); var dataTypes = Database.Fetch(sqlDataTypes); var dataTypeIds = dataTypes.Select(x => x.NodeId).ToList(); diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs index 438b02385b..7b2daa99ef 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using Umbraco.Core.Composing; @@ -18,6 +19,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 private readonly PropertyEditorCollection _propertyEditors; private readonly ILogger _logger; + private static readonly ISet LegacyAliases = new HashSet() + { + Constants.PropertyEditors.Legacy.Aliases.Date, + Constants.PropertyEditors.Legacy.Aliases.Textbox, + Constants.PropertyEditors.Legacy.Aliases.ContentPicker2, + Constants.PropertyEditors.Legacy.Aliases.MediaPicker2, + Constants.PropertyEditors.Legacy.Aliases.MemberPicker2, + Constants.PropertyEditors.Legacy.Aliases.RelatedLinks2, + Constants.PropertyEditors.Legacy.Aliases.TextboxMultiple, + Constants.PropertyEditors.Legacy.Aliases.MultiNodeTreePicker2, + }; + public DataTypeMigration(IMigrationContext context, PreValueMigratorCollection preValueMigrators, PropertyEditorCollection propertyEditors, ILogger logger) : base(context) { @@ -70,16 +83,23 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 var newAlias = migrator.GetNewAlias(dataType.EditorAlias); if (newAlias == null) { - _logger.Warn("Skipping validation of configuration for data type {NodeId} : {EditorAlias}." - + " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.", - dataType.NodeId, dataType.EditorAlias); + if (!LegacyAliases.Contains(dataType.EditorAlias)) + { + _logger.Warn( + "Skipping validation of configuration for data type {NodeId} : {EditorAlias}." + + " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.", + dataType.NodeId, dataType.EditorAlias); + } } else if (!_propertyEditors.TryGet(newAlias, out var propertyEditor)) { - _logger.Warn("Skipping validation of configuration for data type {NodeId} : {NewEditorAlias} (was: {EditorAlias})" - + " because no property editor with that alias was found." - + " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.", - dataType.NodeId, newAlias, dataType.EditorAlias); + if (!LegacyAliases.Contains(newAlias)) + { + _logger.Warn("Skipping validation of configuration for data type {NodeId} : {NewEditorAlias} (was: {EditorAlias})" + + " because no property editor with that alias was found." + + " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.", + dataType.NodeId, newAlias, dataType.EditorAlias); + } } else { diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ContentPickerPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ContentPickerPreValueMigrator.cs index 2e341ad091..f445742aa9 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ContentPickerPreValueMigrator.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ContentPickerPreValueMigrator.cs @@ -3,7 +3,7 @@ class ContentPickerPreValueMigrator : DefaultPreValueMigrator { public override bool CanMigrate(string editorAlias) - => editorAlias == "Umbraco.ContentPicker2"; + => editorAlias == Constants.PropertyEditors.Legacy.Aliases.ContentPicker2; public override string GetNewAlias(string editorAlias) => null; diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/MediaPickerPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/MediaPickerPreValueMigrator.cs index a46b1eefb7..922d886595 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/MediaPickerPreValueMigrator.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/MediaPickerPreValueMigrator.cs @@ -6,15 +6,15 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes { private readonly string[] _editors = { - "Umbraco.MediaPicker2", - "Umbraco.MediaPicker" + Constants.PropertyEditors.Legacy.Aliases.MediaPicker2, + Constants.PropertyEditors.Aliases.MediaPicker }; public override bool CanMigrate(string editorAlias) => _editors.Contains(editorAlias); public override string GetNewAlias(string editorAlias) - => "Umbraco.MediaPicker"; + => Constants.PropertyEditors.Aliases.MediaPicker; // you wish - but MediaPickerConfiguration lives in Umbraco.Web /* diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs index a434b9f1c1..0d451e8460 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { - var dataTypes = GetDataTypes("Umbraco.Date"); + var dataTypes = GetDataTypes(Constants.PropertyEditors.Legacy.Aliases.Date); foreach (var dataType in dataTypes) { @@ -25,6 +25,14 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { config = (DateTimeConfiguration) new CustomDateTimeConfigurationEditor().FromDatabase( dataType.Configuration); + + // If the Umbraco.Date type is the default from V7 and it has never been updated, then the + // configuration is empty, and the format stuff is handled by in JS by moment.js. - We can't do that + // after the migration, so we force the format to the default from V7. + if (string.IsNullOrEmpty(dataType.Configuration)) + { + config.Format = "YYYY-MM-DD"; + }; } catch (Exception ex) { diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs index dac62abb75..89a8f010ec 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigration.cs @@ -12,12 +12,12 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { - RenameDataType(Constants.PropertyEditors.Aliases.ContentPicker + "2", Constants.PropertyEditors.Aliases.ContentPicker); - RenameDataType(Constants.PropertyEditors.Aliases.MediaPicker + "2", Constants.PropertyEditors.Aliases.MediaPicker); - RenameDataType(Constants.PropertyEditors.Aliases.MemberPicker + "2", Constants.PropertyEditors.Aliases.MemberPicker); - RenameDataType(Constants.PropertyEditors.Aliases.MultiNodeTreePicker + "2", Constants.PropertyEditors.Aliases.MultiNodeTreePicker); - RenameDataType("Umbraco.TextboxMultiple", Constants.PropertyEditors.Aliases.TextArea, false); - RenameDataType("Umbraco.Textbox", Constants.PropertyEditors.Aliases.TextBox, false); + RenameDataType(Constants.PropertyEditors.Legacy.Aliases.ContentPicker2, Constants.PropertyEditors.Aliases.ContentPicker); + RenameDataType(Constants.PropertyEditors.Legacy.Aliases.MediaPicker2, Constants.PropertyEditors.Aliases.MediaPicker); + RenameDataType(Constants.PropertyEditors.Legacy.Aliases.MemberPicker2, Constants.PropertyEditors.Aliases.MemberPicker); + RenameDataType(Constants.PropertyEditors.Legacy.Aliases.MultiNodeTreePicker2, Constants.PropertyEditors.Aliases.MultiNodeTreePicker); + RenameDataType(Constants.PropertyEditors.Legacy.Aliases.TextboxMultiple, Constants.PropertyEditors.Aliases.TextArea, false); + RenameDataType(Constants.PropertyEditors.Legacy.Aliases.Textbox, Constants.PropertyEditors.Aliases.TextBox, false); } private void RenameDataType(string fromAlias, string toAlias, bool checkCollision = true) @@ -43,7 +43,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 $"Property Editors. Before upgrading to v8, any Data Types using property editors that are named with the prefix '(Obsolete)' must be migrated " + $"to the non-obsolete v7 property editors of the same type."); } - + } Database.Execute(Sql() From 36030904212d321af75f96bfc894742e86629ec3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 17 Jul 2019 22:51:14 +1000 Subject: [PATCH 164/776] Fixes race condition in PublishedSnapshotService and more... --- src/Umbraco.Core/AsyncLock.cs | 47 ++++++++-------- src/Umbraco.Core/MainDom.cs | 7 ++- src/Umbraco.Core/Runtime/CoreRuntime.cs | 6 +- src/Umbraco.Core/RuntimeState.cs | 5 ++ src/Umbraco.Core/WaitHandleExtensions.cs | 2 + .../PublishedContent/NuCacheChildrenTests.cs | 2 +- .../PublishedContent/NuCacheTests.cs | 2 +- .../Scoping/ScopedNuCacheTests.cs | 2 +- .../ContentTypeServiceVariantsTests.cs | 2 +- .../PublishedCache/NuCache/NuCacheComposer.cs | 2 +- .../NuCache/PublishedSnapshotService.cs | 56 +++++++++---------- .../PublishedSnapshotServiceOptions.cs | 28 ++++++++++ src/Umbraco.Web/Scheduling/KeepAlive.cs | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 + 14 files changed, 101 insertions(+), 63 deletions(-) create mode 100644 src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotServiceOptions.cs diff --git a/src/Umbraco.Core/AsyncLock.cs b/src/Umbraco.Core/AsyncLock.cs index 158b132f26..6dd866705e 100644 --- a/src/Umbraco.Core/AsyncLock.cs +++ b/src/Umbraco.Core/AsyncLock.cs @@ -67,31 +67,34 @@ namespace Umbraco.Core : new NamedSemaphoreReleaser(_semaphore2); } - public Task LockAsync() - { - var wait = _semaphore != null - ? _semaphore.WaitAsync() - : _semaphore2.WaitOneAsync(); + //NOTE: We don't use the "Async" part of this lock at all + //TODO: Remove this and rename this class something like SystemWideLock, then we can re-instate this logic if we ever need an Async lock again - return wait.IsCompleted - ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named - : wait.ContinueWith((_, state) => (((AsyncLock) state).CreateReleaser()), - this, CancellationToken.None, - TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); - } + //public Task LockAsync() + //{ + // var wait = _semaphore != null + // ? _semaphore.WaitAsync() + // : _semaphore2.WaitOneAsync(); - public Task LockAsync(int millisecondsTimeout) - { - var wait = _semaphore != null - ? _semaphore.WaitAsync(millisecondsTimeout) - : _semaphore2.WaitOneAsync(millisecondsTimeout); + // return wait.IsCompleted + // ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named + // : wait.ContinueWith((_, state) => (((AsyncLock) state).CreateReleaser()), + // this, CancellationToken.None, + // TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + //} - return wait.IsCompleted - ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named - : wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()), - this, CancellationToken.None, - TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); - } + //public Task LockAsync(int millisecondsTimeout) + //{ + // var wait = _semaphore != null + // ? _semaphore.WaitAsync(millisecondsTimeout) + // : _semaphore2.WaitOneAsync(millisecondsTimeout); + + // return wait.IsCompleted + // ? _releaserTask ?? Task.FromResult(CreateReleaser()) // anonymous vs named + // : wait.ContinueWith((_, state) => (((AsyncLock)state).CreateReleaser()), + // this, CancellationToken.None, + // TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + //} public IDisposable Lock() { diff --git a/src/Umbraco.Core/MainDom.cs b/src/Umbraco.Core/MainDom.cs index ca875c2167..d1012fb669 100644 --- a/src/Umbraco.Core/MainDom.cs +++ b/src/Umbraco.Core/MainDom.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Web.Hosting; @@ -113,7 +114,7 @@ namespace Umbraco.Core lock (_locko) { - _logger.Debug("Signaled {Signaled} ({SignalSource})", _signaled ? "(again)" : string.Empty, source); + _logger.Debug("Signaled ({Signaled}) ({SignalSource})", _signaled ? "again" : "first", source); if (_signaled) return; if (_isMainDom == false) return; // probably not needed _signaled = true; @@ -171,6 +172,7 @@ namespace Umbraco.Core // if more than 1 instance reach that point, one will get the lock // and the other one will timeout, which is accepted + //TODO: This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset? _asyncLocker = _asyncLock.Lock(LockTimeoutMilliseconds); _isMainDom = true; @@ -181,6 +183,9 @@ namespace Umbraco.Core // which is accepted _signal.Reset(); + + //WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread + _signal.WaitOneAsync() .ContinueWith(_ => OnSignal("signal")); diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index f9a41b4f66..5b069641c4 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -139,7 +139,7 @@ namespace Umbraco.Core.Runtime // there should be none, really - this is here "just in case" Compose(composition); - // acquire the main domain + // acquire the main domain - if this fails then anything that should be registered with MainDom will not operate AcquireMainDom(mainDom); // determine our runtime level @@ -218,13 +218,13 @@ namespace Umbraco.Core.Runtime IOHelper.SetRootDirectory(path); } - private void AcquireMainDom(MainDom mainDom) + private bool AcquireMainDom(MainDom mainDom) { using (var timer = ProfilingLogger.DebugDuration("Acquiring MainDom.", "Acquired.")) { try { - mainDom.Acquire(); + return mainDom.Acquire(); } catch { diff --git a/src/Umbraco.Core/RuntimeState.cs b/src/Umbraco.Core/RuntimeState.cs index 6fb8a04c0d..5d34fe70a1 100644 --- a/src/Umbraco.Core/RuntimeState.cs +++ b/src/Umbraco.Core/RuntimeState.cs @@ -97,6 +97,11 @@ namespace Umbraco.Core /// internal void EnsureApplicationUrl(HttpRequestBase request = null) { + //Fixme: This causes problems with site swap on azure because azure pre-warms a site by calling into `localhost` and when it does that + // it changes the URL to `localhost:80` which actually doesn't work for pinging itself, it only works internally in Azure. The ironic part + // about this is that this is here specifically for the slot swap scenario https://issues.umbraco.org/issue/U4-10626 + + // see U4-10626 - in some cases we want to reset the application url // (this is a simplified version of what was in 7.x) // note: should this be optional? is it expensive? diff --git a/src/Umbraco.Core/WaitHandleExtensions.cs b/src/Umbraco.Core/WaitHandleExtensions.cs index 0d840a2496..7a9294c113 100644 --- a/src/Umbraco.Core/WaitHandleExtensions.cs +++ b/src/Umbraco.Core/WaitHandleExtensions.cs @@ -23,6 +23,8 @@ namespace Umbraco.Core handle, (state, timedOut) => { + //TODO: We aren't checking if this is timed out + tcs.SetResult(null); // we take a lock here to make sure the outer method has completed setting the local variable callbackHandle. diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index f3a520ead1..55304d257b 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -124,7 +124,7 @@ namespace Umbraco.Tests.PublishedContent _source = new TestDataSource(kits); // at last, create the complete NuCache snapshot service! - var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true }; + var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; _snapshotService = new PublishedSnapshotService(options, null, runtime, diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index b66404c954..399b0c1342 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -169,7 +169,7 @@ namespace Umbraco.Tests.PublishedContent _variationAccesor = new TestVariationContextAccessor(); // at last, create the complete NuCache snapshot service! - var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true }; + var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; _snapshotService = new PublishedSnapshotService(options, null, runtime, diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index 397a22fc62..c974fed99e 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -72,7 +72,7 @@ namespace Umbraco.Tests.Scoping protected override IPublishedSnapshotService CreatePublishedSnapshotService() { - var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true }; + var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; var publishedSnapshotAccessor = new UmbracoContextPublishedSnapshotAccessor(Umbraco.Web.Composing.Current.UmbracoContextAccessor); var runtimeStateMock = new Mock(); runtimeStateMock.Setup(x => x.Level).Returns(() => RuntimeLevel.Run); diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 3121988bfe..18ea95cd98 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -42,7 +42,7 @@ namespace Umbraco.Tests.Services protected override IPublishedSnapshotService CreatePublishedSnapshotService() { - var options = new PublishedSnapshotService.Options { IgnoreLocalDb = true }; + var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; var publishedSnapshotAccessor = new UmbracoContextPublishedSnapshotAccessor(Umbraco.Web.Composing.Current.UmbracoContextAccessor); var runtimeStateMock = new Mock(); runtimeStateMock.Setup(x => x.Level).Returns(() => RuntimeLevel.Run); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComposer.cs b/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComposer.cs index 72fa39e7e4..f748fd555c 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComposer.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/NuCacheComposer.cs @@ -15,7 +15,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // register the NuCache published snapshot service // must register default options, required in the service ctor - composition.Register(factory => new PublishedSnapshotService.Options()); + composition.Register(factory => new PublishedSnapshotServiceOptions()); composition.SetPublishedSnapshotService(); // add the NuCache health check (hidden from type finder) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index dad9811af8..5e7c0542db 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -30,7 +30,8 @@ using File = System.IO.File; namespace Umbraco.Web.PublishedCache.NuCache { - class PublishedSnapshotService : PublishedSnapshotServiceBase + + internal class PublishedSnapshotService : PublishedSnapshotServiceBase { private readonly ServiceContext _serviceContext; private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; @@ -56,7 +57,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private BPlusTree _localContentDb; private BPlusTree _localMediaDb; - private readonly bool _localDbExists; + private bool _localDbExists; // define constant - determines whether to use cache when previewing // to store eg routes, property converted values, anything - caching @@ -68,7 +69,7 @@ namespace Umbraco.Web.PublishedCache.NuCache //private static int _singletonCheck; - public PublishedSnapshotService(Options options, IMainDom mainDom, IRuntimeState runtime, + public PublishedSnapshotService(PublishedSnapshotServiceOptions options, IMainDom mainDom, IRuntimeState runtime, ServiceContext serviceContext, IPublishedContentTypeFactory publishedContentTypeFactory, IdkMap idkMap, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, ILogger logger, IScopeProvider scopeProvider, IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, @@ -115,30 +116,36 @@ namespace Umbraco.Web.PublishedCache.NuCache if (options.IgnoreLocalDb == false) { var registered = mainDom.Register( - null, () => { + //"install" phase of MainDom + //this is inside of a lock in MainDom so this is guaranteed to run if MainDom was acquired and guaranteed + //to not run if MainDom wasn't acquired. + //If MainDom was not acquired, then _localContentDb and _localMediaDb will remain null which means this appdomain + //will load in published content via the DB and in that case this appdomain will probably not exist long enough to + //serve more than a page of content. + + var path = GetLocalFilesPath(); + var localContentDbPath = Path.Combine(path, "NuCache.Content.db"); + var localMediaDbPath = Path.Combine(path, "NuCache.Media.db"); + _localDbExists = File.Exists(localContentDbPath) && File.Exists(localMediaDbPath); + // if both local databases exist then GetTree will open them, else new databases will be created + _localContentDb = BTree.GetTree(localContentDbPath, _localDbExists); + _localMediaDb = BTree.GetTree(localMediaDbPath, _localDbExists); + }, + () => + { + //"release" phase of MainDom + lock (_storesLock) { - _contentStore.ReleaseLocalDb(); + _contentStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned _localContentDb = null; - _mediaStore.ReleaseLocalDb(); + _mediaStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned _localMediaDb = null; } }); - if (registered) - { - var path = GetLocalFilesPath(); - var localContentDbPath = Path.Combine(path, "NuCache.Content.db"); - var localMediaDbPath = Path.Combine(path, "NuCache.Media.db"); - _localDbExists = System.IO.File.Exists(localContentDbPath) && System.IO.File.Exists(localMediaDbPath); - - // if both local databases exist then GetTree will open them, else new databases will be created - _localContentDb = BTree.GetTree(localContentDbPath, _localDbExists); - _localMediaDb = BTree.GetTree(localMediaDbPath, _localDbExists); - } - // stores are created with a db so they can write to it, but they do not read from it, // stores need to be populated, happens in OnResolutionFrozen which uses _localDbExists to // figure out whether it can read the databases or it should populate them from sql @@ -251,19 +258,6 @@ namespace Umbraco.Web.PublishedCache.NuCache base.Dispose(); } - public class Options - { - // disabled: prevents the published snapshot from updating and exposing changes - // or even creating a new published snapshot to see changes, uses old cache = bad - // - //// indicates that the snapshot cache should reuse the application request cache - //// otherwise a new cache object would be created for the snapshot specifically, - //// which is the default - web boot manager uses this to optimize facades - //public bool PublishedSnapshotCacheIsApplicationRequestCache; - - public bool IgnoreLocalDb; - } - #endregion #region Local files diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotServiceOptions.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotServiceOptions.cs new file mode 100644 index 0000000000..ef9d83a802 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotServiceOptions.cs @@ -0,0 +1,28 @@ +namespace Umbraco.Web.PublishedCache.NuCache +{ + /// + /// Options class for configuring the + /// + public class PublishedSnapshotServiceOptions + { + // disabled: prevents the published snapshot from updating and exposing changes + // or even creating a new published snapshot to see changes, uses old cache = bad + // + //// indicates that the snapshot cache should reuse the application request cache + //// otherwise a new cache object would be created for the snapshot specifically, + //// which is the default - web boot manager uses this to optimize facades + //public bool PublishedSnapshotCacheIsApplicationRequestCache; + + + /// + /// If true this disables the persisted local cache files for content and media + /// + /// + /// By default this is false which means umbraco will use locally persisted cache files for reading in all published content and media on application startup. + /// The reason for this is to improve startup times because the alternative to populating the published content and media on application startup is to read + /// these values from the database. In scenarios where sites are relatively small (below a few thousand nodes) reading the content/media from the database to populate + /// the in memory cache isn't that slow and is only marginally slower than reading from the locally persisted cache files. + /// + public bool IgnoreLocalDb { get; set; } + } +} diff --git a/src/Umbraco.Web/Scheduling/KeepAlive.cs b/src/Umbraco.Web/Scheduling/KeepAlive.cs index 6dd7572b87..c07430df04 100644 --- a/src/Umbraco.Web/Scheduling/KeepAlive.cs +++ b/src/Umbraco.Web/Scheduling/KeepAlive.cs @@ -64,7 +64,7 @@ namespace Umbraco.Web.Scheduling } catch (Exception ex) { - _logger.Error(ex, "Failed (at '{UmbracoAppUrl}').", umbracoAppUrl); + _logger.Error(ex, "Keep alive failed (at '{UmbracoAppUrl}').", umbracoAppUrl); } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 41a3393fa4..331b2ac7d0 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -224,6 +224,7 @@ + From f3bfc1ffb27618f129d1fdb2e05872e14b3a9f15 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 18 Jul 2019 16:18:11 +1000 Subject: [PATCH 165/776] Puts back UmbracoAntiForgeryAdditionalDataProvider for backwards compat reasons but it is not used --- ...mbracoAntiForgeryAdditionalDataProvider.cs | 91 +++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 2 files changed, 92 insertions(+) create mode 100644 src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs diff --git a/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs b/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs new file mode 100644 index 0000000000..12d7cce753 --- /dev/null +++ b/src/Umbraco.Web/Security/UmbracoAntiForgeryAdditionalDataProvider.cs @@ -0,0 +1,91 @@ +using System; +using Umbraco.Web.Mvc; +using Umbraco.Core; +using System.Web.Helpers; +using System.Web; +using Newtonsoft.Json; +using System.ComponentModel; + +namespace Umbraco.Web.Security +{ + [Obsolete("This is no longer used and will be removed from the codebase in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] + public class UmbracoAntiForgeryAdditionalDataProvider : IAntiForgeryAdditionalDataProvider + { + private readonly IAntiForgeryAdditionalDataProvider _defaultProvider; + + /// + /// Constructor, allows wrapping a default provider + /// + /// + public UmbracoAntiForgeryAdditionalDataProvider(IAntiForgeryAdditionalDataProvider defaultProvider) + { + _defaultProvider = defaultProvider; + } + + public string GetAdditionalData(HttpContextBase context) + { + return JsonConvert.SerializeObject(new AdditionalData + { + Stamp = DateTime.UtcNow.Ticks, + //this value will be here if this is a BeginUmbracoForms form + Ufprt = context.Items["ufprt"]?.ToString(), + //if there was a wrapped provider, add it's value to the json, else just a static value + WrappedValue = _defaultProvider?.GetAdditionalData(context) ?? "default" + }); + } + + public bool ValidateAdditionalData(HttpContextBase context, string additionalData) + { + if (!additionalData.DetectIsJson()) + return false; //must be json + + AdditionalData json; + try + { + json = JsonConvert.DeserializeObject(additionalData); + } + catch + { + return false; //couldn't parse + } + + if (json.Stamp == default) return false; + + //if there was a wrapped provider, validate it, else validate the static value + var validateWrapped = _defaultProvider?.ValidateAdditionalData(context, json.WrappedValue) ?? json.WrappedValue == "default"; + if (!validateWrapped) + return false; + + var ufprtRequest = context.Request["ufprt"]?.ToString(); + + //if the custom BeginUmbracoForms route value is not there, then it's nothing more to validate + if (ufprtRequest.IsNullOrWhiteSpace() && json.Ufprt.IsNullOrWhiteSpace()) + return true; + + //if one or the other is null then something is wrong + if (!ufprtRequest.IsNullOrWhiteSpace() && json.Ufprt.IsNullOrWhiteSpace()) return false; + if (ufprtRequest.IsNullOrWhiteSpace() && !json.Ufprt.IsNullOrWhiteSpace()) return false; + + if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(json.Ufprt, out var additionalDataParts)) + return false; + + if (!UmbracoHelper.DecryptAndValidateEncryptedRouteString(ufprtRequest, out var requestParts)) + return false; + + //ensure they all match + return additionalDataParts.Count == requestParts.Count + && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Controller] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Controller] + && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Action] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Action] + && additionalDataParts[RenderRouteHandler.ReservedAdditionalKeys.Area] == requestParts[RenderRouteHandler.ReservedAdditionalKeys.Area]; + } + + internal class AdditionalData + { + public string Ufprt { get; set; } + public long Stamp { get; set; } + public string WrappedValue { get; set; } + } + + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 2fb54a2c80..01524e5962 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -341,6 +341,7 @@ + From 6544b5c27242be6a80bd1da7b05a774521a75e75 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Thu, 18 Jul 2019 13:03:14 +0100 Subject: [PATCH 166/776] Update src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs Co-Authored-By: Bjarke Berg --- .../V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs index 5f49680d89..1b6597a660 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs @@ -85,7 +85,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 if (exceptions.Any()) { - throw new AggregateException(exceptions); + throw new AggregateException("One or more errors related to unexpected data in grid values occurred.", exceptions); } Context.AddPostMigration(); From 929c84822aaabe9437756c5272d13b32245d3f6a Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 19 Jul 2019 15:18:00 +1000 Subject: [PATCH 167/776] Fixes the Children ext method and adds unit tests --- .../PublishedContentExtensions.cs | 4 - .../PublishedContent/NuCacheChildrenTests.cs | 306 ++++++++++++------ 2 files changed, 209 insertions(+), 101 deletions(-) diff --git a/src/Umbraco.Core/PublishedContentExtensions.cs b/src/Umbraco.Core/PublishedContentExtensions.cs index f220f307d6..e09e76d06a 100644 --- a/src/Umbraco.Core/PublishedContentExtensions.cs +++ b/src/Umbraco.Core/PublishedContentExtensions.cs @@ -111,10 +111,6 @@ namespace Umbraco.Core /// public static IEnumerable Children(this IPublishedContent content, string culture = null) { - // invariant has invariant value (whatever the requested culture) - if (!content.ContentType.VariesByCulture() && culture != "*") - culture = ""; - // handle context culture for variant if (culture == null) culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index f3a520ead1..cc6bcab036 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -151,117 +151,139 @@ namespace Umbraco.Tests.PublishedContent Mock.Get(factory).Setup(x => x.GetInstance(typeof(IVariationContextAccessor))).Returns(_variationAccesor); } + private IEnumerable GetNestedVariantKits() + { + var paths = new Dictionary { { -1, "-1" } }; + + //1x variant (root) + yield return CreateVariantKit(1, -1, 1, paths); + + //1x invariant under root + yield return CreateInvariantKit(4, 1, 1, paths); + + //1x variant under root + yield return CreateVariantKit(7, 1, 4, paths); + + //2x mixed under invariant + yield return CreateVariantKit(10, 4, 1, paths); + yield return CreateInvariantKit(11, 4, 2, paths); + + //2x mixed under variant + yield return CreateVariantKit(12, 7, 1, paths); + yield return CreateInvariantKit(13, 7, 2, paths); + } + private IEnumerable GetInvariantKits() { var paths = new Dictionary { { -1, "-1" } }; - ContentNodeKit CreateKit(int id, int parentId, int sortOrder) + yield return CreateInvariantKit(1, -1, 1, paths); + yield return CreateInvariantKit(2, -1, 2, paths); + yield return CreateInvariantKit(3, -1, 3, paths); + + yield return CreateInvariantKit(4, 1, 1, paths); + yield return CreateInvariantKit(5, 1, 2, paths); + yield return CreateInvariantKit(6, 1, 3, paths); + + yield return CreateInvariantKit(7, 2, 3, paths); + yield return CreateInvariantKit(8, 2, 2, paths); + yield return CreateInvariantKit(9, 2, 1, paths); + + yield return CreateInvariantKit(10, 3, 1, paths); + + yield return CreateInvariantKit(11, 4, 1, paths); + yield return CreateInvariantKit(12, 4, 2, paths); + } + + private ContentNodeKit CreateInvariantKit(int id, int parentId, int sortOrder, Dictionary paths) + { + if (!paths.TryGetValue(parentId, out var parentPath)) + throw new Exception("Unknown parent."); + + var path = paths[id] = parentPath + "," + id; + var level = path.Count(x => x == ','); + var now = DateTime.Now; + + return new ContentNodeKit { - if (!paths.TryGetValue(parentId, out var parentPath)) - throw new Exception("Unknown parent."); - - var path = paths[id] = parentPath + "," + id; - var level = path.Count(x => x == ','); - var now = DateTime.Now; - - return new ContentNodeKit + ContentTypeId = _contentTypeInvariant.Id, + Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData { - ContentTypeId = _contentTypeInvariant.Id, - Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0), - DraftData = null, - PublishedData = new ContentData - { - Name = "N" + id, - Published = true, - TemplateId = 0, - VersionId = 1, - VersionDate = now, - WriterId = 0, - Properties = new Dictionary(), - CultureInfos = new Dictionary() - } - }; - } - - yield return CreateKit(1, -1, 1); - yield return CreateKit(2, -1, 2); - yield return CreateKit(3, -1, 3); - - yield return CreateKit(4, 1, 1); - yield return CreateKit(5, 1, 2); - yield return CreateKit(6, 1, 3); - - yield return CreateKit(7, 2, 3); - yield return CreateKit(8, 2, 2); - yield return CreateKit(9, 2, 1); - - yield return CreateKit(10, 3, 1); - - yield return CreateKit(11, 4, 1); - yield return CreateKit(12, 4, 2); + Name = "N" + id, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = new Dictionary() + } + }; } private IEnumerable GetVariantKits() { var paths = new Dictionary { { -1, "-1" } }; - Dictionary GetCultureInfos(int id, DateTime now) + yield return CreateVariantKit(1, -1, 1, paths); + yield return CreateVariantKit(2, -1, 2, paths); + yield return CreateVariantKit(3, -1, 3, paths); + + yield return CreateVariantKit(4, 1, 1, paths); + yield return CreateVariantKit(5, 1, 2, paths); + yield return CreateVariantKit(6, 1, 3, paths); + + yield return CreateVariantKit(7, 2, 3, paths); + yield return CreateVariantKit(8, 2, 2, paths); + yield return CreateVariantKit(9, 2, 1, paths); + + yield return CreateVariantKit(10, 3, 1, paths); + + yield return CreateVariantKit(11, 4, 1, paths); + yield return CreateVariantKit(12, 4, 2, paths); + } + + private static Dictionary GetCultureInfos(int id, DateTime now) + { + var en = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; + var fr = new[] { 1, 3, 4, 6, 7, 9, 10, 12 }; + + var infos = new Dictionary(); + if (en.Contains(id)) + infos["en-US"] = new CultureVariation { Name = "N" + id + "-" + "en-US", Date = now, IsDraft = false }; + if (fr.Contains(id)) + infos["fr-FR"] = new CultureVariation { Name = "N" + id + "-" + "fr-FR", Date = now, IsDraft = false }; + return infos; + } + + private ContentNodeKit CreateVariantKit(int id, int parentId, int sortOrder, Dictionary paths) + { + if (!paths.TryGetValue(parentId, out var parentPath)) + throw new Exception("Unknown parent."); + + var path = paths[id] = parentPath + "," + id; + var level = path.Count(x => x == ','); + var now = DateTime.Now; + + return new ContentNodeKit { - var en = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; - var fr = new[] { 1, 3, 4, 6, 7, 9, 10, 12 }; - - var infos = new Dictionary(); - if (en.Contains(id)) - infos["en-US"] = new CultureVariation { Name = "N" + id + "-" + "en-US", Date = now, IsDraft = false }; - if (fr.Contains(id)) - infos["fr-FR"] = new CultureVariation { Name = "N" + id + "-" + "fr-FR", Date = now, IsDraft = false }; - return infos; - } - - ContentNodeKit CreateKit(int id, int parentId, int sortOrder) - { - if (!paths.TryGetValue(parentId, out var parentPath)) - throw new Exception("Unknown parent."); - - var path = paths[id] = parentPath + "," + id; - var level = path.Count(x => x == ','); - var now = DateTime.Now; - - return new ContentNodeKit + ContentTypeId = _contentTypeVariant.Id, + Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0), + DraftData = null, + PublishedData = new ContentData { - ContentTypeId = _contentTypeVariant.Id, - Node = new ContentNode(id, Guid.NewGuid(), level, path, sortOrder, parentId, DateTime.Now, 0), - DraftData = null, - PublishedData = new ContentData - { - Name = "N" + id, - Published = true, - TemplateId = 0, - VersionId = 1, - VersionDate = now, - WriterId = 0, - Properties = new Dictionary(), - CultureInfos = GetCultureInfos(id, now) - } - }; - } - - yield return CreateKit(1, -1, 1); - yield return CreateKit(2, -1, 2); - yield return CreateKit(3, -1, 3); - - yield return CreateKit(4, 1, 1); - yield return CreateKit(5, 1, 2); - yield return CreateKit(6, 1, 3); - - yield return CreateKit(7, 2, 3); - yield return CreateKit(8, 2, 2); - yield return CreateKit(9, 2, 1); - - yield return CreateKit(10, 3, 1); - - yield return CreateKit(11, 4, 1); - yield return CreateKit(12, 4, 2); + Name = "N" + id, + Published = true, + TemplateId = 0, + VersionId = 1, + VersionDate = now, + WriterId = 0, + Properties = new Dictionary(), + CultureInfos = GetCultureInfos(id, now) + } + }; } private IEnumerable GetVariantWithDraftKits() @@ -660,6 +682,96 @@ namespace Umbraco.Tests.PublishedContent Assert.AreEqual(1, snapshot.Content.GetById(7).Parent?.Id); } + [Test] + public void NestedVariationChildrenTest() + { + var mixedKits = GetNestedVariantKits(); + Init(mixedKits); + + var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null); + _snapshotAccessor.PublishedSnapshot = snapshot; + + //TEST with en-us variation context + + _variationAccesor.VariationContext = new VariationContext("en-US"); + + var documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1-en-US"); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4", "N7-en-US"); + + //Get the invariant and list children, there's a variation context so it should return invariant AND en-us variants + documents = snapshot.Content.GetById(4).Children().ToArray(); + AssertDocuments(documents, "N10-en-US", "N11"); + + //Get the variant and list children, there's a variation context so it should return invariant AND en-us variants + documents = snapshot.Content.GetById(7).Children().ToArray(); + AssertDocuments(documents, "N12-en-US", "N13"); + + //TEST with fr-fr variation context + + _variationAccesor.VariationContext = new VariationContext("fr-FR"); + + documents = snapshot.Content.GetAtRoot().ToArray(); + AssertDocuments(documents, "N1-fr-FR"); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4", "N7-fr-FR"); + + //Get the invariant and list children, there's a variation context so it should return invariant AND en-us variants + documents = snapshot.Content.GetById(4).Children().ToArray(); + AssertDocuments(documents, "N10-fr-FR", "N11"); + + //Get the variant and list children, there's a variation context so it should return invariant AND en-us variants + documents = snapshot.Content.GetById(7).Children().ToArray(); + AssertDocuments(documents, "N12-fr-FR", "N13"); + + //TEST specific cultures + + documents = snapshot.Content.GetAtRoot("fr-FR").ToArray(); + AssertDocuments(documents, "N1-fr-FR"); + + documents = snapshot.Content.GetById(1).Children("fr-FR").ToArray(); + AssertDocuments(documents, "N4", "N7-fr-FR"); //TODO: Returns invariant ... is that expected? + documents = snapshot.Content.GetById(1).Children("").ToArray(); + AssertDocuments(documents, "N4"); //Only returns invariant since that is what was requested + + documents = snapshot.Content.GetById(4).Children("fr-FR").ToArray(); + AssertDocuments(documents, "N10-fr-FR", "N11"); //TODO: Returns invariant ... is that expected? + documents = snapshot.Content.GetById(4).Children("").ToArray(); + AssertDocuments(documents, "N11"); //Only returns invariant since that is what was requested + + documents = snapshot.Content.GetById(7).Children("fr-FR").ToArray(); + AssertDocuments(documents, "N12-fr-FR", "N13"); //TODO: Returns invariant ... is that expected? + documents = snapshot.Content.GetById(7).Children("").ToArray(); + AssertDocuments(documents, "N13"); //Only returns invariant since that is what was requested + + //TEST without variation context + // This will actually convert the culture to "" which will be invariant since that's all it will know how to do + // This will return a NULL name for culture specific entities because there is no variation context + + _variationAccesor.VariationContext = null; + + documents = snapshot.Content.GetAtRoot().ToArray(); + //will return nothing because there's only variant at root + Assert.AreEqual(0, documents.Length); + //so we'll continue to getting the known variant, do not fully assert this because the Name will NULL + documents = snapshot.Content.GetAtRoot("fr-FR").ToArray(); + Assert.AreEqual(1, documents.Length); + + documents = snapshot.Content.GetById(1).Children().ToArray(); + AssertDocuments(documents, "N4"); + + //Get the invariant and list children + documents = snapshot.Content.GetById(4).Children().ToArray(); + AssertDocuments(documents, "N11"); + + //Get the variant and list children + documents = snapshot.Content.GetById(7).Children().ToArray(); + AssertDocuments(documents, "N13"); + } + [Test] public void VariantChildrenTest() { From 487e45b45bf8d74095838bd1268c809c1e3c49b3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 19 Jul 2019 15:26:31 +1000 Subject: [PATCH 168/776] updates note --- src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index cc6bcab036..65736aabd4 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -733,17 +733,17 @@ namespace Umbraco.Tests.PublishedContent AssertDocuments(documents, "N1-fr-FR"); documents = snapshot.Content.GetById(1).Children("fr-FR").ToArray(); - AssertDocuments(documents, "N4", "N7-fr-FR"); //TODO: Returns invariant ... is that expected? + AssertDocuments(documents, "N4", "N7-fr-FR"); //NOTE: Returns invariant, this is expected documents = snapshot.Content.GetById(1).Children("").ToArray(); AssertDocuments(documents, "N4"); //Only returns invariant since that is what was requested documents = snapshot.Content.GetById(4).Children("fr-FR").ToArray(); - AssertDocuments(documents, "N10-fr-FR", "N11"); //TODO: Returns invariant ... is that expected? + AssertDocuments(documents, "N10-fr-FR", "N11"); //NOTE: Returns invariant, this is expected documents = snapshot.Content.GetById(4).Children("").ToArray(); AssertDocuments(documents, "N11"); //Only returns invariant since that is what was requested documents = snapshot.Content.GetById(7).Children("fr-FR").ToArray(); - AssertDocuments(documents, "N12-fr-FR", "N13"); //TODO: Returns invariant ... is that expected? + AssertDocuments(documents, "N12-fr-FR", "N13"); //NOTE: Returns invariant, this is expected documents = snapshot.Content.GetById(7).Children("").ToArray(); AssertDocuments(documents, "N13"); //Only returns invariant since that is what was requested From b07df634224443152e8d4aebce967c12fe2c39f4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 19 Jul 2019 15:49:39 +1000 Subject: [PATCH 169/776] Adds some better notes to the api docs --- src/Umbraco.Core/PublishedContentExtensions.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/PublishedContentExtensions.cs b/src/Umbraco.Core/PublishedContentExtensions.cs index e09e76d06a..921883b822 100644 --- a/src/Umbraco.Core/PublishedContentExtensions.cs +++ b/src/Umbraco.Core/PublishedContentExtensions.cs @@ -103,11 +103,23 @@ namespace Umbraco.Core /// Gets the children of the content item. ///
/// The content item. - /// The specific culture to get the url children for. If null is used the current culture is used (Default is null). + /// + /// The specific culture to get the url children for. Default is null which will use the current culture in + /// /// /// Gets children that are available for the specified culture. /// Children are sorted by their sortOrder. - /// The '*' culture and supported and returns everything. + /// + /// For culture, + /// if null is used the current culture is used. + /// If an empty string is used only invariant children are returned. + /// If "*" is used all children are returned. + /// + /// + /// If a variant culture is specified or there is a current culture in the then the Children returned + /// will include both the variant children matching the culture AND the invariant children because the invariant children flow with the current culture. + /// However, if an empty string is specified only invariant children are returned. + /// /// public static IEnumerable Children(this IPublishedContent content, string culture = null) { From 02f49a003931a82d0a827e641206ab15d5acd150 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 19 Jul 2019 16:00:42 +1000 Subject: [PATCH 170/776] Cherry picks fix for 5894 and makes adjustments --- .../PublishedCache/NuCache/PublishedSnapshotService.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index dad9811af8..75f6d2f5c3 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -1056,8 +1056,10 @@ namespace Umbraco.Web.PublishedCache.NuCache : _dataSource.GetTypeMediaSources(scope, refreshedIds).ToArray(); _mediaStore.UpdateContentTypes(removedIds, typesA, kits); - _mediaStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray()); - _mediaStore.NewContentTypes(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray()); + if (!otherIds.IsCollectionEmpty()) + _mediaStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray()); + if (!newIds.IsCollectionEmpty()) + _mediaStore.NewContentTypes(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray()); scope.Complete(); } } From 47c3e3a79f4c40a53390b1a3867a13bbf50e895b Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 19 Jul 2019 11:20:31 +0200 Subject: [PATCH 171/776] https://github.com/umbraco/Umbraco-CMS/issues/5921 - AB#1794 - Remove trashed nodes from sql when building xml for previews --- .../Repositories/ContentRepository.cs | 2745 +++++++++-------- 1 file changed, 1373 insertions(+), 1372 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index be5cc20cb6..a0b211b6b2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -1,1372 +1,1373 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Globalization; -using System.Linq; -using System.Xml; -using System.Xml.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.Factories; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Persistence.SqlSyntax; -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 ContentPreviewRepository _contentPreviewRepository; - private readonly ContentXmlRepository _contentXmlRepository; - private readonly PermissionRepository _permissionRepository; - private readonly ContentByGuidReadRepository _contentByGuidReadRepository; - - public ContentRepository(IScopeUnitOfWork 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; - _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.NoCache, logger, syntaxProvider); - _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.NoCache, logger, syntaxProvider); - _permissionRepository = new PermissionRepository(UnitOfWork, cacheHelper, Logger, SqlSyntax); - _contentByGuidReadRepository = new ContentByGuidReadRepository(this, work, cacheHelper, logger, syntaxProvider); - EnsureUniqueNaming = true; - } - - public bool EnsureUniqueNaming { get; set; } - - #region Overrides of RepositoryBase - - protected override IContent PerformGet(int id) - { - var sql = GetBaseQuery(BaseQueryType.FullSingle) - .Where(GetBaseWhereClause(), new { Id = id }) - .Where(x => x.Newest, SqlSyntax) - .OrderByDescending(x => x.VersionDate, SqlSyntax); - - var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); - - if (dto == null) - return null; - - var content = CreateContentFromDto(dto, sql); - - return content; - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - Func translate = s => - { - if (ids.Any()) - { - s.Where("umbracoNode.id in (@ids)", new { ids }); - } - //we only want the newest ones with this method - s.Where(x => x.Newest, SqlSyntax); - return s; - }; - - var sqlBaseFull = GetBaseQuery(BaseQueryType.FullMultiple); - var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids); - - return ProcessQuery(translate(sqlBaseFull), new PagingSqlQuery(translate(sqlBaseIds))); - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - var sqlBaseFull = GetBaseQuery(BaseQueryType.FullMultiple); - var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids); - - Func, Sql> translate = (translator) => - { - return translator.Translate() - .Where(x => x.Newest, SqlSyntax) - .OrderByDescending(x => x.VersionDate, SqlSyntax) - .OrderBy(x => x.SortOrder, SqlSyntax); - }; - - var translatorFull = new SqlTranslator(sqlBaseFull, query); - var translatorIds = new SqlTranslator(sqlBaseIds, query); - - return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds))); - } - - #endregion - - #region Overrides of PetaPocoRepositoryBase - - /// - /// Returns the base query to return Content - /// - /// - /// - /// - /// Content queries will differ depending on what needs to be returned: - /// * FullSingle: When querying for a single document, this will include the Outer join to fetch the content item's published version info - /// * FullMultiple: When querying for multiple documents, this will exclude the Outer join to fetch the content item's published version info - this info would need to be fetched separately - /// * Ids: This would essentially be the same as FullMultiple however the columns specified will only return the Ids for the documents - /// * Count: A query to return the count for documents - /// - protected override Sql GetBaseQuery(BaseQueryType queryType) - { - var sql = new Sql(); - sql.Select(queryType == BaseQueryType.Count ? "COUNT(*)" : (queryType == BaseQueryType.Ids ? "cmsDocument.nodeId" : "*")) - .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) - .InnerJoin() - .On(left => left.NodeId, right => right.ContentTypeId); - //TODO: IF we want to enable querying on content type information this will need to be joined - //.InnerJoin(SqlSyntax) - //.On(SqlSyntax, left => left.ContentTypeId, right => right.NodeId, SqlSyntax); - - if (queryType == BaseQueryType.FullSingle) - { - //The only reason we apply this left outer join is to be able to pull back the DocumentPublishedReadOnlyDto - //information with the entire data set, so basically this will get both the latest document and also it's published - //version if it has one. When performing a count or when retrieving Ids like in paging, this is unecessary - //and causes huge performance overhead for the SQL server, especially when sorting the result. - //We also don't include this outer join when querying for multiple entities since it is much faster to fetch this information - //in a separate query. For a single entity this is ok. - - 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")); - - // 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 - sql.Append(sqlx /*, new { @published = true }*/); - } - - sql.Where(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax); - - return sql; - } - - protected override Sql GetBaseQuery(bool isCount) - { - return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.FullSingle); - } - - protected override string GetBaseWhereClause() - { - return "umbracoNode.id = @Id"; - } - - protected override IEnumerable GetDeleteClauses() - { - var list = new List - { - "DELETE FROM umbracoRedirectUrl WHERE contentKey IN (SELECT uniqueID FROM umbracoNode WHERE id = @Id)", - "DELETE FROM cmsTask WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", - "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @Id", - "DELETE FROM umbracoUserStartNode WHERE startNode = @Id", - "UPDATE umbracoUserGroup SET startContentId = NULL WHERE startContentId = @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 Constants.ObjectTypes.DocumentGuid; } - } - - #endregion - - #region Overrides of VersionableRepositoryBase - - public void RebuildXmlStructures(Func serializer, int groupSize = 200, IEnumerable contentTypeIds = null) - { - // the previous way of doing this was to run it all in one big transaction, - // and to bulk-insert groups of xml rows - which works, until the transaction - // times out - and besides, because v7 transactions are ReadCommited, it does - // not bring much safety - so this reverts to updating each record individually, - // and it may be slower in the end, but should be more resilient. - - var contentTypeIdsA = contentTypeIds == null ? new int[0] : contentTypeIds.ToArray(); - - Func translate = (bId, sql) => - { - if (contentTypeIdsA.Length > 0) - { - sql.WhereIn(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax); - } - - sql - .Where(x => x.NodeId > bId && x.Trashed == false, SqlSyntax) - .Where(x => x.Published, SqlSyntax) - .OrderBy(x => x.NodeId, SqlSyntax); - - return sql; - }; - - var baseId = 0; - - while (true) - { - // get the next group of nodes - var sqlFull = translate(baseId, GetBaseQuery(BaseQueryType.FullMultiple)); - var sqlIds = translate(baseId, GetBaseQuery(BaseQueryType.Ids)); - - var xmlItems = ProcessQuery(SqlSyntax.SelectTop(sqlFull, groupSize), new PagingSqlQuery(SqlSyntax.SelectTop(sqlIds, groupSize))) - .Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() }) - .ToList(); - - // no more nodes, break - if (xmlItems.Count == 0) break; - - foreach (var xmlItem in xmlItems) - { - try - { - // should happen in most cases, then it tries to insert, and it should work - // unless the node has been deleted, and we just report the exception - Database.InsertOrUpdate(xmlItem); - } - catch (Exception e) - { - Logger.Error("Could not rebuild XML for nodeId=" + xmlItem.NodeId, e); - } - } - baseId = xmlItems[xmlItems.Count - 1].NodeId; - } - - //now delete the items that shouldn't be there - var sqlAllIds = translate(0, GetBaseQuery(BaseQueryType.Ids)); - var allContentIds = Database.Fetch(sqlAllIds); - var docObjectType = NodeObjectTypeId; - var xmlIdsQuery = new Sql() - .Select("DISTINCT cmsContentXml.nodeId") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId); - - if (contentTypeIdsA.Length > 0) - { - xmlIdsQuery.InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.NodeId) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.NodeId, right => right.ContentTypeId) - .WhereIn(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax); - } - - xmlIdsQuery.Where(dto => dto.NodeObjectType == docObjectType, SqlSyntax); - - var allXmlIds = Database.Fetch(xmlIdsQuery); - - var toRemove = allXmlIds.Except(allContentIds).ToArray(); - if (toRemove.Length > 0) - { - foreach (var idGroup in toRemove.InGroupsOf(2000)) - { - Database.Execute("DELETE FROM cmsContentXml WHERE nodeId IN (@ids)", new { ids = idGroup }); - } - } - - } - - public override IEnumerable GetAllVersions(int id) - { - Func translate = s => - { - return s.Where(GetBaseWhereClause(), new {Id = id}) - .OrderByDescending(x => x.VersionDate, SqlSyntax); - }; - - var sqlFull = translate(GetBaseQuery(BaseQueryType.FullMultiple)); - var sqlIds = translate(GetBaseQuery(BaseQueryType.Ids)); - - return ProcessQuery(sqlFull, new PagingSqlQuery(sqlIds), true, includeAllVersions:true); - } - - public override IContent GetByVersion(Guid versionId) - { - var sql = GetBaseQuery(BaseQueryType.FullSingle); - //TODO: cmsContentVersion.VersionId has a Unique Index constraint applied, seems silly then to also add OrderByDescending since it would be impossible to return more than one. - sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(x => x.VersionDate, SqlSyntax); - - var dto = Database.Fetch(sql).FirstOrDefault(); - - if (dto == null) - return null; - - var content = CreateContentFromDto(dto, sql); - - return content; - } - - public override void DeleteVersion(Guid versionId) - { - var sql = new Sql() - .Select("*") - .From(SqlSyntax) - .InnerJoin(SqlSyntax) - .On(SqlSyntax, left => left.VersionId, right => right.VersionId) - .Where(x => x.VersionId == versionId, SqlSyntax) - .Where(x => x.Newest != true, SqlSyntax); - 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; - - // note: - // there used to be a check on Database.IsNew(nodeDto) here to either Insert or Update, - // but I cannot figure out what was the point, as the node should obviously be new if - // we reach that point - removed. - - // see if there's a reserved identifier for this unique id - var sql = new Sql("SELECT id FROM umbracoNode WHERE uniqueID=@0 AND nodeObjectType=@1", nodeDto.UniqueId, Constants.ObjectTypes.IdReservationGuid); - var id = Database.ExecuteScalar(sql); - if (id > 0) - { - nodeDto.NodeId = id; - Database.Update(nodeDto); - } - else - { - Database.Insert(nodeDto); - } - - //Update with new correct path - nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); - nodeDto.ValidatePathWithException(); - 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 - 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, - VersionDate = dto.UpdateDate, - Newest = true, - NodeId = dto.NodeId, - Published = true - }; - ((Content) entity).PublishedVersionGuid = dto.VersionId; - ((Content) entity).PublishedDate = dto.UpdateDate; - } - - 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 - { - if (entity.IsPropertyDirty("UpdateDate") == false || entity.UpdateDate == default(DateTime)) - 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; - nodeDto.ValidatePathWithException(); - 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)) - { - //TODO: This perf can be improved, it could easily be UPDATE WHERE.... (one SQL call instead of many) - 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 - //TODO: This perf can be improved, it could easily be UPDATE WHERE.... (one SQL call instead of many) - 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 }); - if (contentVerDto != null) - { - 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, - VersionDate = dto.UpdateDate, - Newest = true, - NodeId = dto.NodeId, - Published = true - }; - ((Content) entity).PublishedVersionGuid = dto.VersionId; - ((Content) entity).PublishedDate = dto.UpdateDate; - } - else if (publishedStateChanged) - { - dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto - { - VersionId = default (Guid), - VersionDate = default (DateTime), - Newest = false, - NodeId = dto.NodeId, - Published = false - }; - ((Content) entity).PublishedVersionGuid = default(Guid); - ((Content) entity).PublishedDate = default (DateTime); - } - - entity.ResetDirtyProperties(); - } - - - #endregion - - #region Implementation of IContentRepository - - public IEnumerable GetByPublishedVersion(IQuery query) - { - Func, Sql> translate = t => - { - return t.Translate() - .Where(x => x.Published, SqlSyntax) - .OrderBy(x => x.Level, SqlSyntax) - .OrderBy(x => x.SortOrder, SqlSyntax); - }; - - // 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 sqlFull = GetBaseQuery(BaseQueryType.FullMultiple); - var translatorFull = new SqlTranslator(sqlFull, query); - var sqlIds = GetBaseQuery(BaseQueryType.Ids); - var translatorIds = new SqlTranslator(sqlIds, query); - - return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds)), true); - } - - public IEnumerable GetBlueprints(IQuery query) - { - Func, Sql> translate = t => t.Translate(); - - var sqlFull = GetBaseQuery(BaseQueryType.FullMultiple); - var translatorFull = new SqlTranslator(sqlFull, query); - var sqlIds = GetBaseQuery(BaseQueryType.Ids); - var translatorIds = new SqlTranslator(sqlIds, query); - - return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds)), true); - } - - /// - /// This builds the Xml document used for the XML cache - /// - /// - public XmlDocument BuildXmlCache() - { - //TODO: This is what we should do , but converting to use XDocument would be breaking unless we convert - // to XmlDocument at the end of this, but again, this would be bad for memory... though still not nearly as - // bad as what is happening before! - // We'll keep using XmlDocument for now though, but XDocument xml generation is much faster: - // https://blogs.msdn.microsoft.com/codejunkie/2008/10/08/xmldocument-vs-xelement-performance/ - // I think we already have code in here to convert XDocument to XmlDocument but in case we don't here - // it is: https://blogs.msdn.microsoft.com/marcelolr/2009/03/13/fast-way-to-convert-xmldocument-into-xdocument/ - - //// Prepare an XmlDocument with an appropriate inline DTD to match - //// the expected content - //var parent = new XElement("root", new XAttribute("id", "-1")); - //var xmlDoc = new XDocument( - // new XDocumentType("root", null, null, DocumentType.GenerateDtd()), - // parent); - - var xmlDoc = new XmlDocument(); - var doctype = xmlDoc.CreateDocumentType("root", null, null, - ApplicationContext.Current.Services.ContentTypeService.GetContentTypesDtd()); - xmlDoc.AppendChild(doctype); - var parent = xmlDoc.CreateElement("root"); - var pIdAtt = xmlDoc.CreateAttribute("id"); - pIdAtt.Value = "-1"; - parent.Attributes.Append(pIdAtt); - xmlDoc.AppendChild(parent); - - //Ensure that only nodes that have published versions are selected - var sql = string.Format(@"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsContentXml.{0}, umbracoNode.{1} from umbracoNode -inner join cmsContentXml on cmsContentXml.nodeId = umbracoNode.id and umbracoNode.nodeObjectType = @type -where umbracoNode.id in (select cmsDocument.nodeId from cmsDocument where cmsDocument.published = 1) -order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", - SqlSyntax.GetQuotedColumnName("xml"), - SqlSyntax.GetQuotedColumnName("level"), - SqlSyntax.GetQuotedColumnName("level")); - - XmlElement last = null; - - //NOTE: Query creates a reader - does not load all into memory - foreach (var row in Database.Query(sql, new { type = NodeObjectTypeId })) - { - string parentId = ((int)row.parentID).ToInvariantString(); - string xml = row.xml; - int sortOrder = row.sortOrder; - - //if the parentid is changing - if (last != null && last.GetAttribute("parentID") != parentId) - { - parent = xmlDoc.GetElementById(parentId); - if (parent == null) - { - //Need to short circuit here, if the parent is not there it means that the parent is unpublished - // and therefore the child is not published either so cannot be included in the xml cache - continue; - } - } - - var xmlDocFragment = xmlDoc.CreateDocumentFragment(); - xmlDocFragment.InnerXml = xml; - - last = (XmlElement)parent.AppendChild(xmlDocFragment); - - // fix sortOrder - see notes in UpdateSortOrder - last.Attributes["sortOrder"].Value = sortOrder.ToInvariantString(); - } - - return xmlDoc; - - } - - public XmlDocument BuildPreviewXmlCache() - { - var xmlDoc = new XmlDocument(); - var doctype = xmlDoc.CreateDocumentType("root", null, null, - ApplicationContext.Current.Services.ContentTypeService.GetContentTypesDtd()); - xmlDoc.AppendChild(doctype); - var parent = xmlDoc.CreateElement("root"); - var pIdAtt = xmlDoc.CreateAttribute("id"); - pIdAtt.Value = "-1"; - parent.Attributes.Append(pIdAtt); - xmlDoc.AppendChild(parent); - - //Ensure that only nodes that have published versions are selected - var sql = string.Format(@"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsPreviewXml.{0}, umbracoNode.{1} from umbracoNode -inner join cmsPreviewXml on cmsPreviewXml.nodeId = umbracoNode.id and umbracoNode.nodeObjectType = @type -inner join cmsDocument on cmsPreviewXml.versionId = cmsDocument.versionId and cmsDocument.newest=1 -order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", - SqlSyntax.GetQuotedColumnName("xml"), - SqlSyntax.GetQuotedColumnName("level"), - SqlSyntax.GetQuotedColumnName("level")); - - XmlElement last = null; - - //NOTE: Query creates a reader - does not load all into memory - foreach (var row in Database.Query(sql, new { type = NodeObjectTypeId })) - { - string parentId = ((int)row.parentID).ToInvariantString(); - string xml = row.xml; - int sortOrder = row.sortOrder; - - //if the parentid is changing - if (last != null && last.GetAttribute("parentID") != parentId) - { - parent = xmlDoc.GetElementById(parentId); - if (parent == null) - { - //Need to short circuit here, if the parent is not there it means that the parent is unpublished - // and therefore the child is not published either so cannot be included in the xml cache - continue; - } - } - - var xmlDocFragment = xmlDoc.CreateDocumentFragment(); - xmlDocFragment.InnerXml = xml; - - last = (XmlElement)parent.AppendChild(xmlDocFragment); - - // fix sortOrder - see notes in UpdateSortOrder - last.Attributes["sortOrder"].Value = sortOrder.ToInvariantString(); - } - - return xmlDoc; - - } - - public int CountPublished(string contentTypeAlias = null) - { - if (contentTypeAlias.IsNullOrWhiteSpace()) - { - var sql = GetBaseQuery(true).Where(x => x.Trashed == false) - .Where(x => x.Published == true); - return Database.ExecuteScalar(sql); - } - else - { - var sql = GetBaseQuery(true).Where(x => x.Trashed == false) - .Where(x => x.Published == true) - .Where(x => x.Alias == contentTypeAlias); - return Database.ExecuteScalar(sql); - } - } - - public void ReplaceContentPermissions(EntityPermissionSet permissionSet) - { - _permissionRepository.ReplaceEntityPermissions(permissionSet); - } - - public void ClearPublished(IContent content) - { - var sql = "UPDATE cmsDocument SET published=0 WHERE nodeId=@id AND published=1"; - Database.Execute(sql, new {id = content.Id}); - } - - /// - /// Assigns a single permission to the current content item for the specified user group ids - /// - /// - /// - /// - public void AssignEntityPermission(IContent entity, char permission, IEnumerable groupIds) - { - _permissionRepository.AssignEntityPermission(entity, permission, groupIds); - } - - /// - /// Gets the explicit list of permissions for the content item - /// - /// - /// - public EntityPermissionCollection GetPermissionsForEntity(int entityId) - { - return _permissionRepository.GetPermissionsForEntity(entityId); - } - - /// - /// Adds/updates content/published xml - /// - /// - /// - public void AddOrUpdateContentXml(IContent content, Func xml) - { - _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(content, xml)); - } - - /// - /// Used to add/update a permission for a content item - /// - /// - public void AddOrUpdatePermissions(ContentPermissionSet permission) - { - _permissionRepository.AddOrUpdate(permission); - } - - /// - /// 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, IQuery filter = null) - { - - //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 filterSql = new Sql().Append("AND (cmsDocument.newest = 1)"); - if (filter != null) - { - foreach (var filterClause in filter.GetWhereClauses()) - { - filterSql.Append(string.Format("AND ({0})", filterClause.Item1), filterClause.Item2); - } - } - - Func> filterCallback = () => new Tuple(filterSql.SQL, filterSql.Arguments); - - return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - new Tuple("cmsDocument", "nodeId"), - (sqlFull, pagingSqlQuery) => ProcessQuery(sqlFull, pagingSqlQuery), orderBy, orderDirection, orderBySystemField, - filterCallback); - - } - - #endregion - - #region IRecycleBinRepository members - - protected override int RecycleBinId - { - get { return Constants.System.RecycleBinContent; } - } - - #endregion - - #region Read Repository implementation for GUID keys - public IContent Get(Guid id) - { - return _contentByGuidReadRepository.Get(id); - } - - IEnumerable IReadRepository.GetAll(params Guid[] ids) - { - return _contentByGuidReadRepository.GetAll(ids); - } - - public bool Exists(Guid id) - { - return _contentByGuidReadRepository.Exists(id); - } - - /// - /// A reading repository purely for looking up by GUID - /// - /// - /// TODO: This is ugly and to fix we need to decouple the IRepositoryQueryable -> IRepository -> IReadRepository which should all be separate things! - /// Then we can do the same thing with repository instances and we wouldn't need to leave all these methods as not implemented because we wouldn't need to implement them - /// - private class ContentByGuidReadRepository : PetaPocoRepositoryBase - { - private readonly ContentRepository _outerRepo; - - public ContentByGuidReadRepository(ContentRepository outerRepo, - IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax) - { - _outerRepo = outerRepo; - } - - protected override IContent PerformGet(Guid id) - { - var sql = _outerRepo.GetBaseQuery(BaseQueryType.FullSingle) - .Where(GetBaseWhereClause(), new { Id = id }) - .Where(x => x.Newest, SqlSyntax) - .OrderByDescending(x => x.VersionDate, SqlSyntax); - - var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); - - if (dto == null) - return null; - - var content = _outerRepo.CreateContentFromDto(dto, sql); - - return content; - } - - protected override IEnumerable PerformGetAll(params Guid[] ids) - { - Func translate = s => - { - if (ids.Any()) - { - s.Where("umbracoNode.uniqueID in (@ids)", new { ids }); - } - //we only want the newest ones with this method - s.Where(x => x.Newest, SqlSyntax); - return s; - }; - - var sqlBaseFull = _outerRepo.GetBaseQuery(BaseQueryType.FullMultiple); - var sqlBaseIds = _outerRepo.GetBaseQuery(BaseQueryType.Ids); - - return _outerRepo.ProcessQuery(translate(sqlBaseFull), new PagingSqlQuery(translate(sqlBaseIds))); - } - - protected override Sql GetBaseQuery(bool isCount) - { - return _outerRepo.GetBaseQuery(isCount); - } - - protected override string GetBaseWhereClause() - { - return "umbracoNode.uniqueID = @Id"; - } - - protected override Guid NodeObjectTypeId - { - get { return _outerRepo.NodeObjectTypeId; } - } - - #region Not needed to implement - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - throw new NotImplementedException(); - } - protected override IEnumerable GetDeleteClauses() - { - throw new NotImplementedException(); - } - protected override void PersistNewItem(IContent entity) - { - throw new NotImplementedException(); - } - protected override void PersistUpdatedItem(IContent entity) - { - throw new NotImplementedException(); - } - #endregion - } - #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); - } - - /// - /// This is the underlying method that processes most queries for this repository - /// - /// - /// The FullMultiple SQL without the outer join to return all data required to create an IContent excluding it's published state data which this will query separately - /// - /// - /// The Id SQL without the outer join to just return all document ids - used to process the properties for the content item - /// - /// - /// - /// Generally when querying for content we only want to return the most recent version of the content item, however in some cases like when - /// we want to return all versions of a content item, we can't simply return the latest - /// - /// - private IEnumerable ProcessQuery(Sql sqlFull, PagingSqlQuery pagingSqlQuery, bool withCache = false, bool includeAllVersions = false) - { - // fetch returns a list so it's ok to iterate it in this method - var dtos = Database.Fetch(sqlFull); - if (dtos.Count == 0) return Enumerable.Empty(); - - //Go and get all of the published version data separately for this data, this is because when we are querying - //for multiple content items we don't include the outer join to fetch this data in the same query because - //it is insanely slow. Instead we just fetch the published version data separately in one query. - - //we need to parse the original SQL statement and reduce the columns to just cmsDocument.nodeId so that we can use - // the statement to go get the published data for all of the items by using an inner join - var parsedOriginalSql = "SELECT cmsDocument.nodeId " + sqlFull.SQL.Substring(sqlFull.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)); - } - - //order by update date DESC, if there is corrupted published flags we only want the latest! - var publishedSql = new Sql(@"SELECT cmsDocument.nodeId, cmsDocument.published, cmsDocument.versionId, cmsDocument.updateDate, cmsDocument.newest -FROM cmsDocument INNER JOIN cmsContentVersion ON cmsContentVersion.VersionId = cmsDocument.versionId -WHERE cmsDocument.published = 1 AND cmsDocument.nodeId IN -(" + parsedOriginalSql + @") -ORDER BY cmsContentVersion.id DESC -", sqlFull.Arguments); - - //go and get the published version data, we do a Query here and not a Fetch so we are - //not allocating a whole list to memory just to allocate another list in memory since - //we are assigning this data to a keyed collection for fast lookup below - var publishedData = Database.Query(publishedSql); - var publishedDataCollection = new DocumentPublishedReadOnlyDtoCollection(); - foreach (var publishedDto in publishedData) - { - //double check that there's no corrupt db data, there should only be a single published item - if (publishedDataCollection.Contains(publishedDto.NodeId) == false) - publishedDataCollection.Add(publishedDto); - } - - //This is a tuple list identifying if the content item came from the cache or not - var content = new List>(); - var defs = new DocumentDefinitionCollection(includeAllVersions); - var templateIds = new List(); - - //track the looked up content types, even though the content types are cached - // they still need to be deep cloned out of the cache and we don't want to add - // the overhead of deep cloning them on every item in this loop - var contentTypes = new Dictionary(); - - foreach (var dto in dtos) - { - DocumentPublishedReadOnlyDto publishedDto; - publishedDataCollection.TryGetValue(dto.NodeId, out publishedDto); - - // if the cache contains the published version, use it - if (withCache) - { - var cached = IsolatedCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); - //only use this cached version if the dto returned is also the publish version, they must match and be teh same version - if (cached != null && cached.Version == dto.VersionId && cached.Published && dto.Published) - { - content.Add(new Tuple(cached, true)); - continue; - } - } - - // else, need to fetch from the database - // content type repository is full-cache so OK to get each one independently - - IContentType contentType; - if (contentTypes.ContainsKey(dto.ContentVersionDto.ContentDto.ContentTypeId)) - { - contentType = contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId]; - } - else - { - contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); - contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId] = contentType; - } - - // track the definition and if it's successfully added or updated then processed - if (defs.AddOrUpdate(new DocumentDefinition(dto, contentType))) - { - // assign template - if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) - templateIds.Add(dto.TemplateId.Value); - - content.Add(new Tuple(ContentFactory.BuildEntity(dto, contentType, publishedDto), false)); - } - } - - // load all required templates in 1 query - var templates = _templateRepository.GetAll(templateIds.ToArray()) - .ToDictionary(x => x.Id, x => x); - - // load all properties for all documents from database in 1 query - var propertyData = GetPropertyCollection(pagingSqlQuery, defs); - - // assign template and property data - foreach (var contentItem in content) - { - var cc = contentItem.Item1; - var fromCache = contentItem.Item2; - - //if this has come from cache, we do not need to build up it's structure - if (fromCache) continue; - - var def = defs[includeAllVersions ? (ValueType)cc.Version : cc.Id]; - - ITemplate template = null; - if (def.DocumentDto.TemplateId.HasValue) - templates.TryGetValue(def.DocumentDto.TemplateId.Value, out template); // else null - cc.Template = template; - if (propertyData.ContainsKey(cc.Version)) - { - cc.Properties = propertyData[cc.Version]; - } - else - { - throw new InvalidOperationException($"No property data found for version: '{cc.Version}'."); - } - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - cc.ResetDirtyProperties(false); - } - - return content.Select(x => x.Item1).ToArray(); - } - - /// - /// Private method to create a content object from a DocumentDto, which is used by Get and GetByVersion. - /// - /// - /// - /// - private IContent CreateContentFromDto(DocumentDto dto, Sql docSql) - { - var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); - - var content = ContentFactory.BuildEntity(dto, contentType); - - //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, contentType); - - var properties = GetPropertyCollection(docSql, new[] { docDef }); - - content.Properties = properties[dto.VersionId]; - - //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 names = Database.Fetch("SELECT id, text AS name FROM umbracoNode WHERE nodeObjectType=@objectType AND parentId=@parentId", - new { objectType = NodeObjectTypeId, parentId }); - - return SimilarNodeName.GetUniqueName(names, id, nodeName); - } - - /// - /// Dispose disposable properties - /// - /// - /// Ensure the unit of work is disposed - /// - protected override void DisposeResources() - { - _contentTypeRepository.Dispose(); - _templateRepository.Dispose(); - _tagRepository.Dispose(); - _contentPreviewRepository.Dispose(); - _contentXmlRepository.Dispose(); - } - - /// - /// A keyed collection for fast lookup when retrieving a separate list of published version data - /// - private class DocumentPublishedReadOnlyDtoCollection : KeyedCollection - { - protected override int GetKeyForItem(DocumentPublishedReadOnlyDto item) - { - return item.NodeId; - } - - public bool TryGetValue(int key, out DocumentPublishedReadOnlyDto val) - { - if (Dictionary == null) - { - val = null; - return false; - } - return Dictionary.TryGetValue(key, out val); - } - } - } -} +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Linq; +using System.Xml; +using System.Xml.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Factories; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Persistence.SqlSyntax; +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 ContentPreviewRepository _contentPreviewRepository; + private readonly ContentXmlRepository _contentXmlRepository; + private readonly PermissionRepository _permissionRepository; + private readonly ContentByGuidReadRepository _contentByGuidReadRepository; + + public ContentRepository(IScopeUnitOfWork 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; + _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.NoCache, logger, syntaxProvider); + _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.NoCache, logger, syntaxProvider); + _permissionRepository = new PermissionRepository(UnitOfWork, cacheHelper, Logger, SqlSyntax); + _contentByGuidReadRepository = new ContentByGuidReadRepository(this, work, cacheHelper, logger, syntaxProvider); + EnsureUniqueNaming = true; + } + + public bool EnsureUniqueNaming { get; set; } + + #region Overrides of RepositoryBase + + protected override IContent PerformGet(int id) + { + var sql = GetBaseQuery(BaseQueryType.FullSingle) + .Where(GetBaseWhereClause(), new { Id = id }) + .Where(x => x.Newest, SqlSyntax) + .OrderByDescending(x => x.VersionDate, SqlSyntax); + + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); + + if (dto == null) + return null; + + var content = CreateContentFromDto(dto, sql); + + return content; + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + Func translate = s => + { + if (ids.Any()) + { + s.Where("umbracoNode.id in (@ids)", new { ids }); + } + //we only want the newest ones with this method + s.Where(x => x.Newest, SqlSyntax); + return s; + }; + + var sqlBaseFull = GetBaseQuery(BaseQueryType.FullMultiple); + var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids); + + return ProcessQuery(translate(sqlBaseFull), new PagingSqlQuery(translate(sqlBaseIds))); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + var sqlBaseFull = GetBaseQuery(BaseQueryType.FullMultiple); + var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids); + + Func, Sql> translate = (translator) => + { + return translator.Translate() + .Where(x => x.Newest, SqlSyntax) + .OrderByDescending(x => x.VersionDate, SqlSyntax) + .OrderBy(x => x.SortOrder, SqlSyntax); + }; + + var translatorFull = new SqlTranslator(sqlBaseFull, query); + var translatorIds = new SqlTranslator(sqlBaseIds, query); + + return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds))); + } + + #endregion + + #region Overrides of PetaPocoRepositoryBase + + /// + /// Returns the base query to return Content + /// + /// + /// + /// + /// Content queries will differ depending on what needs to be returned: + /// * FullSingle: When querying for a single document, this will include the Outer join to fetch the content item's published version info + /// * FullMultiple: When querying for multiple documents, this will exclude the Outer join to fetch the content item's published version info - this info would need to be fetched separately + /// * Ids: This would essentially be the same as FullMultiple however the columns specified will only return the Ids for the documents + /// * Count: A query to return the count for documents + /// + protected override Sql GetBaseQuery(BaseQueryType queryType) + { + var sql = new Sql(); + sql.Select(queryType == BaseQueryType.Count ? "COUNT(*)" : (queryType == BaseQueryType.Ids ? "cmsDocument.nodeId" : "*")) + .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) + .InnerJoin() + .On(left => left.NodeId, right => right.ContentTypeId); + //TODO: IF we want to enable querying on content type information this will need to be joined + //.InnerJoin(SqlSyntax) + //.On(SqlSyntax, left => left.ContentTypeId, right => right.NodeId, SqlSyntax); + + if (queryType == BaseQueryType.FullSingle) + { + //The only reason we apply this left outer join is to be able to pull back the DocumentPublishedReadOnlyDto + //information with the entire data set, so basically this will get both the latest document and also it's published + //version if it has one. When performing a count or when retrieving Ids like in paging, this is unecessary + //and causes huge performance overhead for the SQL server, especially when sorting the result. + //We also don't include this outer join when querying for multiple entities since it is much faster to fetch this information + //in a separate query. For a single entity this is ok. + + 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")); + + // 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 + sql.Append(sqlx /*, new { @published = true }*/); + } + + sql.Where(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax); + + return sql; + } + + protected override Sql GetBaseQuery(bool isCount) + { + return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.FullSingle); + } + + protected override string GetBaseWhereClause() + { + return "umbracoNode.id = @Id"; + } + + protected override IEnumerable GetDeleteClauses() + { + var list = new List + { + "DELETE FROM umbracoRedirectUrl WHERE contentKey IN (SELECT uniqueID FROM umbracoNode WHERE id = @Id)", + "DELETE FROM cmsTask WHERE nodeId = @Id", + "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", + "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @Id", + "DELETE FROM umbracoUserStartNode WHERE startNode = @Id", + "UPDATE umbracoUserGroup SET startContentId = NULL WHERE startContentId = @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 Constants.ObjectTypes.DocumentGuid; } + } + + #endregion + + #region Overrides of VersionableRepositoryBase + + public void RebuildXmlStructures(Func serializer, int groupSize = 200, IEnumerable contentTypeIds = null) + { + // the previous way of doing this was to run it all in one big transaction, + // and to bulk-insert groups of xml rows - which works, until the transaction + // times out - and besides, because v7 transactions are ReadCommited, it does + // not bring much safety - so this reverts to updating each record individually, + // and it may be slower in the end, but should be more resilient. + + var contentTypeIdsA = contentTypeIds == null ? new int[0] : contentTypeIds.ToArray(); + + Func translate = (bId, sql) => + { + if (contentTypeIdsA.Length > 0) + { + sql.WhereIn(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax); + } + + sql + .Where(x => x.NodeId > bId && x.Trashed == false, SqlSyntax) + .Where(x => x.Published, SqlSyntax) + .OrderBy(x => x.NodeId, SqlSyntax); + + return sql; + }; + + var baseId = 0; + + while (true) + { + // get the next group of nodes + var sqlFull = translate(baseId, GetBaseQuery(BaseQueryType.FullMultiple)); + var sqlIds = translate(baseId, GetBaseQuery(BaseQueryType.Ids)); + + var xmlItems = ProcessQuery(SqlSyntax.SelectTop(sqlFull, groupSize), new PagingSqlQuery(SqlSyntax.SelectTop(sqlIds, groupSize))) + .Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() }) + .ToList(); + + // no more nodes, break + if (xmlItems.Count == 0) break; + + foreach (var xmlItem in xmlItems) + { + try + { + // should happen in most cases, then it tries to insert, and it should work + // unless the node has been deleted, and we just report the exception + Database.InsertOrUpdate(xmlItem); + } + catch (Exception e) + { + Logger.Error("Could not rebuild XML for nodeId=" + xmlItem.NodeId, e); + } + } + baseId = xmlItems[xmlItems.Count - 1].NodeId; + } + + //now delete the items that shouldn't be there + var sqlAllIds = translate(0, GetBaseQuery(BaseQueryType.Ids)); + var allContentIds = Database.Fetch(sqlAllIds); + var docObjectType = NodeObjectTypeId; + var xmlIdsQuery = new Sql() + .Select("DISTINCT cmsContentXml.nodeId") + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId); + + if (contentTypeIdsA.Length > 0) + { + xmlIdsQuery.InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.ContentTypeId) + .WhereIn(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax); + } + + xmlIdsQuery.Where(dto => dto.NodeObjectType == docObjectType, SqlSyntax); + + var allXmlIds = Database.Fetch(xmlIdsQuery); + + var toRemove = allXmlIds.Except(allContentIds).ToArray(); + if (toRemove.Length > 0) + { + foreach (var idGroup in toRemove.InGroupsOf(2000)) + { + Database.Execute("DELETE FROM cmsContentXml WHERE nodeId IN (@ids)", new { ids = idGroup }); + } + } + + } + + public override IEnumerable GetAllVersions(int id) + { + Func translate = s => + { + return s.Where(GetBaseWhereClause(), new {Id = id}) + .OrderByDescending(x => x.VersionDate, SqlSyntax); + }; + + var sqlFull = translate(GetBaseQuery(BaseQueryType.FullMultiple)); + var sqlIds = translate(GetBaseQuery(BaseQueryType.Ids)); + + return ProcessQuery(sqlFull, new PagingSqlQuery(sqlIds), true, includeAllVersions:true); + } + + public override IContent GetByVersion(Guid versionId) + { + var sql = GetBaseQuery(BaseQueryType.FullSingle); + //TODO: cmsContentVersion.VersionId has a Unique Index constraint applied, seems silly then to also add OrderByDescending since it would be impossible to return more than one. + sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); + sql.OrderByDescending(x => x.VersionDate, SqlSyntax); + + var dto = Database.Fetch(sql).FirstOrDefault(); + + if (dto == null) + return null; + + var content = CreateContentFromDto(dto, sql); + + return content; + } + + public override void DeleteVersion(Guid versionId) + { + var sql = new Sql() + .Select("*") + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.VersionId, right => right.VersionId) + .Where(x => x.VersionId == versionId, SqlSyntax) + .Where(x => x.Newest != true, SqlSyntax); + 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; + + // note: + // there used to be a check on Database.IsNew(nodeDto) here to either Insert or Update, + // but I cannot figure out what was the point, as the node should obviously be new if + // we reach that point - removed. + + // see if there's a reserved identifier for this unique id + var sql = new Sql("SELECT id FROM umbracoNode WHERE uniqueID=@0 AND nodeObjectType=@1", nodeDto.UniqueId, Constants.ObjectTypes.IdReservationGuid); + var id = Database.ExecuteScalar(sql); + if (id > 0) + { + nodeDto.NodeId = id; + Database.Update(nodeDto); + } + else + { + Database.Insert(nodeDto); + } + + //Update with new correct path + nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); + nodeDto.ValidatePathWithException(); + 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 + 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, + VersionDate = dto.UpdateDate, + Newest = true, + NodeId = dto.NodeId, + Published = true + }; + ((Content) entity).PublishedVersionGuid = dto.VersionId; + ((Content) entity).PublishedDate = dto.UpdateDate; + } + + 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 + { + if (entity.IsPropertyDirty("UpdateDate") == false || entity.UpdateDate == default(DateTime)) + 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; + nodeDto.ValidatePathWithException(); + 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)) + { + //TODO: This perf can be improved, it could easily be UPDATE WHERE.... (one SQL call instead of many) + 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 + //TODO: This perf can be improved, it could easily be UPDATE WHERE.... (one SQL call instead of many) + 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 }); + if (contentVerDto != null) + { + 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, + VersionDate = dto.UpdateDate, + Newest = true, + NodeId = dto.NodeId, + Published = true + }; + ((Content) entity).PublishedVersionGuid = dto.VersionId; + ((Content) entity).PublishedDate = dto.UpdateDate; + } + else if (publishedStateChanged) + { + dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto + { + VersionId = default (Guid), + VersionDate = default (DateTime), + Newest = false, + NodeId = dto.NodeId, + Published = false + }; + ((Content) entity).PublishedVersionGuid = default(Guid); + ((Content) entity).PublishedDate = default (DateTime); + } + + entity.ResetDirtyProperties(); + } + + + #endregion + + #region Implementation of IContentRepository + + public IEnumerable GetByPublishedVersion(IQuery query) + { + Func, Sql> translate = t => + { + return t.Translate() + .Where(x => x.Published, SqlSyntax) + .OrderBy(x => x.Level, SqlSyntax) + .OrderBy(x => x.SortOrder, SqlSyntax); + }; + + // 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 sqlFull = GetBaseQuery(BaseQueryType.FullMultiple); + var translatorFull = new SqlTranslator(sqlFull, query); + var sqlIds = GetBaseQuery(BaseQueryType.Ids); + var translatorIds = new SqlTranslator(sqlIds, query); + + return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds)), true); + } + + public IEnumerable GetBlueprints(IQuery query) + { + Func, Sql> translate = t => t.Translate(); + + var sqlFull = GetBaseQuery(BaseQueryType.FullMultiple); + var translatorFull = new SqlTranslator(sqlFull, query); + var sqlIds = GetBaseQuery(BaseQueryType.Ids); + var translatorIds = new SqlTranslator(sqlIds, query); + + return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds)), true); + } + + /// + /// This builds the Xml document used for the XML cache + /// + /// + public XmlDocument BuildXmlCache() + { + //TODO: This is what we should do , but converting to use XDocument would be breaking unless we convert + // to XmlDocument at the end of this, but again, this would be bad for memory... though still not nearly as + // bad as what is happening before! + // We'll keep using XmlDocument for now though, but XDocument xml generation is much faster: + // https://blogs.msdn.microsoft.com/codejunkie/2008/10/08/xmldocument-vs-xelement-performance/ + // I think we already have code in here to convert XDocument to XmlDocument but in case we don't here + // it is: https://blogs.msdn.microsoft.com/marcelolr/2009/03/13/fast-way-to-convert-xmldocument-into-xdocument/ + + //// Prepare an XmlDocument with an appropriate inline DTD to match + //// the expected content + //var parent = new XElement("root", new XAttribute("id", "-1")); + //var xmlDoc = new XDocument( + // new XDocumentType("root", null, null, DocumentType.GenerateDtd()), + // parent); + + var xmlDoc = new XmlDocument(); + var doctype = xmlDoc.CreateDocumentType("root", null, null, + ApplicationContext.Current.Services.ContentTypeService.GetContentTypesDtd()); + xmlDoc.AppendChild(doctype); + var parent = xmlDoc.CreateElement("root"); + var pIdAtt = xmlDoc.CreateAttribute("id"); + pIdAtt.Value = "-1"; + parent.Attributes.Append(pIdAtt); + xmlDoc.AppendChild(parent); + + //Ensure that only nodes that have published versions are selected + var sql = string.Format(@"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsContentXml.{0}, umbracoNode.{1} from umbracoNode +inner join cmsContentXml on cmsContentXml.nodeId = umbracoNode.id and umbracoNode.nodeObjectType = @type +where umbracoNode.id in (select cmsDocument.nodeId from cmsDocument where cmsDocument.published = 1) +order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", + SqlSyntax.GetQuotedColumnName("xml"), + SqlSyntax.GetQuotedColumnName("level"), + SqlSyntax.GetQuotedColumnName("level")); + + XmlElement last = null; + + //NOTE: Query creates a reader - does not load all into memory + foreach (var row in Database.Query(sql, new { type = NodeObjectTypeId })) + { + string parentId = ((int)row.parentID).ToInvariantString(); + string xml = row.xml; + int sortOrder = row.sortOrder; + + //if the parentid is changing + if (last != null && last.GetAttribute("parentID") != parentId) + { + parent = xmlDoc.GetElementById(parentId); + if (parent == null) + { + //Need to short circuit here, if the parent is not there it means that the parent is unpublished + // and therefore the child is not published either so cannot be included in the xml cache + continue; + } + } + + var xmlDocFragment = xmlDoc.CreateDocumentFragment(); + xmlDocFragment.InnerXml = xml; + + last = (XmlElement)parent.AppendChild(xmlDocFragment); + + // fix sortOrder - see notes in UpdateSortOrder + last.Attributes["sortOrder"].Value = sortOrder.ToInvariantString(); + } + + return xmlDoc; + + } + + public XmlDocument BuildPreviewXmlCache() + { + var xmlDoc = new XmlDocument(); + var doctype = xmlDoc.CreateDocumentType("root", null, null, + ApplicationContext.Current.Services.ContentTypeService.GetContentTypesDtd()); + xmlDoc.AppendChild(doctype); + var parent = xmlDoc.CreateElement("root"); + var pIdAtt = xmlDoc.CreateAttribute("id"); + pIdAtt.Value = "-1"; + parent.Attributes.Append(pIdAtt); + xmlDoc.AppendChild(parent); + + //Ensure that only nodes that have published versions are selected + var sql = string.Format(@"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsPreviewXml.{0}, umbracoNode.{1} from umbracoNode +inner join cmsPreviewXml on cmsPreviewXml.nodeId = umbracoNode.id and umbracoNode.nodeObjectType = @type +inner join cmsDocument on cmsPreviewXml.versionId = cmsDocument.versionId and cmsDocument.newest=1 +where umbracoNode.trashed = 0 +order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", + SqlSyntax.GetQuotedColumnName("xml"), + SqlSyntax.GetQuotedColumnName("level"), + SqlSyntax.GetQuotedColumnName("level")); + + XmlElement last = null; + + //NOTE: Query creates a reader - does not load all into memory + foreach (var row in Database.Query(sql, new { type = NodeObjectTypeId })) + { + string parentId = ((int)row.parentID).ToInvariantString(); + string xml = row.xml; + int sortOrder = row.sortOrder; + + //if the parentid is changing + if (last != null && last.GetAttribute("parentID") != parentId) + { + parent = xmlDoc.GetElementById(parentId); + if (parent == null) + { + //Need to short circuit here, if the parent is not there it means that the parent is unpublished + // and therefore the child is not published either so cannot be included in the xml cache + continue; + } + } + + var xmlDocFragment = xmlDoc.CreateDocumentFragment(); + xmlDocFragment.InnerXml = xml; + + last = (XmlElement)parent.AppendChild(xmlDocFragment); + + // fix sortOrder - see notes in UpdateSortOrder + last.Attributes["sortOrder"].Value = sortOrder.ToInvariantString(); + } + + return xmlDoc; + + } + + public int CountPublished(string contentTypeAlias = null) + { + if (contentTypeAlias.IsNullOrWhiteSpace()) + { + var sql = GetBaseQuery(true).Where(x => x.Trashed == false) + .Where(x => x.Published == true); + return Database.ExecuteScalar(sql); + } + else + { + var sql = GetBaseQuery(true).Where(x => x.Trashed == false) + .Where(x => x.Published == true) + .Where(x => x.Alias == contentTypeAlias); + return Database.ExecuteScalar(sql); + } + } + + public void ReplaceContentPermissions(EntityPermissionSet permissionSet) + { + _permissionRepository.ReplaceEntityPermissions(permissionSet); + } + + public void ClearPublished(IContent content) + { + var sql = "UPDATE cmsDocument SET published=0 WHERE nodeId=@id AND published=1"; + Database.Execute(sql, new {id = content.Id}); + } + + /// + /// Assigns a single permission to the current content item for the specified user group ids + /// + /// + /// + /// + public void AssignEntityPermission(IContent entity, char permission, IEnumerable groupIds) + { + _permissionRepository.AssignEntityPermission(entity, permission, groupIds); + } + + /// + /// Gets the explicit list of permissions for the content item + /// + /// + /// + public EntityPermissionCollection GetPermissionsForEntity(int entityId) + { + return _permissionRepository.GetPermissionsForEntity(entityId); + } + + /// + /// Adds/updates content/published xml + /// + /// + /// + public void AddOrUpdateContentXml(IContent content, Func xml) + { + _contentXmlRepository.AddOrUpdate(new ContentXmlEntity(content, xml)); + } + + /// + /// Used to add/update a permission for a content item + /// + /// + public void AddOrUpdatePermissions(ContentPermissionSet permission) + { + _permissionRepository.AddOrUpdate(permission); + } + + /// + /// 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, IQuery filter = null) + { + + //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 filterSql = new Sql().Append("AND (cmsDocument.newest = 1)"); + if (filter != null) + { + foreach (var filterClause in filter.GetWhereClauses()) + { + filterSql.Append(string.Format("AND ({0})", filterClause.Item1), filterClause.Item2); + } + } + + Func> filterCallback = () => new Tuple(filterSql.SQL, filterSql.Arguments); + + return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, + new Tuple("cmsDocument", "nodeId"), + (sqlFull, pagingSqlQuery) => ProcessQuery(sqlFull, pagingSqlQuery), orderBy, orderDirection, orderBySystemField, + filterCallback); + + } + + #endregion + + #region IRecycleBinRepository members + + protected override int RecycleBinId + { + get { return Constants.System.RecycleBinContent; } + } + + #endregion + + #region Read Repository implementation for GUID keys + public IContent Get(Guid id) + { + return _contentByGuidReadRepository.Get(id); + } + + IEnumerable IReadRepository.GetAll(params Guid[] ids) + { + return _contentByGuidReadRepository.GetAll(ids); + } + + public bool Exists(Guid id) + { + return _contentByGuidReadRepository.Exists(id); + } + + /// + /// A reading repository purely for looking up by GUID + /// + /// + /// TODO: This is ugly and to fix we need to decouple the IRepositoryQueryable -> IRepository -> IReadRepository which should all be separate things! + /// Then we can do the same thing with repository instances and we wouldn't need to leave all these methods as not implemented because we wouldn't need to implement them + /// + private class ContentByGuidReadRepository : PetaPocoRepositoryBase + { + private readonly ContentRepository _outerRepo; + + public ContentByGuidReadRepository(ContentRepository outerRepo, + IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + : base(work, cache, logger, sqlSyntax) + { + _outerRepo = outerRepo; + } + + protected override IContent PerformGet(Guid id) + { + var sql = _outerRepo.GetBaseQuery(BaseQueryType.FullSingle) + .Where(GetBaseWhereClause(), new { Id = id }) + .Where(x => x.Newest, SqlSyntax) + .OrderByDescending(x => x.VersionDate, SqlSyntax); + + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); + + if (dto == null) + return null; + + var content = _outerRepo.CreateContentFromDto(dto, sql); + + return content; + } + + protected override IEnumerable PerformGetAll(params Guid[] ids) + { + Func translate = s => + { + if (ids.Any()) + { + s.Where("umbracoNode.uniqueID in (@ids)", new { ids }); + } + //we only want the newest ones with this method + s.Where(x => x.Newest, SqlSyntax); + return s; + }; + + var sqlBaseFull = _outerRepo.GetBaseQuery(BaseQueryType.FullMultiple); + var sqlBaseIds = _outerRepo.GetBaseQuery(BaseQueryType.Ids); + + return _outerRepo.ProcessQuery(translate(sqlBaseFull), new PagingSqlQuery(translate(sqlBaseIds))); + } + + protected override Sql GetBaseQuery(bool isCount) + { + return _outerRepo.GetBaseQuery(isCount); + } + + protected override string GetBaseWhereClause() + { + return "umbracoNode.uniqueID = @Id"; + } + + protected override Guid NodeObjectTypeId + { + get { return _outerRepo.NodeObjectTypeId; } + } + + #region Not needed to implement + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new NotImplementedException(); + } + protected override IEnumerable GetDeleteClauses() + { + throw new NotImplementedException(); + } + protected override void PersistNewItem(IContent entity) + { + throw new NotImplementedException(); + } + protected override void PersistUpdatedItem(IContent entity) + { + throw new NotImplementedException(); + } + #endregion + } + #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); + } + + /// + /// This is the underlying method that processes most queries for this repository + /// + /// + /// The FullMultiple SQL without the outer join to return all data required to create an IContent excluding it's published state data which this will query separately + /// + /// + /// The Id SQL without the outer join to just return all document ids - used to process the properties for the content item + /// + /// + /// + /// Generally when querying for content we only want to return the most recent version of the content item, however in some cases like when + /// we want to return all versions of a content item, we can't simply return the latest + /// + /// + private IEnumerable ProcessQuery(Sql sqlFull, PagingSqlQuery pagingSqlQuery, bool withCache = false, bool includeAllVersions = false) + { + // fetch returns a list so it's ok to iterate it in this method + var dtos = Database.Fetch(sqlFull); + if (dtos.Count == 0) return Enumerable.Empty(); + + //Go and get all of the published version data separately for this data, this is because when we are querying + //for multiple content items we don't include the outer join to fetch this data in the same query because + //it is insanely slow. Instead we just fetch the published version data separately in one query. + + //we need to parse the original SQL statement and reduce the columns to just cmsDocument.nodeId so that we can use + // the statement to go get the published data for all of the items by using an inner join + var parsedOriginalSql = "SELECT cmsDocument.nodeId " + sqlFull.SQL.Substring(sqlFull.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)); + } + + //order by update date DESC, if there is corrupted published flags we only want the latest! + var publishedSql = new Sql(@"SELECT cmsDocument.nodeId, cmsDocument.published, cmsDocument.versionId, cmsDocument.updateDate, cmsDocument.newest +FROM cmsDocument INNER JOIN cmsContentVersion ON cmsContentVersion.VersionId = cmsDocument.versionId +WHERE cmsDocument.published = 1 AND cmsDocument.nodeId IN +(" + parsedOriginalSql + @") +ORDER BY cmsContentVersion.id DESC +", sqlFull.Arguments); + + //go and get the published version data, we do a Query here and not a Fetch so we are + //not allocating a whole list to memory just to allocate another list in memory since + //we are assigning this data to a keyed collection for fast lookup below + var publishedData = Database.Query(publishedSql); + var publishedDataCollection = new DocumentPublishedReadOnlyDtoCollection(); + foreach (var publishedDto in publishedData) + { + //double check that there's no corrupt db data, there should only be a single published item + if (publishedDataCollection.Contains(publishedDto.NodeId) == false) + publishedDataCollection.Add(publishedDto); + } + + //This is a tuple list identifying if the content item came from the cache or not + var content = new List>(); + var defs = new DocumentDefinitionCollection(includeAllVersions); + var templateIds = new List(); + + //track the looked up content types, even though the content types are cached + // they still need to be deep cloned out of the cache and we don't want to add + // the overhead of deep cloning them on every item in this loop + var contentTypes = new Dictionary(); + + foreach (var dto in dtos) + { + DocumentPublishedReadOnlyDto publishedDto; + publishedDataCollection.TryGetValue(dto.NodeId, out publishedDto); + + // if the cache contains the published version, use it + if (withCache) + { + var cached = IsolatedCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); + //only use this cached version if the dto returned is also the publish version, they must match and be teh same version + if (cached != null && cached.Version == dto.VersionId && cached.Published && dto.Published) + { + content.Add(new Tuple(cached, true)); + continue; + } + } + + // else, need to fetch from the database + // content type repository is full-cache so OK to get each one independently + + IContentType contentType; + if (contentTypes.ContainsKey(dto.ContentVersionDto.ContentDto.ContentTypeId)) + { + contentType = contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId]; + } + else + { + contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); + contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId] = contentType; + } + + // track the definition and if it's successfully added or updated then processed + if (defs.AddOrUpdate(new DocumentDefinition(dto, contentType))) + { + // assign template + if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) + templateIds.Add(dto.TemplateId.Value); + + content.Add(new Tuple(ContentFactory.BuildEntity(dto, contentType, publishedDto), false)); + } + } + + // load all required templates in 1 query + var templates = _templateRepository.GetAll(templateIds.ToArray()) + .ToDictionary(x => x.Id, x => x); + + // load all properties for all documents from database in 1 query + var propertyData = GetPropertyCollection(pagingSqlQuery, defs); + + // assign template and property data + foreach (var contentItem in content) + { + var cc = contentItem.Item1; + var fromCache = contentItem.Item2; + + //if this has come from cache, we do not need to build up it's structure + if (fromCache) continue; + + var def = defs[includeAllVersions ? (ValueType)cc.Version : cc.Id]; + + ITemplate template = null; + if (def.DocumentDto.TemplateId.HasValue) + templates.TryGetValue(def.DocumentDto.TemplateId.Value, out template); // else null + cc.Template = template; + if (propertyData.ContainsKey(cc.Version)) + { + cc.Properties = propertyData[cc.Version]; + } + else + { + throw new InvalidOperationException($"No property data found for version: '{cc.Version}'."); + } + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + cc.ResetDirtyProperties(false); + } + + return content.Select(x => x.Item1).ToArray(); + } + + /// + /// Private method to create a content object from a DocumentDto, which is used by Get and GetByVersion. + /// + /// + /// + /// + private IContent CreateContentFromDto(DocumentDto dto, Sql docSql) + { + var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); + + var content = ContentFactory.BuildEntity(dto, contentType); + + //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, contentType); + + var properties = GetPropertyCollection(docSql, new[] { docDef }); + + content.Properties = properties[dto.VersionId]; + + //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 names = Database.Fetch("SELECT id, text AS name FROM umbracoNode WHERE nodeObjectType=@objectType AND parentId=@parentId", + new { objectType = NodeObjectTypeId, parentId }); + + return SimilarNodeName.GetUniqueName(names, id, nodeName); + } + + /// + /// Dispose disposable properties + /// + /// + /// Ensure the unit of work is disposed + /// + protected override void DisposeResources() + { + _contentTypeRepository.Dispose(); + _templateRepository.Dispose(); + _tagRepository.Dispose(); + _contentPreviewRepository.Dispose(); + _contentXmlRepository.Dispose(); + } + + /// + /// A keyed collection for fast lookup when retrieving a separate list of published version data + /// + private class DocumentPublishedReadOnlyDtoCollection : KeyedCollection + { + protected override int GetKeyForItem(DocumentPublishedReadOnlyDto item) + { + return item.NodeId; + } + + public bool TryGetValue(int key, out DocumentPublishedReadOnlyDto val) + { + if (Dictionary == null) + { + val = null; + return false; + } + return Dictionary.TryGetValue(key, out val); + } + } + } +} From 67f680a675e6f6bd2637e6b5aed94b8dcda3567c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 19 Jul 2019 11:24:01 +0200 Subject: [PATCH 172/776] Revert "https://github.com/umbraco/Umbraco-CMS/issues/5921 - AB#1794 - Remove trashed nodes from sql when building xml for previews" This reverts commit 47c3e3a7 --- src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index a0b211b6b2..f559b91ba5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -851,7 +851,6 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", var sql = string.Format(@"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsPreviewXml.{0}, umbracoNode.{1} from umbracoNode inner join cmsPreviewXml on cmsPreviewXml.nodeId = umbracoNode.id and umbracoNode.nodeObjectType = @type inner join cmsDocument on cmsPreviewXml.versionId = cmsDocument.versionId and cmsDocument.newest=1 -where umbracoNode.trashed = 0 order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", SqlSyntax.GetQuotedColumnName("xml"), SqlSyntax.GetQuotedColumnName("level"), @@ -1185,7 +1184,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", //order by update date DESC, if there is corrupted published flags we only want the latest! var publishedSql = new Sql(@"SELECT cmsDocument.nodeId, cmsDocument.published, cmsDocument.versionId, cmsDocument.updateDate, cmsDocument.newest FROM cmsDocument INNER JOIN cmsContentVersion ON cmsContentVersion.VersionId = cmsDocument.versionId -WHERE cmsDocument.published = 1 AND cmsDocument.nodeId IN +WHERE cmsDocument.published = 1 AND cmsDocument.nodeId IN (" + parsedOriginalSql + @") ORDER BY cmsContentVersion.id DESC ", sqlFull.Arguments); From cd27bb210f24f1b37cbca34547a52d1aa46031cb Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 19 Jul 2019 11:25:33 +0200 Subject: [PATCH 173/776] https://github.com/umbraco/Umbraco-CMS/issues/5921 - AB#1794 - Remove trashed nodes from sql when building xml for previews --- src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index f559b91ba5..a0b211b6b2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -851,6 +851,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", var sql = string.Format(@"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsPreviewXml.{0}, umbracoNode.{1} from umbracoNode inner join cmsPreviewXml on cmsPreviewXml.nodeId = umbracoNode.id and umbracoNode.nodeObjectType = @type inner join cmsDocument on cmsPreviewXml.versionId = cmsDocument.versionId and cmsDocument.newest=1 +where umbracoNode.trashed = 0 order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", SqlSyntax.GetQuotedColumnName("xml"), SqlSyntax.GetQuotedColumnName("level"), @@ -1184,7 +1185,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", //order by update date DESC, if there is corrupted published flags we only want the latest! var publishedSql = new Sql(@"SELECT cmsDocument.nodeId, cmsDocument.published, cmsDocument.versionId, cmsDocument.updateDate, cmsDocument.newest FROM cmsDocument INNER JOIN cmsContentVersion ON cmsContentVersion.VersionId = cmsDocument.versionId -WHERE cmsDocument.published = 1 AND cmsDocument.nodeId IN +WHERE cmsDocument.published = 1 AND cmsDocument.nodeId IN (" + parsedOriginalSql + @") ORDER BY cmsContentVersion.id DESC ", sqlFull.Arguments); From 968463912ab548808ff87bc58760b2b2bc7bb45e Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Sun, 21 Jul 2019 14:47:37 +0100 Subject: [PATCH 174/776] Include CheckBoxList in ValueListPreValueMigrator --- .../Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs index 7249ebd6ec..07fefc8e85 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs @@ -9,6 +9,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes private readonly string[] _editors = { "Umbraco.RadioButtonList", + "Umbraco.CheckBoxList", "Umbraco.DropDown", "Umbraco.DropdownlistPublishingKeys", "Umbraco.DropDownMultiple", From 0320a56cb56606bd468cdda87c24358c6e332834 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 22 Jul 2019 13:27:51 +1000 Subject: [PATCH 175/776] Fixes up dataTypeId var declarations --- .../common/dialogs/linkpicker.controller.js | 6 +- .../linkpicker/linkpicker.controller.js | 9 +- .../mediaPicker/mediapicker.controller.js | 14 +--- .../treepicker/treepicker.controller.js | 6 +- .../contentpicker/contentpicker.controller.js | 84 +++++++++---------- .../grid/editors/media.controller.js | 14 ++-- .../grid/editors/rte.controller.js | 13 +-- .../mediapicker/mediapicker.controller.js | 8 +- .../multiurlpicker.controller.js | 7 +- .../relatedlinks/relatedlinks.controller.js | 14 +--- .../propertyeditors/rte/rte.controller.js | 14 +--- 11 files changed, 66 insertions(+), 123 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js index 876a9f9426..8cf7f937a3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/linkpicker.controller.js @@ -8,17 +8,13 @@ angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController", searchText = value + "..."; }); - var dataTypeId = null; - if($scope.model && $scope.model.dataTypeId) { - dataTypeId = $scope.model.dataTypeId; - } $scope.dialogTreeEventHandler = $({}); $scope.target = {}; $scope.searchInfo = { searchFromId: null, searchFromName: null, showSearch: false, - dataTypeId: dataTypeId, + dataTypeId: ($scope.model && $scope.model.dataTypeId) ? $scope.model.dataTypeId : null, results: [], selectedSearchResults: [] } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js index 0c5641ba0a..f1241c1976 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js @@ -17,7 +17,6 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", dataTypeId = dialogOptions.dataTypeId; } - $scope.dialogTreeEventHandler = $({}); $scope.model.target = {}; $scope.searchInfo = { @@ -28,7 +27,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", results: [], selectedSearchResults: [] }; - $scope.customTreeParams = dialogOptions.dataTypeId ? "dataTypeId=" + dialogOptions.dataTypeId : ""; + $scope.customTreeParams = dataTypeId !== null ? "dataTypeId=" + dataTypeId : ""; $scope.showTarget = $scope.model.hideTarget !== true; if (dialogOptions.currentTarget) { @@ -121,11 +120,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController", startNodeId = -1; startNodeIsVirtual = true; } - var dataTypeId = null; - if(dialogOptions && dialogOptions.dataTypeId){ - dataTypeId = dialogOptions.dataTypeId; - } - + $scope.mediaPickerOverlay = { view: "mediapicker", startNodeId: startNodeId, 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 61f9619a52..bae4882a3e 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 @@ -54,7 +54,7 @@ angular.module("umbraco") totalItems: 0, totalPages: 0, filter: '', - dataTypeId: dataTypeId + dataTypeId: dataTypeId, }; //preload selected item @@ -161,11 +161,7 @@ angular.module("umbraco") } if (folder.id > 0) { - var dataTypeId = null; - if($scope.model && $scope.model.dataTypeId) { - dataTypeId = $scope.model.dataTypeId; - } - + entityResource.getAncestors(folder.id, "media", { dataTypeId: dataTypeId }) .then(function (anc) { $scope.path = _.filter(anc, @@ -318,11 +314,7 @@ angular.module("umbraco") if ($scope.searchOptions.filter) { searchMedia(); } else { - var dataTypeId = null; - if($scope.model && $scope.model.dataTypeId) { - dataTypeId = $scope.model.dataTypeId; - } - + // reset pagination $scope.searchOptions = { pageNumber: 1, diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js index 851d270789..2d5fb132b7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js @@ -5,10 +5,6 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", var tree = null; var dialogOptions = $scope.model; - var dataTypeId = null; - if(dialogOptions && dialogOptions.dataTypeId){ - dataTypeId = dialogOptions.dataTypeId; - } $scope.treeReady = false; $scope.dialogTreeEventHandler = $({}); $scope.section = dialogOptions.section; @@ -21,7 +17,7 @@ angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController", searchFromId: dialogOptions.startNodeId, searchFromName: null, showSearch: false, - dataTypeId: dataTypeId, + dataTypeId: (dialogOptions && dialogOptions.dataTypeId) ? dialogOptions.dataTypeId : null, results: [], selectedSearchResults: [] } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 37a372a5b1..61e841d1af 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -68,11 +68,11 @@ function contentPickerController($scope, entityResource, editorState, iconHelper showPathOnHover: false, dataTypeId: null, maxNumber: 1, - minNumber : 0, + minNumber: 0, startNode: { query: "", type: "content", - id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1 // get start node for simple Content Picker + id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1 // get start node for simple Content Picker } }; @@ -104,8 +104,8 @@ function contentPickerController($scope, entityResource, editorState, iconHelper var entityType = $scope.model.config.startNode.type === "member" ? "Member" : $scope.model.config.startNode.type === "media" - ? "Media" - : "Document"; + ? "Media" + : "Document"; $scope.allowOpenButton = entityType === "Document"; $scope.allowEditButton = entityType === "Document"; $scope.allowRemoveButton = true; @@ -144,7 +144,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper dialogOptions.filterCssClass = "not-allowed"; var currFilter = dialogOptions.filter; //now change the filter to be a method - dialogOptions.filter = function(i) { + dialogOptions.filter = function (i) { //filter out the list view nodes if (i.metaData.isContainer) { return true; @@ -179,34 +179,30 @@ function contentPickerController($scope, entityResource, editorState, iconHelper } //dialog - $scope.openContentPicker = function() { - var dataTypeId = null; - if($scope.model && $scope.model.dataTypeId) { - dataTypeId = $scope.model.dataTypeId; + $scope.openContentPicker = function () { + + $scope.contentPickerOverlay = dialogOptions; + $scope.contentPickerOverlay.view = "treepicker"; + $scope.contentPickerOverlay.show = true; + $scope.contentPickerOverlay.dataTypeId = ($scope.model && $scope.model.dataTypeId) ? $scope.model.dataTypeId : null; + + $scope.contentPickerOverlay.submit = function (model) { + + if (angular.isArray(model.selection)) { + _.each(model.selection, function (item, i) { + $scope.add(item); + }); + angularHelper.getCurrentForm($scope).$setDirty(); + } + + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; } - $scope.contentPickerOverlay = dialogOptions; - $scope.contentPickerOverlay.view = "treepicker"; - $scope.contentPickerOverlay.show = true; - $scope.contentPickerOverlay.dataTypeId = dataTypeId; - - $scope.contentPickerOverlay.submit = function(model) { - - if (angular.isArray(model.selection)) { - _.each(model.selection, function (item, i) { - $scope.add(item); - }); - angularHelper.getCurrentForm($scope).$setDirty(); - } - - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - } - - $scope.contentPickerOverlay.close = function(oldModel) { - $scope.contentPickerOverlay.show = false; - $scope.contentPickerOverlay = null; - } + $scope.contentPickerOverlay.close = function (oldModel) { + $scope.contentPickerOverlay.show = false; + $scope.contentPickerOverlay = null; + } }; @@ -246,13 +242,13 @@ function contentPickerController($scope, entityResource, editorState, iconHelper $scope.renderModel = []; }; - $scope.openMiniEditor = function(node) { - miniEditorHelper.launchMiniEditor(node).then(function(updatedNode){ + $scope.openMiniEditor = function (node) { + miniEditorHelper.launchMiniEditor(node).then(function (updatedNode) { // update the node node.name = updatedNode.name; node.published = updatedNode.hasPublishedVersion; - if(entityType !== "Member") { - entityResource.getUrl(updatedNode.id, entityType).then(function(data){ + if (entityType !== "Member") { + entityResource.getUrl(updatedNode.id, entityType).then(function (data) { node.url = data; }); } @@ -261,7 +257,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper //when the scope is destroyed we need to unsubscribe $scope.$on('$destroy', function () { - if(unsubscribe) { + if (unsubscribe) { unsubscribe(); } }); @@ -270,12 +266,12 @@ function contentPickerController($scope, entityResource, editorState, iconHelper //load current data if anything selected if (modelIds.length > 0) { - entityResource.getByIds(modelIds, entityType).then(function(data) { + entityResource.getByIds(modelIds, entityType).then(function (data) { _.each(modelIds, - function(id, i) { + function (id, i) { var entity = _.find(data, - function(d) { + function (d) { return $scope.model.config.idType === "udi" ? (d.udi == id) : (d.id == id); }); @@ -299,10 +295,10 @@ function contentPickerController($scope, entityResource, editorState, iconHelper function setEntityUrl(entity) { // get url for content and media items - if(entityType !== "Member") { - entityResource.getUrl(entity.id, entityType).then(function(data){ + if (entityType !== "Member") { + entityResource.getUrl(entity.id, entityType).then(function (data) { // update url - angular.forEach($scope.renderModel, function(item){ + angular.forEach($scope.renderModel, function (item) { if (item.id === entity.id) { if (entity.trashed) { item.url = localizationService.dictionary.general_recycleBin; @@ -324,7 +320,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper function addSelectedItem(item) { // set icon - if(item.icon) { + if (item.icon) { item.icon = iconHelper.convertFromLegacyIcon(item.icon); } @@ -359,7 +355,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper function setSortingState(items) { // disable sorting if the list only consist of one item - if(items.length > 1) { + if (items.length > 1) { $scope.sortableOptions.disabled = false; } else { $scope.sortableOptions.disabled = true; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js index e9c095c1b9..3d0d568866 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js @@ -16,17 +16,13 @@ angular.module("umbraco") } $scope.setImage = function(){ - var dataTypeId = null; - if($scope.model && $scope.model.dataTypeId) { - dataTypeId = $scope.model.dataTypeId; - } - + $scope.mediaPickerOverlay = {}; $scope.mediaPickerOverlay.view = "mediapicker"; - $scope.mediaPickerOverlay.startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined; - $scope.mediaPickerOverlay.startNodeIsVirtual = $scope.mediaPickerOverlay.startNodeId ? $scope.model.config.startNodeIsVirtual : undefined; - $scope.mediaPickerOverlay.dataTypeId = dataTypeId; - $scope.mediaPickerOverlay.cropSize = $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined; + $scope.mediaPickerOverlay.startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : null; + $scope.mediaPickerOverlay.startNodeIsVirtual = $scope.mediaPickerOverlay.startNodeId ? $scope.model.config.startNodeIsVirtual : null; + $scope.mediaPickerOverlay.dataTypeId = ($scope.model && $scope.model.dataTypeId) ? $scope.model.dataTypeId : null; + $scope.mediaPickerOverlay.cropSize = $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : null; $scope.mediaPickerOverlay.showDetails = true; $scope.mediaPickerOverlay.disableFolderSelect = true; $scope.mediaPickerOverlay.onlyImages = true; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js index 580d231de8..94f2c83cbf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js @@ -10,14 +10,12 @@ vm.openMacroPicker = openMacroPicker; vm.openEmbed = openEmbed; + const dataTypeId = ($scope.model && $scope.model.dataTypeId) ? $scope.model.dataTypeId : null; + function openLinkPicker(editor, currentTarget, anchorElement) { entityResource.getAnchors(JSON.stringify($scope.model.value)).then(function(anchorValues) { - var dataTypeId = null; - if($scope.model && $scope.model.dataTypeId) { - dataTypeId = $scope.model.dataTypeId; - } - + vm.linkPickerOverlay = { view: "linkpicker", currentTarget: currentTarget, @@ -45,11 +43,6 @@ startNodeIsVirtual = true; } - var dataTypeId = null; - if($scope.model && $scope.model.dataTypeId) { - dataTypeId = $scope.model.dataTypeId; - } - vm.mediaPickerOverlay = { currentTarget: currentTarget, onlyImages: true, 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 63beddb49c..8aafba8ca1 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 @@ -107,17 +107,13 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl }; $scope.add = function() { - var dataTypeId = null; - if($scope.model && $scope.model.dataTypeId) { - dataTypeId = $scope.model.dataTypeId; - } - + $scope.mediaPickerOverlay = { view: "mediapicker", title: "Select media", startNodeId: $scope.model.config.startNodeId, startNodeIsVirtual: $scope.model.config.startNodeIsVirtual, - dataTypeId: dataTypeId, + dataTypeId: ($scope.model && $scope.model.dataTypeId) ? $scope.model.dataTypeId : null, multiPicker: multiPicker, onlyImages: onlyImages, disableFolderSelect: disableFolderSelect, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js index cab124ad23..a5fc5c50d2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js @@ -68,15 +68,10 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en target: link.target } : null; - var dataTypeId = null; - if($scope.model && $scope.model.dataTypeId) { - dataTypeId = $scope.model.dataTypeId; - } - $scope.linkPickerOverlay = { view: "linkpicker", currentTarget: target, - dataTypeId: dataTypeId, + dataTypeId: ($scope.model && $scope.model.dataTypeId) ? $scope.model.dataTypeId : null, ignoreUserStartNodes : $scope.model.config.ignoreUserStartNodes, show: true, submit: function (model) { 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 fa92442eac..78adf2fee5 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 @@ -18,12 +18,10 @@ $scope.currentEditLink = null; $scope.hasError = false; - $scope.internal = function($event) { - var dataTypeId = null; - if($scope.model && $scope.model.dataTypeId) { - dataTypeId = $scope.model.dataTypeId; - } + const dataTypeId = ($scope.model && $scope.model.dataTypeId) ? $scope.model.dataTypeId : null; + $scope.internal = function($event) { + $scope.currentEditLink = null; $scope.contentPickerOverlay = {}; @@ -50,11 +48,7 @@ }; $scope.selectInternal = function ($event, link) { - var dataTypeId = null; - if($scope.model && $scope.model.dataTypeId) { - dataTypeId = $scope.model.dataTypeId; - } - + $scope.currentEditLink = link; $scope.contentPickerOverlay = {}; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index 2b3ee930d9..d92319a3fc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -52,6 +52,8 @@ angular.module("umbraco") editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize; } + const dataTypeId = ($scope.model && $scope.model.dataTypeId) ? $scope.model.dataTypeId : null; + //queue file loading if (typeof tinymce === "undefined") { // Don't reload tinymce if already loaded await.push(assetsService.loadJs("lib/tinymce/tinymce.min.js", $scope)); @@ -272,11 +274,7 @@ angular.module("umbraco") tinyMceService.createLinkPicker(editor, $scope, function(currentTarget, anchorElement) { entityResource.getAnchors($scope.model.value).then(function(anchorValues){ - var dataTypeId = null; - if($scope.model && $scope.model.dataTypeId) { - dataTypeId = $scope.model.dataTypeId; - } - + $scope.linkPickerOverlay = { view: "linkpicker", currentTarget: currentTarget, @@ -305,11 +303,7 @@ angular.module("umbraco") startNodeId = -1; startNodeIsVirtual = true; } - var dataTypeId = null; - if($scope.model && $scope.model.dataTypeId) { - dataTypeId = $scope.model.dataTypeId; - } - + $scope.mediaPickerOverlay = { currentTarget: currentTarget, onlyImages: true, From 6148ce4e52e66ca7e4edbba8228b067cd09ff112 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 22 Jul 2019 13:41:26 +1000 Subject: [PATCH 176/776] oops no es6 support in 7.x --- .../views/common/overlays/mediaPicker/mediapicker.controller.js | 2 +- .../src/views/propertyeditors/grid/editors/rte.controller.js | 2 +- .../propertyeditors/relatedlinks/relatedlinks.controller.js | 2 +- .../src/views/propertyeditors/rte/rte.controller.js | 2 +- 4 files changed, 4 insertions(+), 4 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 bae4882a3e..5f76528f5d 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 @@ -54,7 +54,7 @@ angular.module("umbraco") totalItems: 0, totalPages: 0, filter: '', - dataTypeId: dataTypeId, + dataTypeId: dataTypeId }; //preload selected item diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js index 94f2c83cbf..e36e398024 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js @@ -10,7 +10,7 @@ vm.openMacroPicker = openMacroPicker; vm.openEmbed = openEmbed; - const dataTypeId = ($scope.model && $scope.model.dataTypeId) ? $scope.model.dataTypeId : null; + var dataTypeId = ($scope.model && $scope.model.dataTypeId) ? $scope.model.dataTypeId : null; function openLinkPicker(editor, currentTarget, anchorElement) { 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 78adf2fee5..392e0dc0cd 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 @@ -18,7 +18,7 @@ $scope.currentEditLink = null; $scope.hasError = false; - const dataTypeId = ($scope.model && $scope.model.dataTypeId) ? $scope.model.dataTypeId : null; + var dataTypeId = ($scope.model && $scope.model.dataTypeId) ? $scope.model.dataTypeId : null; $scope.internal = function($event) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index d92319a3fc..109aa37fbb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -52,7 +52,7 @@ angular.module("umbraco") editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize; } - const dataTypeId = ($scope.model && $scope.model.dataTypeId) ? $scope.model.dataTypeId : null; + var dataTypeId = ($scope.model && $scope.model.dataTypeId) ? $scope.model.dataTypeId : null; //queue file loading if (typeof tinymce === "undefined") { // Don't reload tinymce if already loaded From 5126521d37fcc1050a5da44f667560e29752d412 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 12 Jul 2019 13:15:41 +1000 Subject: [PATCH 177/776] Fixes #5789 --- src/Umbraco.Web/IPublishedContentQuery.cs | 8 ++++---- src/Umbraco.Web/PublishedContentQuery.cs | 25 +++++++++++++++-------- src/Umbraco.Web/UmbracoContextFactory.cs | 8 ++++++++ 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web/IPublishedContentQuery.cs b/src/Umbraco.Web/IPublishedContentQuery.cs index 76e7be5e97..35db121a60 100644 --- a/src/Umbraco.Web/IPublishedContentQuery.cs +++ b/src/Umbraco.Web/IPublishedContentQuery.cs @@ -39,10 +39,10 @@ namespace Umbraco.Web /// Optional culture. /// Optional index name. /// - /// When the is not specified, all cultures are searched. + /// When the is not specified or is *, all cultures are searched. To search only invariant use null. /// While enumerating results, the ambient culture is changed to be the searched culture. /// - IEnumerable Search(string term, string culture = null, string indexName = null); + IEnumerable Search(string term, string culture = "*", string indexName = null); /// /// Searches content. @@ -54,10 +54,10 @@ namespace Umbraco.Web /// Optional culture. /// Optional index name. /// - /// When the is not specified, all cultures are searched. + /// When the is not specified or is *, all cultures are searched. To search only invariant use null. /// While enumerating results, the ambient culture is changed to be the searched culture. /// - IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = null, string indexName = null); + IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = null); /// /// Executes the query and converts the results to PublishedSearchResult. diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 61180580cb..2772cc94f6 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -175,13 +175,13 @@ namespace Umbraco.Web #region Search /// - public IEnumerable Search(string term, string culture = null, string indexName = null) + public IEnumerable Search(string term, string culture = "*", string indexName = null) { return Search(term, 0, 0, out _, culture, indexName); } /// - public IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = null, string indexName = null) + public IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = null) { indexName = string.IsNullOrEmpty(indexName) ? Constants.UmbracoIndexes.ExternalIndexName @@ -195,20 +195,29 @@ namespace Umbraco.Web // default to max 500 results var count = skip == 0 && take == 0 ? 500 : skip + take; - //set this to the specific culture or to the culture in the request - culture = culture ?? _variationContextAccessor.VariationContext.Culture; - ISearchResults results; - if (culture.IsNullOrWhiteSpace()) + if (culture == "*") { + //search everything + results = searcher.Search(term, count); } + else if (culture.IsNullOrWhiteSpace()) + { + //only search invariant + + var qry = searcher.CreateQuery().Field(UmbracoContentIndex.VariesByCultureFieldName, "n"); //must not vary by culture + qry = qry.And().ManagedQuery(term); + results = qry.Execute(count); + } else { + //search only the specified culture + //get all index fields suffixed with the culture name supplied - var cultureFields = umbIndex.GetCultureFields(culture); + var cultureFields = umbIndex.GetCultureFields(culture).ToArray(); var qry = searcher.CreateQuery().Field(UmbracoContentIndex.VariesByCultureFieldName, "y"); //must vary by culture - qry = qry.And().ManagedQuery(term, cultureFields.ToArray()); + qry = qry.And().ManagedQuery(term, cultureFields); results = qry.Execute(count); } diff --git a/src/Umbraco.Web/UmbracoContextFactory.cs b/src/Umbraco.Web/UmbracoContextFactory.cs index 2a812036bf..11d8952fa6 100644 --- a/src/Umbraco.Web/UmbracoContextFactory.cs +++ b/src/Umbraco.Web/UmbracoContextFactory.cs @@ -53,7 +53,15 @@ namespace Umbraco.Web { // make sure we have a variation context if (_variationContextAccessor.VariationContext == null) + { + // TODO: By using _defaultCultureAccessor.DefaultCulture this means that the VariationContext will always return a variant culture, it will never + // return an empty string signifying that the culture is invariant. But does this matter? Are we actually expecting this to return an empty string + // for invariant routes? From what i can tell throughout the codebase is that whenever we are checking against the VariationContext.Culture we are + // also checking if the content type varies by culture or not. This is fine, however the code in the ctor of VariationContext is then misleading + // since it's assuming that the Culture can be empty (invariant) when in reality of a website this will never be empty since a real culture is always set here. _variationContextAccessor.VariationContext = new VariationContext(_defaultCultureAccessor.DefaultCulture); + } + var webSecurity = new WebSecurity(httpContext, _userService, _globalSettings); From dc371c1979e5d3df409da4e4fae229d52944ae72 Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Sun, 21 Jul 2019 15:47:37 +0200 Subject: [PATCH 178/776] Include CheckBoxList in ValueListPreValueMigrator --- .../Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs index 7249ebd6ec..07fefc8e85 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs @@ -9,6 +9,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes private readonly string[] _editors = { "Umbraco.RadioButtonList", + "Umbraco.CheckBoxList", "Umbraco.DropDown", "Umbraco.DropdownlistPublishingKeys", "Umbraco.DropDownMultiple", From 1c6bf55e5d3a65dd63ba5dcbd05bffc69a89f537 Mon Sep 17 00:00:00 2001 From: Frans de Jong Date: Fri, 19 Jul 2019 14:42:44 +0200 Subject: [PATCH 179/776] GetCurrentLoginStatus() In the summary of MembershipHelper.GetCurrentLoginStatus() it states that null will be returned when no member is logged in but that isn't the case (anymore?). --- src/Umbraco.Web/Security/MembershipHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index cdf696c520..f74897d565 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -542,7 +542,7 @@ namespace Umbraco.Web.Security } /// - /// Returns the login status model of the currently logged in member, if no member is logged in it returns null; + /// Returns the login status model of the currently logged in member. /// /// public virtual LoginStatusModel GetCurrentLoginStatus() From a8ed7f2c17512f36ef883a9805603488df6c5b42 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 23 Jul 2019 22:11:02 +1000 Subject: [PATCH 180/776] adds new method --- src/Umbraco.Examine/ExamineExtensions.cs | 25 ++++++++++++++++++++++++ src/Umbraco.Web/PublishedContentQuery.cs | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Examine/ExamineExtensions.cs b/src/Umbraco.Examine/ExamineExtensions.cs index 1b8033c458..d97278f31c 100644 --- a/src/Umbraco.Examine/ExamineExtensions.cs +++ b/src/Umbraco.Examine/ExamineExtensions.cs @@ -48,6 +48,31 @@ namespace Umbraco.Examine } } + /// + /// Returns all index fields that are culture specific (suffixed) or invariant + /// + /// + /// + /// + public static IEnumerable GetCultureAndInvariantFields(this IUmbracoIndex index, string culture) + { + var allFields = index.GetFields(); + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var field in allFields) + { + var match = CultureIsoCodeFieldNameMatchExpression.Match(field); + if (match.Success && match.Groups.Count == 3 && culture.InvariantEquals(match.Groups[2].Value)) + { + yield return field; //matches this culture field + } + else if (!match.Success) + { + yield return field; //matches no culture field (invariant) + } + + } + } + internal static bool TryParseLuceneQuery(string query) { // TODO: I'd assume there would be a more strict way to parse the query but not that i can find yet, for now we'll diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 2772cc94f6..19e303602d 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -215,7 +215,7 @@ namespace Umbraco.Web //search only the specified culture //get all index fields suffixed with the culture name supplied - var cultureFields = umbIndex.GetCultureFields(culture).ToArray(); + var cultureFields = umbIndex.GetCultureAndInvariantFields(culture).ToArray(); var qry = searcher.CreateQuery().Field(UmbracoContentIndex.VariesByCultureFieldName, "y"); //must vary by culture qry = qry.And().ManagedQuery(term, cultureFields); results = qry.Execute(count); From 2f8979bbc3975dea34d1cfd8c60a0ebb90b0896b Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 24 Jul 2019 17:29:00 +1000 Subject: [PATCH 181/776] Update src/Umbraco.Web/PublishedContentQuery.cs Co-Authored-By: Bjarke Berg --- src/Umbraco.Web/PublishedContentQuery.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 19e303602d..5af5837495 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -216,7 +216,7 @@ namespace Umbraco.Web //get all index fields suffixed with the culture name supplied var cultureFields = umbIndex.GetCultureAndInvariantFields(culture).ToArray(); - var qry = searcher.CreateQuery().Field(UmbracoContentIndex.VariesByCultureFieldName, "y"); //must vary by culture + var qry = searcher.CreateQuery(); qry = qry.And().ManagedQuery(term, cultureFields); results = qry.Execute(count); } From beb8c7ac7c1076eabc132e3eb824e8ea8c6cd667 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 24 Jul 2019 17:30:29 +1000 Subject: [PATCH 182/776] Update src/Umbraco.Web/PublishedContentQuery.cs Co-Authored-By: Bjarke Berg --- src/Umbraco.Web/PublishedContentQuery.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 5af5837495..cfdd31f138 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -217,7 +217,7 @@ namespace Umbraco.Web //get all index fields suffixed with the culture name supplied var cultureFields = umbIndex.GetCultureAndInvariantFields(culture).ToArray(); var qry = searcher.CreateQuery(); - qry = qry.And().ManagedQuery(term, cultureFields); + qry = qry.ManagedQuery(term, cultureFields); results = qry.Execute(count); } From 210e43fcb084c977a514e92f0f4376442ea85bb3 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 24 Jul 2019 09:53:51 +0200 Subject: [PATCH 183/776] Fix for build --- src/Umbraco.Web/PublishedContentQuery.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index cfdd31f138..887368a3e9 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -216,8 +216,7 @@ namespace Umbraco.Web //get all index fields suffixed with the culture name supplied var cultureFields = umbIndex.GetCultureAndInvariantFields(culture).ToArray(); - var qry = searcher.CreateQuery(); - qry = qry.ManagedQuery(term, cultureFields); + var qry = searcher.CreateQuery().ManagedQuery(term, cultureFields); results = qry.Execute(count); } @@ -313,7 +312,7 @@ namespace Umbraco.Web } } - + #endregion From 8a43e3a87ec28b997878e2433e464b07680bf5e2 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 12 Jul 2019 19:20:15 +0200 Subject: [PATCH 184/776] Make sure save options are up to date with the content state --- .../common/directives/components/content/edit.controller.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index a548820138..58a22ac74e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -94,6 +94,10 @@ content.apps[0].active = true; $scope.appChanged(content.apps[0]); } + // otherwise make sure the save options are up to date with the current content state + else { + createButtons($scope.content); + } editorState.set(content); From 2c795662d2d5493039eda2a85120c2e3d39b3758 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 24 Jul 2019 18:46:14 +1000 Subject: [PATCH 185/776] Adds tests for PublishedContentQuery search --- .../TestHelpers/RandomIdRamDirectory.cs | 22 +++ src/Umbraco.Tests/Umbraco.Tests.csproj | 2 + .../Web/PublishedContentQueryTests.cs | 157 ++++++++++++++++++ src/Umbraco.Tests/Web/UmbracoHelperTests.cs | 10 +- src/Umbraco.Web/IPublishedContentQuery.cs | 12 +- src/Umbraco.Web/PublishedContentQuery.cs | 17 +- 6 files changed, 210 insertions(+), 10 deletions(-) create mode 100644 src/Umbraco.Tests/TestHelpers/RandomIdRamDirectory.cs create mode 100644 src/Umbraco.Tests/Web/PublishedContentQueryTests.cs diff --git a/src/Umbraco.Tests/TestHelpers/RandomIdRamDirectory.cs b/src/Umbraco.Tests/TestHelpers/RandomIdRamDirectory.cs new file mode 100644 index 0000000000..34904db1ae --- /dev/null +++ b/src/Umbraco.Tests/TestHelpers/RandomIdRamDirectory.cs @@ -0,0 +1,22 @@ +using Lucene.Net.Store; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Tests.TestHelpers +{ + + /// + /// Used for tests with Lucene so that each RAM directory is unique + /// + public class RandomIdRAMDirectory : RAMDirectory + { + private readonly string _lockId = Guid.NewGuid().ToString(); + public override string GetLockId() + { + return _lockId; + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index f41ff1dd07..717006b702 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -157,6 +157,7 @@ + @@ -268,6 +269,7 @@ + diff --git a/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs b/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs new file mode 100644 index 0000000000..b2a2741bcf --- /dev/null +++ b/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Examine; +using Examine.LuceneEngine.Providers; +using Lucene.Net.Store; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Examine; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web; +using Umbraco.Web.PublishedCache; + +namespace Umbraco.Tests.Web +{ + [TestFixture] + public class PublishedContentQueryTests + { + + private class TestIndex : LuceneIndex, IUmbracoIndex + { + private readonly string[] _fieldNames; + + public TestIndex(string name, Directory luceneDirectory, string[] fieldNames) + : base(name, luceneDirectory, null, null, null, null) + { + _fieldNames = fieldNames; + } + public bool EnableDefaultEventHandler => throw new NotImplementedException(); + public bool PublishedValuesOnly => throw new NotImplementedException(); + public IEnumerable GetFields() => _fieldNames; + } + + private TestIndex CreateTestIndex(Directory luceneDirectory, string[] fieldNames) + { + var indexer = new TestIndex("TestIndex", luceneDirectory, fieldNames); + + //populate with some test data + indexer.IndexItem(new ValueSet("1", "content", new Dictionary + { + [fieldNames[0]] = "Hello world, there are products here", + [UmbracoContentIndex.VariesByCultureFieldName] = "n" + })); + indexer.IndexItem(new ValueSet("2", "content", new Dictionary + { + [fieldNames[1]] = "Hello world, there are products here", + [UmbracoContentIndex.VariesByCultureFieldName] = "y" + })); + indexer.IndexItem(new ValueSet("3", "content", new Dictionary + { + [fieldNames[2]] = "Hello world, there are products here", + [UmbracoContentIndex.VariesByCultureFieldName] = "y" + })); + return indexer; + } + + private PublishedContentQuery CreatePublishedContentQuery(IIndex indexer) + { + var examineManager = new Mock(); + IIndex outarg = indexer; + examineManager.Setup(x => x.TryGetIndex("TestIndex", out outarg)).Returns(true); + + var contentCache = new Mock(); + contentCache.Setup(x => x.GetById(It.IsAny())).Returns((int intId) => Mock.Of(x => x.Id == intId)); + var snapshot = Mock.Of(x => x.Content == contentCache.Object); + var variationContext = new VariationContext(); + var variationContextAccessor = Mock.Of(x => x.VariationContext == variationContext); + + return new PublishedContentQuery(snapshot, variationContextAccessor, examineManager.Object); + } + + [Test] + public void Search_Wildcard() + { + using (var luceneDir = new RandomIdRAMDirectory()) + { + var fieldNames = new[] { "title", "title_en-us", "title_fr-fr" }; + using (var indexer = CreateTestIndex(luceneDir, fieldNames)) + { + var pcq = CreatePublishedContentQuery(indexer); + + var results = pcq.Search("Products", "*", "TestIndex"); + + var ids = results.Select(x => x.Content.Id).ToList(); + Assert.AreEqual(3, ids.Count); + + //returns results for all fields and document types + Assert.IsTrue(ids.Contains(1) && ids.Contains(2) && ids.Contains(3)); + } + } + } + + [Test] + public void Search_Invariant() + { + using (var luceneDir = new RandomIdRAMDirectory()) + { + var fieldNames = new[] { "title", "title_en-us", "title_fr-fr" }; + using (var indexer = CreateTestIndex(luceneDir, fieldNames)) + { + var pcq = CreatePublishedContentQuery(indexer); + + var results = pcq.Search("Products", null, "TestIndex"); + + var ids = results.Select(x => x.Content.Id).ToList(); + Assert.AreEqual(1, ids.Count); + + //returns results for only invariant fields and invariant documents + Assert.IsTrue(ids.Contains(1) && !ids.Contains(2) && !ids.Contains(3)); + } + } + } + + [Test] + public void Search_Culture1() + { + using (var luceneDir = new RandomIdRAMDirectory()) + { + var fieldNames = new[] { "title", "title_en-us", "title_fr-fr" }; + using (var indexer = CreateTestIndex(luceneDir, fieldNames)) + { + var pcq = CreatePublishedContentQuery(indexer); + + var results = pcq.Search("Products", "en-us", "TestIndex"); + + var ids = results.Select(x => x.Content.Id).ToList(); + Assert.AreEqual(2, ids.Count); + + //returns results for en-us fields and invariant fields for all document types + Assert.IsTrue(ids.Contains(1) && ids.Contains(2) && !ids.Contains(3)); + } + } + } + + [Test] + public void Search_Culture2() + { + using (var luceneDir = new RandomIdRAMDirectory()) + { + var fieldNames = new[] { "title", "title_en-us", "title_fr-fr" }; + using (var indexer = CreateTestIndex(luceneDir, fieldNames)) + { + var pcq = CreatePublishedContentQuery(indexer); + + var results = pcq.Search("Products", "fr-fr", "TestIndex"); + + var ids = results.Select(x => x.Content.Id).ToList(); + Assert.AreEqual(2, ids.Count); + + //returns results for fr-fr fields and invariant fields for all document types + Assert.IsTrue(ids.Contains(1) && !ids.Contains(2) && ids.Contains(3)); + } + } + } + } +} diff --git a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs index b23b5bd6b7..26d85f60cf 100644 --- a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs @@ -1,5 +1,8 @@ using System; using System.Text; +using Examine.LuceneEngine; +using Lucene.Net.Analysis; +using Lucene.Net.Analysis.Standard; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -13,18 +16,19 @@ using Umbraco.Web; namespace Umbraco.Tests.Web { + [TestFixture] public class UmbracoHelperTests - { + { [TearDown] public void TearDown() { Current.Reset(); } - - + + // ------- Int32 conversion tests [Test] public static void Converting_Boxed_34_To_An_Int_Returns_34() diff --git a/src/Umbraco.Web/IPublishedContentQuery.cs b/src/Umbraco.Web/IPublishedContentQuery.cs index 35db121a60..8a8d678aba 100644 --- a/src/Umbraco.Web/IPublishedContentQuery.cs +++ b/src/Umbraco.Web/IPublishedContentQuery.cs @@ -39,7 +39,11 @@ namespace Umbraco.Web /// Optional culture. /// Optional index name. /// - /// When the is not specified or is *, all cultures are searched. To search only invariant use null. + /// + /// When the is not specified or is *, all cultures are searched. + /// To search for only invariant documents and fields use null. + /// When searching on a specific culture, all culture specific fields are searched for the provided culture and all invariant fields for all documents. + /// /// While enumerating results, the ambient culture is changed to be the searched culture. /// IEnumerable Search(string term, string culture = "*", string indexName = null); @@ -54,7 +58,11 @@ namespace Umbraco.Web /// Optional culture. /// Optional index name. /// - /// When the is not specified or is *, all cultures are searched. To search only invariant use null. + /// + /// When the is not specified or is *, all cultures are searched. + /// To search for only invariant documents and fields use null. + /// When searching on a specific culture, all culture specific fields are searched for the provided culture and all invariant fields for all documents. + /// /// While enumerating results, the ambient culture is changed to be the searched culture. /// IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = null); diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index cfdd31f138..2dbe4de4c5 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -20,14 +20,22 @@ namespace Umbraco.Web { private readonly IPublishedSnapshot _publishedSnapshot; private readonly IVariationContextAccessor _variationContextAccessor; + private readonly IExamineManager _examineManager; + + [Obsolete("Use the constructor with all parameters instead")] + public PublishedContentQuery(IPublishedSnapshot publishedSnapshot, IVariationContextAccessor variationContextAccessor) + : this (publishedSnapshot, variationContextAccessor, ExamineManager.Instance) + { + } /// /// Initializes a new instance of the class. /// - public PublishedContentQuery(IPublishedSnapshot publishedSnapshot, IVariationContextAccessor variationContextAccessor) + public PublishedContentQuery(IPublishedSnapshot publishedSnapshot, IVariationContextAccessor variationContextAccessor, IExamineManager examineManager) { _publishedSnapshot = publishedSnapshot ?? throw new ArgumentNullException(nameof(publishedSnapshot)); _variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); + _examineManager = examineManager ?? throw new ArgumentNullException(nameof(examineManager)); } #region Content @@ -187,7 +195,7 @@ namespace Umbraco.Web ? Constants.UmbracoIndexes.ExternalIndexName : indexName; - if (!ExamineManager.Instance.TryGetIndex(indexName, out var index) || !(index is IUmbracoIndex umbIndex)) + if (!_examineManager.TryGetIndex(indexName, out var index) || !(index is IUmbracoIndex umbIndex)) throw new InvalidOperationException($"No index found by name {indexName} or is not of type {typeof(IUmbracoIndex)}"); var searcher = umbIndex.GetSearcher(); @@ -216,8 +224,7 @@ namespace Umbraco.Web //get all index fields suffixed with the culture name supplied var cultureFields = umbIndex.GetCultureAndInvariantFields(culture).ToArray(); - var qry = searcher.CreateQuery(); - qry = qry.ManagedQuery(term, cultureFields); + var qry = searcher.CreateQuery().ManagedQuery(term, cultureFields); results = qry.Execute(count); } @@ -313,7 +320,7 @@ namespace Umbraco.Web } } - + #endregion From 342b2087e999f508adcfdcbf4000504dbdf0f0f4 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 12 Jul 2019 19:20:15 +0200 Subject: [PATCH 186/776] Make sure save options are up to date with the content state (cherry picked from commit 8a43e3a87ec28b997878e2433e464b07680bf5e2) --- .../common/directives/components/content/edit.controller.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index a548820138..58a22ac74e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -94,6 +94,10 @@ content.apps[0].active = true; $scope.appChanged(content.apps[0]); } + // otherwise make sure the save options are up to date with the current content state + else { + createButtons($scope.content); + } editorState.set(content); From 308f929f7b5ba1bfd6b8fc373427a426929db2a2 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 24 Jul 2019 11:13:21 +0200 Subject: [PATCH 187/776] Refactored tests to avoid duplicate code --- .../Web/PublishedContentQueryTests.cs | 78 ++----------------- 1 file changed, 8 insertions(+), 70 deletions(-) diff --git a/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs b/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs index b2a2741bcf..2cff946372 100644 --- a/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs +++ b/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs @@ -70,8 +70,11 @@ namespace Umbraco.Tests.Web return new PublishedContentQuery(snapshot, variationContextAccessor, examineManager.Object); } - [Test] - public void Search_Wildcard() + [TestCase("fr-fr", ExpectedResult = "1, 3", TestName = "Search Culture: fr-fr. Must return both fr-fr and invariant results")] + [TestCase("en-us", ExpectedResult = "1, 2", TestName = "Search Culture: en-us. Must return both en-us and invariant results")] + [TestCase("*", ExpectedResult = "1, 2, 3", TestName = "Search Culture: *. Must return all cultures and all invariant results")] + [TestCase(null, ExpectedResult = "1", TestName = "Search Culture: null. Must return only invariant results")] + public string Search(string culture) { using (var luceneDir = new RandomIdRAMDirectory()) { @@ -80,76 +83,11 @@ namespace Umbraco.Tests.Web { var pcq = CreatePublishedContentQuery(indexer); - var results = pcq.Search("Products", "*", "TestIndex"); + var results = pcq.Search("Products", culture, "TestIndex"); - var ids = results.Select(x => x.Content.Id).ToList(); - Assert.AreEqual(3, ids.Count); + var ids = results.Select(x => x.Content.Id).ToArray(); - //returns results for all fields and document types - Assert.IsTrue(ids.Contains(1) && ids.Contains(2) && ids.Contains(3)); - } - } - } - - [Test] - public void Search_Invariant() - { - using (var luceneDir = new RandomIdRAMDirectory()) - { - var fieldNames = new[] { "title", "title_en-us", "title_fr-fr" }; - using (var indexer = CreateTestIndex(luceneDir, fieldNames)) - { - var pcq = CreatePublishedContentQuery(indexer); - - var results = pcq.Search("Products", null, "TestIndex"); - - var ids = results.Select(x => x.Content.Id).ToList(); - Assert.AreEqual(1, ids.Count); - - //returns results for only invariant fields and invariant documents - Assert.IsTrue(ids.Contains(1) && !ids.Contains(2) && !ids.Contains(3)); - } - } - } - - [Test] - public void Search_Culture1() - { - using (var luceneDir = new RandomIdRAMDirectory()) - { - var fieldNames = new[] { "title", "title_en-us", "title_fr-fr" }; - using (var indexer = CreateTestIndex(luceneDir, fieldNames)) - { - var pcq = CreatePublishedContentQuery(indexer); - - var results = pcq.Search("Products", "en-us", "TestIndex"); - - var ids = results.Select(x => x.Content.Id).ToList(); - Assert.AreEqual(2, ids.Count); - - //returns results for en-us fields and invariant fields for all document types - Assert.IsTrue(ids.Contains(1) && ids.Contains(2) && !ids.Contains(3)); - } - } - } - - [Test] - public void Search_Culture2() - { - using (var luceneDir = new RandomIdRAMDirectory()) - { - var fieldNames = new[] { "title", "title_en-us", "title_fr-fr" }; - using (var indexer = CreateTestIndex(luceneDir, fieldNames)) - { - var pcq = CreatePublishedContentQuery(indexer); - - var results = pcq.Search("Products", "fr-fr", "TestIndex"); - - var ids = results.Select(x => x.Content.Id).ToList(); - Assert.AreEqual(2, ids.Count); - - //returns results for fr-fr fields and invariant fields for all document types - Assert.IsTrue(ids.Contains(1) && !ids.Contains(2) && ids.Contains(3)); + return string.Join(", ", ids); } } } From a97604d6c405c7ebf6372f69807193e8cbbc7a50 Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Sun, 21 Jul 2019 15:19:17 +0100 Subject: [PATCH 188/776] Handle CSV values in TagsPropertyEditor --- .../PropertyEditors/TagsPropertyEditor.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs index 90527a8b8d..b7101aa764 100644 --- a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using Newtonsoft.Json.Linq; @@ -38,9 +39,15 @@ namespace Umbraco.Web.PropertyEditors /// public override object FromEditor(ContentPropertyData editorValue, object currentValue) { - return editorValue.Value is JArray json - ? json.Select(x => x.Value()) - : null; + if (editorValue.Value is JArray json) + { + return json.Select(x => x.Value()); + } + else if ( editorValue.Value is string stringValue && !String.IsNullOrWhiteSpace(stringValue)) + { + return stringValue.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + } + return null; } /// From 186460ac08de66428a54357857c630093b8c9674 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 24 Jul 2019 19:23:05 +1000 Subject: [PATCH 189/776] removes refs from old packages.umbraco.org --- src/Umbraco.Web/Properties/Settings.settings | 3 - .../Properties/Settings1.Designer.cs | 12 +- src/Umbraco.Web/Umbraco.Web.csproj | 22 - .../org.umbraco.our/Reference.cs | 1046 ----------------- .../org.umbraco.our/Reference.map | 7 - .../org.umbraco.our/repository.disco | 6 - .../org.umbraco.our/repository.wsdl | 995 ---------------- 7 files changed, 1 insertion(+), 2090 deletions(-) delete mode 100644 src/Umbraco.Web/Web References/org.umbraco.our/Reference.cs delete mode 100644 src/Umbraco.Web/Web References/org.umbraco.our/Reference.map delete mode 100644 src/Umbraco.Web/Web References/org.umbraco.our/repository.disco delete mode 100644 src/Umbraco.Web/Web References/org.umbraco.our/repository.wsdl diff --git a/src/Umbraco.Web/Properties/Settings.settings b/src/Umbraco.Web/Properties/Settings.settings index da9e6f69c0..a7b911ae3b 100644 --- a/src/Umbraco.Web/Properties/Settings.settings +++ b/src/Umbraco.Web/Properties/Settings.settings @@ -11,8 +11,5 @@ Somthing - - https://our.umbraco.com/umbraco/webservices/api/repository.asmx - \ No newline at end of file diff --git a/src/Umbraco.Web/Properties/Settings1.Designer.cs b/src/Umbraco.Web/Properties/Settings1.Designer.cs index e7028f28df..3847901f20 100644 --- a/src/Umbraco.Web/Properties/Settings1.Designer.cs +++ b/src/Umbraco.Web/Properties/Settings1.Designer.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.8.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.2.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -50,15 +50,5 @@ namespace Umbraco.Web.Properties { return ((string)(this["test"])); } } - - [global::System.Configuration.ApplicationScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.SpecialSettingAttribute(global::System.Configuration.SpecialSetting.WebServiceUrl)] - [global::System.Configuration.DefaultSettingValueAttribute("https://our.umbraco.com/umbraco/webservices/api/repository.asmx")] - public string umbraco_org_umbraco_our_Repository { - get { - return ((string)(this["umbraco_org_umbraco_our_Repository"])); - } - } } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 167c99b9b9..5705f6b7eb 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -812,11 +812,6 @@ - - True - True - Reference.map - @@ -1828,11 +1823,6 @@ Mvc\web.config - - MSDiscoCodeGenerator - Reference.cs - - @@ -1883,7 +1873,6 @@ - Reference.map @@ -1977,17 +1966,6 @@ - - Dynamic - Web References\org.umbraco.our\ - https://our.umbraco.com/umbraco/webservices/api/repository.asmx - - - - - Settings - umbraco_org_umbraco_our_Repository - Dynamic Web References\org.umbraco.update\ diff --git a/src/Umbraco.Web/Web References/org.umbraco.our/Reference.cs b/src/Umbraco.Web/Web References/org.umbraco.our/Reference.cs deleted file mode 100644 index d0c8990660..0000000000 --- a/src/Umbraco.Web/Web References/org.umbraco.our/Reference.cs +++ /dev/null @@ -1,1046 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -// -// This source code was auto-generated by Microsoft.VSDesigner, Version 4.0.30319.42000. -// -#pragma warning disable 1591 - -namespace Umbraco.Web.org.umbraco.our { - using System; - using System.Web.Services; - using System.Diagnostics; - using System.Web.Services.Protocols; - using System.Xml.Serialization; - using System.ComponentModel; - - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Web.Services.WebServiceBindingAttribute(Name="RepositorySoap", Namespace="http://packages.umbraco.org/webservices/")] - public partial class Repository : System.Web.Services.Protocols.SoapHttpClientProtocol { - - private System.Threading.SendOrPostCallback CategoriesOperationCompleted; - - private System.Threading.SendOrPostCallback ModulesOperationCompleted; - - private System.Threading.SendOrPostCallback ModulesCategorizedOperationCompleted; - - private System.Threading.SendOrPostCallback NitrosOperationCompleted; - - private System.Threading.SendOrPostCallback NitrosCategorizedOperationCompleted; - - private System.Threading.SendOrPostCallback authenticateOperationCompleted; - - private System.Threading.SendOrPostCallback fetchPackageOperationCompleted; - - private System.Threading.SendOrPostCallback fetchPackageByVersionOperationCompleted; - - private System.Threading.SendOrPostCallback fetchProtectedPackageOperationCompleted; - - private System.Threading.SendOrPostCallback SubmitPackageOperationCompleted; - - private System.Threading.SendOrPostCallback PackageByGuidOperationCompleted; - - private bool useDefaultCredentialsSetExplicitly; - - /// - public Repository() { - this.Url = global::Umbraco.Web.Properties.Settings.Default.umbraco_org_umbraco_our_Repository; - if ((this.IsLocalFileSystemWebService(this.Url) == true)) { - this.UseDefaultCredentials = true; - this.useDefaultCredentialsSetExplicitly = false; - } - else { - this.useDefaultCredentialsSetExplicitly = true; - } - } - - public new string Url { - get { - return base.Url; - } - set { - if ((((this.IsLocalFileSystemWebService(base.Url) == true) - && (this.useDefaultCredentialsSetExplicitly == false)) - && (this.IsLocalFileSystemWebService(value) == false))) { - base.UseDefaultCredentials = false; - } - base.Url = value; - } - } - - public new bool UseDefaultCredentials { - get { - return base.UseDefaultCredentials; - } - set { - base.UseDefaultCredentials = value; - this.useDefaultCredentialsSetExplicitly = true; - } - } - - /// - public event CategoriesCompletedEventHandler CategoriesCompleted; - - /// - public event ModulesCompletedEventHandler ModulesCompleted; - - /// - public event ModulesCategorizedCompletedEventHandler ModulesCategorizedCompleted; - - /// - public event NitrosCompletedEventHandler NitrosCompleted; - - /// - public event NitrosCategorizedCompletedEventHandler NitrosCategorizedCompleted; - - /// - public event authenticateCompletedEventHandler authenticateCompleted; - - /// - public event fetchPackageCompletedEventHandler fetchPackageCompleted; - - /// - public event fetchPackageByVersionCompletedEventHandler fetchPackageByVersionCompleted; - - /// - public event fetchProtectedPackageCompletedEventHandler fetchProtectedPackageCompleted; - - /// - public event SubmitPackageCompletedEventHandler SubmitPackageCompleted; - - /// - public event PackageByGuidCompletedEventHandler PackageByGuidCompleted; - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/Categories", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Category[] Categories(string repositoryGuid) { - object[] results = this.Invoke("Categories", new object[] { - repositoryGuid}); - return ((Category[])(results[0])); - } - - /// - public void CategoriesAsync(string repositoryGuid) { - this.CategoriesAsync(repositoryGuid, null); - } - - /// - public void CategoriesAsync(string repositoryGuid, object userState) { - if ((this.CategoriesOperationCompleted == null)) { - this.CategoriesOperationCompleted = new System.Threading.SendOrPostCallback(this.OnCategoriesOperationCompleted); - } - this.InvokeAsync("Categories", new object[] { - repositoryGuid}, this.CategoriesOperationCompleted, userState); - } - - private void OnCategoriesOperationCompleted(object arg) { - if ((this.CategoriesCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.CategoriesCompleted(this, new CategoriesCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/Modules", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Package[] Modules() { - object[] results = this.Invoke("Modules", new object[0]); - return ((Package[])(results[0])); - } - - /// - public void ModulesAsync() { - this.ModulesAsync(null); - } - - /// - public void ModulesAsync(object userState) { - if ((this.ModulesOperationCompleted == null)) { - this.ModulesOperationCompleted = new System.Threading.SendOrPostCallback(this.OnModulesOperationCompleted); - } - this.InvokeAsync("Modules", new object[0], this.ModulesOperationCompleted, userState); - } - - private void OnModulesOperationCompleted(object arg) { - if ((this.ModulesCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.ModulesCompleted(this, new ModulesCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/ModulesCategorized", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Category[] ModulesCategorized() { - object[] results = this.Invoke("ModulesCategorized", new object[0]); - return ((Category[])(results[0])); - } - - /// - public void ModulesCategorizedAsync() { - this.ModulesCategorizedAsync(null); - } - - /// - public void ModulesCategorizedAsync(object userState) { - if ((this.ModulesCategorizedOperationCompleted == null)) { - this.ModulesCategorizedOperationCompleted = new System.Threading.SendOrPostCallback(this.OnModulesCategorizedOperationCompleted); - } - this.InvokeAsync("ModulesCategorized", new object[0], this.ModulesCategorizedOperationCompleted, userState); - } - - private void OnModulesCategorizedOperationCompleted(object arg) { - if ((this.ModulesCategorizedCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.ModulesCategorizedCompleted(this, new ModulesCategorizedCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/Nitros", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Package[] Nitros() { - object[] results = this.Invoke("Nitros", new object[0]); - return ((Package[])(results[0])); - } - - /// - public void NitrosAsync() { - this.NitrosAsync(null); - } - - /// - public void NitrosAsync(object userState) { - if ((this.NitrosOperationCompleted == null)) { - this.NitrosOperationCompleted = new System.Threading.SendOrPostCallback(this.OnNitrosOperationCompleted); - } - this.InvokeAsync("Nitros", new object[0], this.NitrosOperationCompleted, userState); - } - - private void OnNitrosOperationCompleted(object arg) { - if ((this.NitrosCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.NitrosCompleted(this, new NitrosCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/NitrosCategorized", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Category[] NitrosCategorized() { - object[] results = this.Invoke("NitrosCategorized", new object[0]); - return ((Category[])(results[0])); - } - - /// - public void NitrosCategorizedAsync() { - this.NitrosCategorizedAsync(null); - } - - /// - public void NitrosCategorizedAsync(object userState) { - if ((this.NitrosCategorizedOperationCompleted == null)) { - this.NitrosCategorizedOperationCompleted = new System.Threading.SendOrPostCallback(this.OnNitrosCategorizedOperationCompleted); - } - this.InvokeAsync("NitrosCategorized", new object[0], this.NitrosCategorizedOperationCompleted, userState); - } - - private void OnNitrosCategorizedOperationCompleted(object arg) { - if ((this.NitrosCategorizedCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.NitrosCategorizedCompleted(this, new NitrosCategorizedCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/authenticate", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public string authenticate(string email, string md5Password) { - object[] results = this.Invoke("authenticate", new object[] { - email, - md5Password}); - return ((string)(results[0])); - } - - /// - public void authenticateAsync(string email, string md5Password) { - this.authenticateAsync(email, md5Password, null); - } - - /// - public void authenticateAsync(string email, string md5Password, object userState) { - if ((this.authenticateOperationCompleted == null)) { - this.authenticateOperationCompleted = new System.Threading.SendOrPostCallback(this.OnauthenticateOperationCompleted); - } - this.InvokeAsync("authenticate", new object[] { - email, - md5Password}, this.authenticateOperationCompleted, userState); - } - - private void OnauthenticateOperationCompleted(object arg) { - if ((this.authenticateCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.authenticateCompleted(this, new authenticateCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/fetchPackage", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - [return: System.Xml.Serialization.XmlElementAttribute(DataType="base64Binary")] - public byte[] fetchPackage(string packageGuid) { - object[] results = this.Invoke("fetchPackage", new object[] { - packageGuid}); - return ((byte[])(results[0])); - } - - /// - public void fetchPackageAsync(string packageGuid) { - this.fetchPackageAsync(packageGuid, null); - } - - /// - public void fetchPackageAsync(string packageGuid, object userState) { - if ((this.fetchPackageOperationCompleted == null)) { - this.fetchPackageOperationCompleted = new System.Threading.SendOrPostCallback(this.OnfetchPackageOperationCompleted); - } - this.InvokeAsync("fetchPackage", new object[] { - packageGuid}, this.fetchPackageOperationCompleted, userState); - } - - private void OnfetchPackageOperationCompleted(object arg) { - if ((this.fetchPackageCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.fetchPackageCompleted(this, new fetchPackageCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/fetchPackageByVersion", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - [return: System.Xml.Serialization.XmlElementAttribute(DataType="base64Binary")] - public byte[] fetchPackageByVersion(string packageGuid, string repoVersion) { - object[] results = this.Invoke("fetchPackageByVersion", new object[] { - packageGuid, - repoVersion}); - return ((byte[])(results[0])); - } - - /// - public void fetchPackageByVersionAsync(string packageGuid, string repoVersion) { - this.fetchPackageByVersionAsync(packageGuid, repoVersion, null); - } - - /// - public void fetchPackageByVersionAsync(string packageGuid, string repoVersion, object userState) { - if ((this.fetchPackageByVersionOperationCompleted == null)) { - this.fetchPackageByVersionOperationCompleted = new System.Threading.SendOrPostCallback(this.OnfetchPackageByVersionOperationCompleted); - } - this.InvokeAsync("fetchPackageByVersion", new object[] { - packageGuid, - repoVersion}, this.fetchPackageByVersionOperationCompleted, userState); - } - - private void OnfetchPackageByVersionOperationCompleted(object arg) { - if ((this.fetchPackageByVersionCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.fetchPackageByVersionCompleted(this, new fetchPackageByVersionCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/fetchProtectedPackage", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - [return: System.Xml.Serialization.XmlElementAttribute(DataType="base64Binary")] - public byte[] fetchProtectedPackage(string packageGuid, string memberKey) { - object[] results = this.Invoke("fetchProtectedPackage", new object[] { - packageGuid, - memberKey}); - return ((byte[])(results[0])); - } - - /// - public void fetchProtectedPackageAsync(string packageGuid, string memberKey) { - this.fetchProtectedPackageAsync(packageGuid, memberKey, null); - } - - /// - public void fetchProtectedPackageAsync(string packageGuid, string memberKey, object userState) { - if ((this.fetchProtectedPackageOperationCompleted == null)) { - this.fetchProtectedPackageOperationCompleted = new System.Threading.SendOrPostCallback(this.OnfetchProtectedPackageOperationCompleted); - } - this.InvokeAsync("fetchProtectedPackage", new object[] { - packageGuid, - memberKey}, this.fetchProtectedPackageOperationCompleted, userState); - } - - private void OnfetchProtectedPackageOperationCompleted(object arg) { - if ((this.fetchProtectedPackageCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.fetchProtectedPackageCompleted(this, new fetchProtectedPackageCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/SubmitPackage", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public SubmitStatus SubmitPackage(string repositoryGuid, string authorGuid, string packageGuid, [System.Xml.Serialization.XmlElementAttribute(DataType="base64Binary")] byte[] packageFile, [System.Xml.Serialization.XmlElementAttribute(DataType="base64Binary")] byte[] packageDoc, [System.Xml.Serialization.XmlElementAttribute(DataType="base64Binary")] byte[] packageThumbnail, string name, string author, string authorUrl, string description) { - object[] results = this.Invoke("SubmitPackage", new object[] { - repositoryGuid, - authorGuid, - packageGuid, - packageFile, - packageDoc, - packageThumbnail, - name, - author, - authorUrl, - description}); - return ((SubmitStatus)(results[0])); - } - - /// - public void SubmitPackageAsync(string repositoryGuid, string authorGuid, string packageGuid, byte[] packageFile, byte[] packageDoc, byte[] packageThumbnail, string name, string author, string authorUrl, string description) { - this.SubmitPackageAsync(repositoryGuid, authorGuid, packageGuid, packageFile, packageDoc, packageThumbnail, name, author, authorUrl, description, null); - } - - /// - public void SubmitPackageAsync(string repositoryGuid, string authorGuid, string packageGuid, byte[] packageFile, byte[] packageDoc, byte[] packageThumbnail, string name, string author, string authorUrl, string description, object userState) { - if ((this.SubmitPackageOperationCompleted == null)) { - this.SubmitPackageOperationCompleted = new System.Threading.SendOrPostCallback(this.OnSubmitPackageOperationCompleted); - } - this.InvokeAsync("SubmitPackage", new object[] { - repositoryGuid, - authorGuid, - packageGuid, - packageFile, - packageDoc, - packageThumbnail, - name, - author, - authorUrl, - description}, this.SubmitPackageOperationCompleted, userState); - } - - private void OnSubmitPackageOperationCompleted(object arg) { - if ((this.SubmitPackageCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.SubmitPackageCompleted(this, new SubmitPackageCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/PackageByGuid", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Package PackageByGuid(string packageGuid) { - object[] results = this.Invoke("PackageByGuid", new object[] { - packageGuid}); - return ((Package)(results[0])); - } - - /// - public void PackageByGuidAsync(string packageGuid) { - this.PackageByGuidAsync(packageGuid, null); - } - - /// - public void PackageByGuidAsync(string packageGuid, object userState) { - if ((this.PackageByGuidOperationCompleted == null)) { - this.PackageByGuidOperationCompleted = new System.Threading.SendOrPostCallback(this.OnPackageByGuidOperationCompleted); - } - this.InvokeAsync("PackageByGuid", new object[] { - packageGuid}, this.PackageByGuidOperationCompleted, userState); - } - - private void OnPackageByGuidOperationCompleted(object arg) { - if ((this.PackageByGuidCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.PackageByGuidCompleted(this, new PackageByGuidCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - public new void CancelAsync(object userState) { - base.CancelAsync(userState); - } - - private bool IsLocalFileSystemWebService(string url) { - if (((url == null) - || (url == string.Empty))) { - return false; - } - System.Uri wsUri = new System.Uri(url); - if (((wsUri.Port >= 1024) - && (string.Compare(wsUri.Host, "localHost", System.StringComparison.OrdinalIgnoreCase) == 0))) { - return true; - } - return false; - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.7.3056.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://packages.umbraco.org/webservices/")] - public partial class Category { - - private string textField; - - private string descriptionField; - - private string urlField; - - private int idField; - - private Package[] packagesField; - - /// - public string Text { - get { - return this.textField; - } - set { - this.textField = value; - } - } - - /// - public string Description { - get { - return this.descriptionField; - } - set { - this.descriptionField = value; - } - } - - /// - public string Url { - get { - return this.urlField; - } - set { - this.urlField = value; - } - } - - /// - public int Id { - get { - return this.idField; - } - set { - this.idField = value; - } - } - - /// - public Package[] Packages { - get { - return this.packagesField; - } - set { - this.packagesField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.7.3056.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://packages.umbraco.org/webservices/")] - public partial class Package { - - private System.Guid repoGuidField; - - private string textField; - - private string descriptionField; - - private string iconField; - - private string thumbnailField; - - private string documentationField; - - private string demoField; - - private bool acceptedField; - - private bool isModuleField; - - private bool editorsPickField; - - private bool protectedField; - - private bool hasUpgradeField; - - private string upgradeVersionField; - - private string upgradeReadMeField; - - private string urlField; - - /// - public System.Guid RepoGuid { - get { - return this.repoGuidField; - } - set { - this.repoGuidField = value; - } - } - - /// - public string Text { - get { - return this.textField; - } - set { - this.textField = value; - } - } - - /// - public string Description { - get { - return this.descriptionField; - } - set { - this.descriptionField = value; - } - } - - /// - public string Icon { - get { - return this.iconField; - } - set { - this.iconField = value; - } - } - - /// - public string Thumbnail { - get { - return this.thumbnailField; - } - set { - this.thumbnailField = value; - } - } - - /// - public string Documentation { - get { - return this.documentationField; - } - set { - this.documentationField = value; - } - } - - /// - public string Demo { - get { - return this.demoField; - } - set { - this.demoField = value; - } - } - - /// - public bool Accepted { - get { - return this.acceptedField; - } - set { - this.acceptedField = value; - } - } - - /// - public bool IsModule { - get { - return this.isModuleField; - } - set { - this.isModuleField = value; - } - } - - /// - public bool EditorsPick { - get { - return this.editorsPickField; - } - set { - this.editorsPickField = value; - } - } - - /// - public bool Protected { - get { - return this.protectedField; - } - set { - this.protectedField = value; - } - } - - /// - public bool HasUpgrade { - get { - return this.hasUpgradeField; - } - set { - this.hasUpgradeField = value; - } - } - - /// - public string UpgradeVersion { - get { - return this.upgradeVersionField; - } - set { - this.upgradeVersionField = value; - } - } - - /// - public string UpgradeReadMe { - get { - return this.upgradeReadMeField; - } - set { - this.upgradeReadMeField = value; - } - } - - /// - public string Url { - get { - return this.urlField; - } - set { - this.urlField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.7.3056.0")] - [System.SerializableAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://packages.umbraco.org/webservices/")] - public enum SubmitStatus { - - /// - Complete, - - /// - Exists, - - /// - NoAccess, - - /// - Error, - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - public delegate void CategoriesCompletedEventHandler(object sender, CategoriesCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class CategoriesCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal CategoriesCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public Category[] Result { - get { - this.RaiseExceptionIfNecessary(); - return ((Category[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - public delegate void ModulesCompletedEventHandler(object sender, ModulesCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class ModulesCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal ModulesCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public Package[] Result { - get { - this.RaiseExceptionIfNecessary(); - return ((Package[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - public delegate void ModulesCategorizedCompletedEventHandler(object sender, ModulesCategorizedCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class ModulesCategorizedCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal ModulesCategorizedCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public Category[] Result { - get { - this.RaiseExceptionIfNecessary(); - return ((Category[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - public delegate void NitrosCompletedEventHandler(object sender, NitrosCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class NitrosCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal NitrosCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public Package[] Result { - get { - this.RaiseExceptionIfNecessary(); - return ((Package[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - public delegate void NitrosCategorizedCompletedEventHandler(object sender, NitrosCategorizedCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class NitrosCategorizedCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal NitrosCategorizedCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public Category[] Result { - get { - this.RaiseExceptionIfNecessary(); - return ((Category[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - public delegate void authenticateCompletedEventHandler(object sender, authenticateCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class authenticateCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal authenticateCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public string Result { - get { - this.RaiseExceptionIfNecessary(); - return ((string)(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - public delegate void fetchPackageCompletedEventHandler(object sender, fetchPackageCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class fetchPackageCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal fetchPackageCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public byte[] Result { - get { - this.RaiseExceptionIfNecessary(); - return ((byte[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - public delegate void fetchPackageByVersionCompletedEventHandler(object sender, fetchPackageByVersionCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class fetchPackageByVersionCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal fetchPackageByVersionCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public byte[] Result { - get { - this.RaiseExceptionIfNecessary(); - return ((byte[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - public delegate void fetchProtectedPackageCompletedEventHandler(object sender, fetchProtectedPackageCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class fetchProtectedPackageCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal fetchProtectedPackageCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public byte[] Result { - get { - this.RaiseExceptionIfNecessary(); - return ((byte[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - public delegate void SubmitPackageCompletedEventHandler(object sender, SubmitPackageCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class SubmitPackageCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal SubmitPackageCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public SubmitStatus Result { - get { - this.RaiseExceptionIfNecessary(); - return ((SubmitStatus)(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - public delegate void PackageByGuidCompletedEventHandler(object sender, PackageByGuidCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3056.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class PackageByGuidCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal PackageByGuidCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public Package Result { - get { - this.RaiseExceptionIfNecessary(); - return ((Package)(this.results[0])); - } - } - } -} - -#pragma warning restore 1591 \ No newline at end of file diff --git a/src/Umbraco.Web/Web References/org.umbraco.our/Reference.map b/src/Umbraco.Web/Web References/org.umbraco.our/Reference.map deleted file mode 100644 index cd819c5591..0000000000 --- a/src/Umbraco.Web/Web References/org.umbraco.our/Reference.map +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web/Web References/org.umbraco.our/repository.disco b/src/Umbraco.Web/Web References/org.umbraco.our/repository.disco deleted file mode 100644 index fcc65dea0c..0000000000 --- a/src/Umbraco.Web/Web References/org.umbraco.our/repository.disco +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web/Web References/org.umbraco.our/repository.wsdl b/src/Umbraco.Web/Web References/org.umbraco.our/repository.wsdl deleted file mode 100644 index 17d24e9c54..0000000000 --- a/src/Umbraco.Web/Web References/org.umbraco.our/repository.wsdl +++ /dev/null @@ -1,995 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 28a80271795e2b2d149b8fa815271a5994394baf Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 24 Jul 2019 19:25:32 +1000 Subject: [PATCH 190/776] removes reference to old packages.umbraco.org --- .../Properties/Settings.Designer.cs | 12 +- src/Umbraco.Web/Properties/Settings.settings | 3 - src/Umbraco.Web/Umbraco.Web.csproj | 22 - .../org.umbraco.our/Reference.cs | 1046 ----------------- .../org.umbraco.our/Reference.map | 7 - .../org.umbraco.our/repository.disco | 6 - .../org.umbraco.our/repository.wsdl | 995 ---------------- 7 files changed, 1 insertion(+), 2090 deletions(-) delete mode 100644 src/Umbraco.Web/Web References/org.umbraco.our/Reference.cs delete mode 100644 src/Umbraco.Web/Web References/org.umbraco.our/Reference.map delete mode 100644 src/Umbraco.Web/Web References/org.umbraco.our/repository.disco delete mode 100644 src/Umbraco.Web/Web References/org.umbraco.our/repository.wsdl diff --git a/src/Umbraco.Web/Properties/Settings.Designer.cs b/src/Umbraco.Web/Properties/Settings.Designer.cs index 5a5a863f4f..5f7ccbd7e1 100644 --- a/src/Umbraco.Web/Properties/Settings.Designer.cs +++ b/src/Umbraco.Web/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.2.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -50,15 +50,5 @@ namespace Umbraco.Web.Properties { return ((string)(this["test"])); } } - - [global::System.Configuration.ApplicationScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.SpecialSettingAttribute(global::System.Configuration.SpecialSetting.WebServiceUrl)] - [global::System.Configuration.DefaultSettingValueAttribute("https://our.umbraco.com/umbraco/webservices/api/repository.asmx")] - public string umbraco_org_umbraco_our_Repository { - get { - return ((string)(this["umbraco_org_umbraco_our_Repository"])); - } - } } } diff --git a/src/Umbraco.Web/Properties/Settings.settings b/src/Umbraco.Web/Properties/Settings.settings index 757b363da2..3a6f68023e 100644 --- a/src/Umbraco.Web/Properties/Settings.settings +++ b/src/Umbraco.Web/Properties/Settings.settings @@ -11,8 +11,5 @@ Something - - https://our.umbraco.com/umbraco/webservices/api/repository.asmx - \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index dcaa3494fd..730a321a16 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -906,11 +906,6 @@ - - True - True - Reference.map - @@ -1220,12 +1215,6 @@ Mvc\web.config - - MSDiscoCodeGenerator - Reference.cs - - - Reference.map @@ -1238,17 +1227,6 @@ - - Dynamic - Web References\org.umbraco.our\ - http://our.umbraco.org/umbraco/webservices/api/repository.asmx - - - - - Settings - umbraco_org_umbraco_our_Repository - Dynamic Web References\org.umbraco.update\ diff --git a/src/Umbraco.Web/Web References/org.umbraco.our/Reference.cs b/src/Umbraco.Web/Web References/org.umbraco.our/Reference.cs deleted file mode 100644 index caa2887797..0000000000 --- a/src/Umbraco.Web/Web References/org.umbraco.our/Reference.cs +++ /dev/null @@ -1,1046 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -// -// This source code was auto-generated by Microsoft.VSDesigner, Version 4.0.30319.42000. -// -#pragma warning disable 1591 - -namespace Umbraco.Web.org.umbraco.our { - using System; - using System.Web.Services; - using System.Diagnostics; - using System.Web.Services.Protocols; - using System.Xml.Serialization; - using System.ComponentModel; - - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Web.Services.WebServiceBindingAttribute(Name="RepositorySoap", Namespace="http://packages.umbraco.org/webservices/")] - public partial class Repository : System.Web.Services.Protocols.SoapHttpClientProtocol { - - private System.Threading.SendOrPostCallback CategoriesOperationCompleted; - - private System.Threading.SendOrPostCallback ModulesOperationCompleted; - - private System.Threading.SendOrPostCallback ModulesCategorizedOperationCompleted; - - private System.Threading.SendOrPostCallback NitrosOperationCompleted; - - private System.Threading.SendOrPostCallback NitrosCategorizedOperationCompleted; - - private System.Threading.SendOrPostCallback authenticateOperationCompleted; - - private System.Threading.SendOrPostCallback fetchPackageOperationCompleted; - - private System.Threading.SendOrPostCallback fetchPackageByVersionOperationCompleted; - - private System.Threading.SendOrPostCallback fetchProtectedPackageOperationCompleted; - - private System.Threading.SendOrPostCallback SubmitPackageOperationCompleted; - - private System.Threading.SendOrPostCallback PackageByGuidOperationCompleted; - - private bool useDefaultCredentialsSetExplicitly; - - /// - public Repository() { - this.Url = "http://our.umbraco.org/umbraco/webservices/api/repository.asmx"; - if ((this.IsLocalFileSystemWebService(this.Url) == true)) { - this.UseDefaultCredentials = true; - this.useDefaultCredentialsSetExplicitly = false; - } - else { - this.useDefaultCredentialsSetExplicitly = true; - } - } - - public new string Url { - get { - return base.Url; - } - set { - if ((((this.IsLocalFileSystemWebService(base.Url) == true) - && (this.useDefaultCredentialsSetExplicitly == false)) - && (this.IsLocalFileSystemWebService(value) == false))) { - base.UseDefaultCredentials = false; - } - base.Url = value; - } - } - - public new bool UseDefaultCredentials { - get { - return base.UseDefaultCredentials; - } - set { - base.UseDefaultCredentials = value; - this.useDefaultCredentialsSetExplicitly = true; - } - } - - /// - public event CategoriesCompletedEventHandler CategoriesCompleted; - - /// - public event ModulesCompletedEventHandler ModulesCompleted; - - /// - public event ModulesCategorizedCompletedEventHandler ModulesCategorizedCompleted; - - /// - public event NitrosCompletedEventHandler NitrosCompleted; - - /// - public event NitrosCategorizedCompletedEventHandler NitrosCategorizedCompleted; - - /// - public event authenticateCompletedEventHandler authenticateCompleted; - - /// - public event fetchPackageCompletedEventHandler fetchPackageCompleted; - - /// - public event fetchPackageByVersionCompletedEventHandler fetchPackageByVersionCompleted; - - /// - public event fetchProtectedPackageCompletedEventHandler fetchProtectedPackageCompleted; - - /// - public event SubmitPackageCompletedEventHandler SubmitPackageCompleted; - - /// - public event PackageByGuidCompletedEventHandler PackageByGuidCompleted; - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/Categories", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Category[] Categories(string repositoryGuid) { - object[] results = this.Invoke("Categories", new object[] { - repositoryGuid}); - return ((Category[])(results[0])); - } - - /// - public void CategoriesAsync(string repositoryGuid) { - this.CategoriesAsync(repositoryGuid, null); - } - - /// - public void CategoriesAsync(string repositoryGuid, object userState) { - if ((this.CategoriesOperationCompleted == null)) { - this.CategoriesOperationCompleted = new System.Threading.SendOrPostCallback(this.OnCategoriesOperationCompleted); - } - this.InvokeAsync("Categories", new object[] { - repositoryGuid}, this.CategoriesOperationCompleted, userState); - } - - private void OnCategoriesOperationCompleted(object arg) { - if ((this.CategoriesCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.CategoriesCompleted(this, new CategoriesCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/Modules", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Package[] Modules() { - object[] results = this.Invoke("Modules", new object[0]); - return ((Package[])(results[0])); - } - - /// - public void ModulesAsync() { - this.ModulesAsync(null); - } - - /// - public void ModulesAsync(object userState) { - if ((this.ModulesOperationCompleted == null)) { - this.ModulesOperationCompleted = new System.Threading.SendOrPostCallback(this.OnModulesOperationCompleted); - } - this.InvokeAsync("Modules", new object[0], this.ModulesOperationCompleted, userState); - } - - private void OnModulesOperationCompleted(object arg) { - if ((this.ModulesCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.ModulesCompleted(this, new ModulesCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/ModulesCategorized", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Category[] ModulesCategorized() { - object[] results = this.Invoke("ModulesCategorized", new object[0]); - return ((Category[])(results[0])); - } - - /// - public void ModulesCategorizedAsync() { - this.ModulesCategorizedAsync(null); - } - - /// - public void ModulesCategorizedAsync(object userState) { - if ((this.ModulesCategorizedOperationCompleted == null)) { - this.ModulesCategorizedOperationCompleted = new System.Threading.SendOrPostCallback(this.OnModulesCategorizedOperationCompleted); - } - this.InvokeAsync("ModulesCategorized", new object[0], this.ModulesCategorizedOperationCompleted, userState); - } - - private void OnModulesCategorizedOperationCompleted(object arg) { - if ((this.ModulesCategorizedCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.ModulesCategorizedCompleted(this, new ModulesCategorizedCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/Nitros", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Package[] Nitros() { - object[] results = this.Invoke("Nitros", new object[0]); - return ((Package[])(results[0])); - } - - /// - public void NitrosAsync() { - this.NitrosAsync(null); - } - - /// - public void NitrosAsync(object userState) { - if ((this.NitrosOperationCompleted == null)) { - this.NitrosOperationCompleted = new System.Threading.SendOrPostCallback(this.OnNitrosOperationCompleted); - } - this.InvokeAsync("Nitros", new object[0], this.NitrosOperationCompleted, userState); - } - - private void OnNitrosOperationCompleted(object arg) { - if ((this.NitrosCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.NitrosCompleted(this, new NitrosCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/NitrosCategorized", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Category[] NitrosCategorized() { - object[] results = this.Invoke("NitrosCategorized", new object[0]); - return ((Category[])(results[0])); - } - - /// - public void NitrosCategorizedAsync() { - this.NitrosCategorizedAsync(null); - } - - /// - public void NitrosCategorizedAsync(object userState) { - if ((this.NitrosCategorizedOperationCompleted == null)) { - this.NitrosCategorizedOperationCompleted = new System.Threading.SendOrPostCallback(this.OnNitrosCategorizedOperationCompleted); - } - this.InvokeAsync("NitrosCategorized", new object[0], this.NitrosCategorizedOperationCompleted, userState); - } - - private void OnNitrosCategorizedOperationCompleted(object arg) { - if ((this.NitrosCategorizedCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.NitrosCategorizedCompleted(this, new NitrosCategorizedCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/authenticate", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public string authenticate(string email, string md5Password) { - object[] results = this.Invoke("authenticate", new object[] { - email, - md5Password}); - return ((string)(results[0])); - } - - /// - public void authenticateAsync(string email, string md5Password) { - this.authenticateAsync(email, md5Password, null); - } - - /// - public void authenticateAsync(string email, string md5Password, object userState) { - if ((this.authenticateOperationCompleted == null)) { - this.authenticateOperationCompleted = new System.Threading.SendOrPostCallback(this.OnauthenticateOperationCompleted); - } - this.InvokeAsync("authenticate", new object[] { - email, - md5Password}, this.authenticateOperationCompleted, userState); - } - - private void OnauthenticateOperationCompleted(object arg) { - if ((this.authenticateCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.authenticateCompleted(this, new authenticateCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/fetchPackage", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - [return: System.Xml.Serialization.XmlElementAttribute(DataType="base64Binary")] - public byte[] fetchPackage(string packageGuid) { - object[] results = this.Invoke("fetchPackage", new object[] { - packageGuid}); - return ((byte[])(results[0])); - } - - /// - public void fetchPackageAsync(string packageGuid) { - this.fetchPackageAsync(packageGuid, null); - } - - /// - public void fetchPackageAsync(string packageGuid, object userState) { - if ((this.fetchPackageOperationCompleted == null)) { - this.fetchPackageOperationCompleted = new System.Threading.SendOrPostCallback(this.OnfetchPackageOperationCompleted); - } - this.InvokeAsync("fetchPackage", new object[] { - packageGuid}, this.fetchPackageOperationCompleted, userState); - } - - private void OnfetchPackageOperationCompleted(object arg) { - if ((this.fetchPackageCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.fetchPackageCompleted(this, new fetchPackageCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/fetchPackageByVersion", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - [return: System.Xml.Serialization.XmlElementAttribute(DataType="base64Binary")] - public byte[] fetchPackageByVersion(string packageGuid, string repoVersion) { - object[] results = this.Invoke("fetchPackageByVersion", new object[] { - packageGuid, - repoVersion}); - return ((byte[])(results[0])); - } - - /// - public void fetchPackageByVersionAsync(string packageGuid, string repoVersion) { - this.fetchPackageByVersionAsync(packageGuid, repoVersion, null); - } - - /// - public void fetchPackageByVersionAsync(string packageGuid, string repoVersion, object userState) { - if ((this.fetchPackageByVersionOperationCompleted == null)) { - this.fetchPackageByVersionOperationCompleted = new System.Threading.SendOrPostCallback(this.OnfetchPackageByVersionOperationCompleted); - } - this.InvokeAsync("fetchPackageByVersion", new object[] { - packageGuid, - repoVersion}, this.fetchPackageByVersionOperationCompleted, userState); - } - - private void OnfetchPackageByVersionOperationCompleted(object arg) { - if ((this.fetchPackageByVersionCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.fetchPackageByVersionCompleted(this, new fetchPackageByVersionCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/fetchProtectedPackage", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - [return: System.Xml.Serialization.XmlElementAttribute(DataType="base64Binary")] - public byte[] fetchProtectedPackage(string packageGuid, string memberKey) { - object[] results = this.Invoke("fetchProtectedPackage", new object[] { - packageGuid, - memberKey}); - return ((byte[])(results[0])); - } - - /// - public void fetchProtectedPackageAsync(string packageGuid, string memberKey) { - this.fetchProtectedPackageAsync(packageGuid, memberKey, null); - } - - /// - public void fetchProtectedPackageAsync(string packageGuid, string memberKey, object userState) { - if ((this.fetchProtectedPackageOperationCompleted == null)) { - this.fetchProtectedPackageOperationCompleted = new System.Threading.SendOrPostCallback(this.OnfetchProtectedPackageOperationCompleted); - } - this.InvokeAsync("fetchProtectedPackage", new object[] { - packageGuid, - memberKey}, this.fetchProtectedPackageOperationCompleted, userState); - } - - private void OnfetchProtectedPackageOperationCompleted(object arg) { - if ((this.fetchProtectedPackageCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.fetchProtectedPackageCompleted(this, new fetchProtectedPackageCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/SubmitPackage", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public SubmitStatus SubmitPackage(string repositoryGuid, string authorGuid, string packageGuid, [System.Xml.Serialization.XmlElementAttribute(DataType="base64Binary")] byte[] packageFile, [System.Xml.Serialization.XmlElementAttribute(DataType="base64Binary")] byte[] packageDoc, [System.Xml.Serialization.XmlElementAttribute(DataType="base64Binary")] byte[] packageThumbnail, string name, string author, string authorUrl, string description) { - object[] results = this.Invoke("SubmitPackage", new object[] { - repositoryGuid, - authorGuid, - packageGuid, - packageFile, - packageDoc, - packageThumbnail, - name, - author, - authorUrl, - description}); - return ((SubmitStatus)(results[0])); - } - - /// - public void SubmitPackageAsync(string repositoryGuid, string authorGuid, string packageGuid, byte[] packageFile, byte[] packageDoc, byte[] packageThumbnail, string name, string author, string authorUrl, string description) { - this.SubmitPackageAsync(repositoryGuid, authorGuid, packageGuid, packageFile, packageDoc, packageThumbnail, name, author, authorUrl, description, null); - } - - /// - public void SubmitPackageAsync(string repositoryGuid, string authorGuid, string packageGuid, byte[] packageFile, byte[] packageDoc, byte[] packageThumbnail, string name, string author, string authorUrl, string description, object userState) { - if ((this.SubmitPackageOperationCompleted == null)) { - this.SubmitPackageOperationCompleted = new System.Threading.SendOrPostCallback(this.OnSubmitPackageOperationCompleted); - } - this.InvokeAsync("SubmitPackage", new object[] { - repositoryGuid, - authorGuid, - packageGuid, - packageFile, - packageDoc, - packageThumbnail, - name, - author, - authorUrl, - description}, this.SubmitPackageOperationCompleted, userState); - } - - private void OnSubmitPackageOperationCompleted(object arg) { - if ((this.SubmitPackageCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.SubmitPackageCompleted(this, new SubmitPackageCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://packages.umbraco.org/webservices/PackageByGuid", RequestNamespace="http://packages.umbraco.org/webservices/", ResponseNamespace="http://packages.umbraco.org/webservices/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] - public Package PackageByGuid(string packageGuid) { - object[] results = this.Invoke("PackageByGuid", new object[] { - packageGuid}); - return ((Package)(results[0])); - } - - /// - public void PackageByGuidAsync(string packageGuid) { - this.PackageByGuidAsync(packageGuid, null); - } - - /// - public void PackageByGuidAsync(string packageGuid, object userState) { - if ((this.PackageByGuidOperationCompleted == null)) { - this.PackageByGuidOperationCompleted = new System.Threading.SendOrPostCallback(this.OnPackageByGuidOperationCompleted); - } - this.InvokeAsync("PackageByGuid", new object[] { - packageGuid}, this.PackageByGuidOperationCompleted, userState); - } - - private void OnPackageByGuidOperationCompleted(object arg) { - if ((this.PackageByGuidCompleted != null)) { - System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg)); - this.PackageByGuidCompleted(this, new PackageByGuidCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState)); - } - } - - /// - public new void CancelAsync(object userState) { - base.CancelAsync(userState); - } - - private bool IsLocalFileSystemWebService(string url) { - if (((url == null) - || (url == string.Empty))) { - return false; - } - System.Uri wsUri = new System.Uri(url); - if (((wsUri.Port >= 1024) - && (string.Compare(wsUri.Host, "localHost", System.StringComparison.OrdinalIgnoreCase) == 0))) { - return true; - } - return false; - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.7.3062.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://packages.umbraco.org/webservices/")] - public partial class Category { - - private string textField; - - private string descriptionField; - - private string urlField; - - private int idField; - - private Package[] packagesField; - - /// - public string Text { - get { - return this.textField; - } - set { - this.textField = value; - } - } - - /// - public string Description { - get { - return this.descriptionField; - } - set { - this.descriptionField = value; - } - } - - /// - public string Url { - get { - return this.urlField; - } - set { - this.urlField = value; - } - } - - /// - public int Id { - get { - return this.idField; - } - set { - this.idField = value; - } - } - - /// - public Package[] Packages { - get { - return this.packagesField; - } - set { - this.packagesField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.7.3062.0")] - [System.SerializableAttribute()] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://packages.umbraco.org/webservices/")] - public partial class Package { - - private System.Guid repoGuidField; - - private string textField; - - private string descriptionField; - - private string iconField; - - private string thumbnailField; - - private string documentationField; - - private string demoField; - - private bool acceptedField; - - private bool isModuleField; - - private bool editorsPickField; - - private bool protectedField; - - private bool hasUpgradeField; - - private string upgradeVersionField; - - private string upgradeReadMeField; - - private string urlField; - - /// - public System.Guid RepoGuid { - get { - return this.repoGuidField; - } - set { - this.repoGuidField = value; - } - } - - /// - public string Text { - get { - return this.textField; - } - set { - this.textField = value; - } - } - - /// - public string Description { - get { - return this.descriptionField; - } - set { - this.descriptionField = value; - } - } - - /// - public string Icon { - get { - return this.iconField; - } - set { - this.iconField = value; - } - } - - /// - public string Thumbnail { - get { - return this.thumbnailField; - } - set { - this.thumbnailField = value; - } - } - - /// - public string Documentation { - get { - return this.documentationField; - } - set { - this.documentationField = value; - } - } - - /// - public string Demo { - get { - return this.demoField; - } - set { - this.demoField = value; - } - } - - /// - public bool Accepted { - get { - return this.acceptedField; - } - set { - this.acceptedField = value; - } - } - - /// - public bool IsModule { - get { - return this.isModuleField; - } - set { - this.isModuleField = value; - } - } - - /// - public bool EditorsPick { - get { - return this.editorsPickField; - } - set { - this.editorsPickField = value; - } - } - - /// - public bool Protected { - get { - return this.protectedField; - } - set { - this.protectedField = value; - } - } - - /// - public bool HasUpgrade { - get { - return this.hasUpgradeField; - } - set { - this.hasUpgradeField = value; - } - } - - /// - public string UpgradeVersion { - get { - return this.upgradeVersionField; - } - set { - this.upgradeVersionField = value; - } - } - - /// - public string UpgradeReadMe { - get { - return this.upgradeReadMeField; - } - set { - this.upgradeReadMeField = value; - } - } - - /// - public string Url { - get { - return this.urlField; - } - set { - this.urlField = value; - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.7.3062.0")] - [System.SerializableAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://packages.umbraco.org/webservices/")] - public enum SubmitStatus { - - /// - Complete, - - /// - Exists, - - /// - NoAccess, - - /// - Error, - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - public delegate void CategoriesCompletedEventHandler(object sender, CategoriesCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class CategoriesCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal CategoriesCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public Category[] Result { - get { - this.RaiseExceptionIfNecessary(); - return ((Category[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - public delegate void ModulesCompletedEventHandler(object sender, ModulesCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class ModulesCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal ModulesCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public Package[] Result { - get { - this.RaiseExceptionIfNecessary(); - return ((Package[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - public delegate void ModulesCategorizedCompletedEventHandler(object sender, ModulesCategorizedCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class ModulesCategorizedCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal ModulesCategorizedCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public Category[] Result { - get { - this.RaiseExceptionIfNecessary(); - return ((Category[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - public delegate void NitrosCompletedEventHandler(object sender, NitrosCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class NitrosCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal NitrosCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public Package[] Result { - get { - this.RaiseExceptionIfNecessary(); - return ((Package[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - public delegate void NitrosCategorizedCompletedEventHandler(object sender, NitrosCategorizedCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class NitrosCategorizedCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal NitrosCategorizedCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public Category[] Result { - get { - this.RaiseExceptionIfNecessary(); - return ((Category[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - public delegate void authenticateCompletedEventHandler(object sender, authenticateCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class authenticateCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal authenticateCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public string Result { - get { - this.RaiseExceptionIfNecessary(); - return ((string)(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - public delegate void fetchPackageCompletedEventHandler(object sender, fetchPackageCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class fetchPackageCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal fetchPackageCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public byte[] Result { - get { - this.RaiseExceptionIfNecessary(); - return ((byte[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - public delegate void fetchPackageByVersionCompletedEventHandler(object sender, fetchPackageByVersionCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class fetchPackageByVersionCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal fetchPackageByVersionCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public byte[] Result { - get { - this.RaiseExceptionIfNecessary(); - return ((byte[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - public delegate void fetchProtectedPackageCompletedEventHandler(object sender, fetchProtectedPackageCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class fetchProtectedPackageCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal fetchProtectedPackageCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public byte[] Result { - get { - this.RaiseExceptionIfNecessary(); - return ((byte[])(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - public delegate void SubmitPackageCompletedEventHandler(object sender, SubmitPackageCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class SubmitPackageCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal SubmitPackageCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public SubmitStatus Result { - get { - this.RaiseExceptionIfNecessary(); - return ((SubmitStatus)(this.results[0])); - } - } - } - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - public delegate void PackageByGuidCompletedEventHandler(object sender, PackageByGuidCompletedEventArgs e); - - /// - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.7.3062.0")] - [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.ComponentModel.DesignerCategoryAttribute("code")] - public partial class PackageByGuidCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { - - private object[] results; - - internal PackageByGuidCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : - base(exception, cancelled, userState) { - this.results = results; - } - - /// - public Package Result { - get { - this.RaiseExceptionIfNecessary(); - return ((Package)(this.results[0])); - } - } - } -} - -#pragma warning restore 1591 \ No newline at end of file diff --git a/src/Umbraco.Web/Web References/org.umbraco.our/Reference.map b/src/Umbraco.Web/Web References/org.umbraco.our/Reference.map deleted file mode 100644 index 8d133063a5..0000000000 --- a/src/Umbraco.Web/Web References/org.umbraco.our/Reference.map +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web/Web References/org.umbraco.our/repository.disco b/src/Umbraco.Web/Web References/org.umbraco.our/repository.disco deleted file mode 100644 index 4bf1a61fca..0000000000 --- a/src/Umbraco.Web/Web References/org.umbraco.our/repository.disco +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web/Web References/org.umbraco.our/repository.wsdl b/src/Umbraco.Web/Web References/org.umbraco.our/repository.wsdl deleted file mode 100644 index 2a3f3502db..0000000000 --- a/src/Umbraco.Web/Web References/org.umbraco.our/repository.wsdl +++ /dev/null @@ -1,995 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From ca75a258029bf076ed1c00bbeb7e7456da2f10cd Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 24 Jul 2019 11:32:18 +0200 Subject: [PATCH 191/776] Makes checks a little more robust --- .../PropertyEditors/TagsPropertyEditor.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs index b7101aa764..578b6fcd00 100644 --- a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs @@ -39,14 +39,23 @@ namespace Umbraco.Web.PropertyEditors /// public override object FromEditor(ContentPropertyData editorValue, object currentValue) { + var value = editorValue?.Value?.ToString(); + + if (string.IsNullOrEmpty(value)) + { + return null; + } + if (editorValue.Value is JArray json) { return json.Select(x => x.Value()); } - else if ( editorValue.Value is string stringValue && !String.IsNullOrWhiteSpace(stringValue)) + + if (string.IsNullOrWhiteSpace(value) == false) { - return stringValue.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + return value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); } + return null; } From 361557dc39969299edb0f57d89b47d0e76d31369 Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Sun, 21 Jul 2019 15:19:17 +0100 Subject: [PATCH 192/776] Handle CSV values in TagsPropertyEditor (cherry picked from commit a97604d6c405c7ebf6372f69807193e8cbbc7a50) --- .../PropertyEditors/TagsPropertyEditor.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs index 90527a8b8d..b7101aa764 100644 --- a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using Newtonsoft.Json.Linq; @@ -38,9 +39,15 @@ namespace Umbraco.Web.PropertyEditors /// public override object FromEditor(ContentPropertyData editorValue, object currentValue) { - return editorValue.Value is JArray json - ? json.Select(x => x.Value()) - : null; + if (editorValue.Value is JArray json) + { + return json.Select(x => x.Value()); + } + else if ( editorValue.Value is string stringValue && !String.IsNullOrWhiteSpace(stringValue)) + { + return stringValue.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + } + return null; } /// From 17527c99ebbf80de6ec2d5e75a044eca717a5be9 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 24 Jul 2019 11:32:18 +0200 Subject: [PATCH 193/776] Makes checks a little more robust (cherry picked from commit ca75a258029bf076ed1c00bbeb7e7456da2f10cd) --- .../PropertyEditors/TagsPropertyEditor.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs index b7101aa764..578b6fcd00 100644 --- a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs @@ -39,14 +39,23 @@ namespace Umbraco.Web.PropertyEditors /// public override object FromEditor(ContentPropertyData editorValue, object currentValue) { + var value = editorValue?.Value?.ToString(); + + if (string.IsNullOrEmpty(value)) + { + return null; + } + if (editorValue.Value is JArray json) { return json.Select(x => x.Value()); } - else if ( editorValue.Value is string stringValue && !String.IsNullOrWhiteSpace(stringValue)) + + if (string.IsNullOrWhiteSpace(value) == false) { - return stringValue.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + return value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); } + return null; } From efd6bd82bd9f31c32d2b507cb79e140ff070129e Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 12 Jul 2019 11:23:36 +1000 Subject: [PATCH 194/776] Revert "V8: Don't show multiple open menus (take two) (#5609)" This reverts commit 9ce996cbba49199423f4e79e223497d5c3c8cd4f. (cherry picked from commit 1d49c8626ec40519a19764959b18efb2868d8f61) --- .../components/events/events.directive.js | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js index 47e6818466..15e74bbd90 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js @@ -101,14 +101,14 @@ angular.module('umbraco.directives') var eventBindings = []; function oneTimeClick(event) { - // ignore clicks on button groups toggles (i.e. the save and publish button) - var parents = $(event.target).closest("[data-element='button-group-toggle']"); - if (parents.length > 0) { - return; - } + var el = event.target.nodeName; + + //ignore link and button clicks + var els = ["INPUT", "A", "BUTTON"]; + if (els.indexOf(el) >= 0) { return; } // ignore clicks on new overlay - parents = $(event.target).parents(".umb-overlay,.umb-tour"); + var parents = $(event.target).parents("a,button,.umb-overlay,.umb-tour"); if (parents.length > 0) { return; } @@ -131,12 +131,6 @@ angular.module('umbraco.directives') return; } - // ignore clicks on dialog actions - var actions = $(event.target).parents(".umb-action"); - if (actions.length === 1) { - return; - } - //ignore clicks inside this element if ($(element).has($(event.target)).length > 0) { return; From 7bc7dba86d4d7bd3d45f8b73c53006cc7f3ef61f Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 24 Jul 2019 21:46:19 +1000 Subject: [PATCH 195/776] fixes timing issue --- .../Web/PublishedContentQueryTests.cs | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs b/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs index 2cff946372..a3505aeb0e 100644 --- a/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs +++ b/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs @@ -36,22 +36,26 @@ namespace Umbraco.Tests.Web { var indexer = new TestIndex("TestIndex", luceneDirectory, fieldNames); - //populate with some test data - indexer.IndexItem(new ValueSet("1", "content", new Dictionary + using (indexer.ProcessNonAsync()) { - [fieldNames[0]] = "Hello world, there are products here", - [UmbracoContentIndex.VariesByCultureFieldName] = "n" - })); - indexer.IndexItem(new ValueSet("2", "content", new Dictionary - { - [fieldNames[1]] = "Hello world, there are products here", - [UmbracoContentIndex.VariesByCultureFieldName] = "y" - })); - indexer.IndexItem(new ValueSet("3", "content", new Dictionary - { - [fieldNames[2]] = "Hello world, there are products here", - [UmbracoContentIndex.VariesByCultureFieldName] = "y" - })); + //populate with some test data + indexer.IndexItem(new ValueSet("1", "content", new Dictionary + { + [fieldNames[0]] = "Hello world, there are products here", + [UmbracoContentIndex.VariesByCultureFieldName] = "n" + })); + indexer.IndexItem(new ValueSet("2", "content", new Dictionary + { + [fieldNames[1]] = "Hello world, there are products here", + [UmbracoContentIndex.VariesByCultureFieldName] = "y" + })); + indexer.IndexItem(new ValueSet("3", "content", new Dictionary + { + [fieldNames[2]] = "Hello world, there are products here", + [UmbracoContentIndex.VariesByCultureFieldName] = "y" + })); + } + return indexer; } @@ -80,7 +84,7 @@ namespace Umbraco.Tests.Web { var fieldNames = new[] { "title", "title_en-us", "title_fr-fr" }; using (var indexer = CreateTestIndex(luceneDir, fieldNames)) - { + { var pcq = CreatePublishedContentQuery(indexer); var results = pcq.Search("Products", culture, "TestIndex"); From 0ab84fc44357928e580329f19a6ede5da9ce365a Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 25 Jul 2019 10:28:16 +0200 Subject: [PATCH 196/776] Allow removing user start nodes --- .../src/views/users/views/user/details.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html index 793f404f0c..881d7a9c0d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html @@ -90,6 +90,7 @@ ng-repeat="node in model.user.startContentIds" icon="node.icon" name="node.name" + allow-remove="true" on-remove="model.removeSelectedItem($index, model.user.startContentIds)"> @@ -114,6 +115,7 @@ ng-repeat="node in model.user.startMediaIds" icon="node.icon" name="node.name" + allow-remove="true" on-remove="model.removeSelectedItem($index, model.user.startMediaIds)"> From 4a24064783efb45f7eed1db9833ca1f8b9012f3d Mon Sep 17 00:00:00 2001 From: arkadiuszbiel Date: Sat, 27 Jul 2019 20:41:15 +0100 Subject: [PATCH 197/776] Value Set builder shouldn't stop indexing when one field is empty in value sets --- src/Umbraco.Examine/BaseValueSetBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Examine/BaseValueSetBuilder.cs b/src/Umbraco.Examine/BaseValueSetBuilder.cs index 22d379d148..93cee88231 100644 --- a/src/Umbraco.Examine/BaseValueSetBuilder.cs +++ b/src/Umbraco.Examine/BaseValueSetBuilder.cs @@ -45,7 +45,7 @@ namespace Umbraco.Examine continue; case string strVal: { - if (strVal.IsNullOrWhiteSpace()) return; + if (strVal.IsNullOrWhiteSpace()) continue; var key = $"{keyVal.Key}{cultureSuffix}"; if (values.TryGetValue(key, out var v)) values[key] = new List(v) { val }.ToArray(); From 41093e5b8b59ecd7a71dae7ec3fddb5e672e9a2d Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 29 Jul 2019 15:38:08 +1000 Subject: [PATCH 198/776] Value Set builder shouldn't stop indexing when one field is empty in value sets --- src/Umbraco.Examine/BaseValueSetBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Examine/BaseValueSetBuilder.cs b/src/Umbraco.Examine/BaseValueSetBuilder.cs index 22d379d148..93cee88231 100644 --- a/src/Umbraco.Examine/BaseValueSetBuilder.cs +++ b/src/Umbraco.Examine/BaseValueSetBuilder.cs @@ -45,7 +45,7 @@ namespace Umbraco.Examine continue; case string strVal: { - if (strVal.IsNullOrWhiteSpace()) return; + if (strVal.IsNullOrWhiteSpace()) continue; var key = $"{keyVal.Key}{cultureSuffix}"; if (values.TryGetValue(key, out var v)) values[key] = new List(v) { val }.ToArray(); From 4612c036576468ad6f24240686b823034cf7254f Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 29 Jul 2019 15:43:03 +1000 Subject: [PATCH 199/776] Fixes #5789 - PublishedContentQuery Search always searches on culture --- src/Umbraco.Examine/ExamineExtensions.cs | 25 +++++ .../TestHelpers/RandomIdRamDirectory.cs | 22 +++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 2 + .../Web/PublishedContentQueryTests.cs | 99 +++++++++++++++++++ src/Umbraco.Tests/Web/UmbracoHelperTests.cs | 10 +- src/Umbraco.Web/IPublishedContentQuery.cs | 16 ++- src/Umbraco.Web/PublishedContentQuery.cs | 40 +++++--- src/Umbraco.Web/UmbracoContextFactory.cs | 8 ++ 8 files changed, 203 insertions(+), 19 deletions(-) create mode 100644 src/Umbraco.Tests/TestHelpers/RandomIdRamDirectory.cs create mode 100644 src/Umbraco.Tests/Web/PublishedContentQueryTests.cs diff --git a/src/Umbraco.Examine/ExamineExtensions.cs b/src/Umbraco.Examine/ExamineExtensions.cs index 43a3ccc196..376950c95e 100644 --- a/src/Umbraco.Examine/ExamineExtensions.cs +++ b/src/Umbraco.Examine/ExamineExtensions.cs @@ -48,6 +48,31 @@ namespace Umbraco.Examine } } + /// + /// Returns all index fields that are culture specific (suffixed) or invariant + /// + /// + /// + /// + public static IEnumerable GetCultureAndInvariantFields(this IUmbracoIndex index, string culture) + { + var allFields = index.GetFields(); + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var field in allFields) + { + var match = CultureIsoCodeFieldNameMatchExpression.Match(field); + if (match.Success && match.Groups.Count == 3 && culture.InvariantEquals(match.Groups[2].Value)) + { + yield return field; //matches this culture field + } + else if (!match.Success) + { + yield return field; //matches no culture field (invariant) + } + + } + } + internal static bool TryParseLuceneQuery(string query) { // TODO: I'd assume there would be a more strict way to parse the query but not that i can find yet, for now we'll diff --git a/src/Umbraco.Tests/TestHelpers/RandomIdRamDirectory.cs b/src/Umbraco.Tests/TestHelpers/RandomIdRamDirectory.cs new file mode 100644 index 0000000000..34904db1ae --- /dev/null +++ b/src/Umbraco.Tests/TestHelpers/RandomIdRamDirectory.cs @@ -0,0 +1,22 @@ +using Lucene.Net.Store; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Tests.TestHelpers +{ + + /// + /// Used for tests with Lucene so that each RAM directory is unique + /// + public class RandomIdRAMDirectory : RAMDirectory + { + private readonly string _lockId = Guid.NewGuid().ToString(); + public override string GetLockId() + { + return _lockId; + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index f41ff1dd07..717006b702 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -157,6 +157,7 @@ + @@ -268,6 +269,7 @@ + diff --git a/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs b/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs new file mode 100644 index 0000000000..a3505aeb0e --- /dev/null +++ b/src/Umbraco.Tests/Web/PublishedContentQueryTests.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Examine; +using Examine.LuceneEngine.Providers; +using Lucene.Net.Store; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Examine; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web; +using Umbraco.Web.PublishedCache; + +namespace Umbraco.Tests.Web +{ + [TestFixture] + public class PublishedContentQueryTests + { + + private class TestIndex : LuceneIndex, IUmbracoIndex + { + private readonly string[] _fieldNames; + + public TestIndex(string name, Directory luceneDirectory, string[] fieldNames) + : base(name, luceneDirectory, null, null, null, null) + { + _fieldNames = fieldNames; + } + public bool EnableDefaultEventHandler => throw new NotImplementedException(); + public bool PublishedValuesOnly => throw new NotImplementedException(); + public IEnumerable GetFields() => _fieldNames; + } + + private TestIndex CreateTestIndex(Directory luceneDirectory, string[] fieldNames) + { + var indexer = new TestIndex("TestIndex", luceneDirectory, fieldNames); + + using (indexer.ProcessNonAsync()) + { + //populate with some test data + indexer.IndexItem(new ValueSet("1", "content", new Dictionary + { + [fieldNames[0]] = "Hello world, there are products here", + [UmbracoContentIndex.VariesByCultureFieldName] = "n" + })); + indexer.IndexItem(new ValueSet("2", "content", new Dictionary + { + [fieldNames[1]] = "Hello world, there are products here", + [UmbracoContentIndex.VariesByCultureFieldName] = "y" + })); + indexer.IndexItem(new ValueSet("3", "content", new Dictionary + { + [fieldNames[2]] = "Hello world, there are products here", + [UmbracoContentIndex.VariesByCultureFieldName] = "y" + })); + } + + return indexer; + } + + private PublishedContentQuery CreatePublishedContentQuery(IIndex indexer) + { + var examineManager = new Mock(); + IIndex outarg = indexer; + examineManager.Setup(x => x.TryGetIndex("TestIndex", out outarg)).Returns(true); + + var contentCache = new Mock(); + contentCache.Setup(x => x.GetById(It.IsAny())).Returns((int intId) => Mock.Of(x => x.Id == intId)); + var snapshot = Mock.Of(x => x.Content == contentCache.Object); + var variationContext = new VariationContext(); + var variationContextAccessor = Mock.Of(x => x.VariationContext == variationContext); + + return new PublishedContentQuery(snapshot, variationContextAccessor, examineManager.Object); + } + + [TestCase("fr-fr", ExpectedResult = "1, 3", TestName = "Search Culture: fr-fr. Must return both fr-fr and invariant results")] + [TestCase("en-us", ExpectedResult = "1, 2", TestName = "Search Culture: en-us. Must return both en-us and invariant results")] + [TestCase("*", ExpectedResult = "1, 2, 3", TestName = "Search Culture: *. Must return all cultures and all invariant results")] + [TestCase(null, ExpectedResult = "1", TestName = "Search Culture: null. Must return only invariant results")] + public string Search(string culture) + { + using (var luceneDir = new RandomIdRAMDirectory()) + { + var fieldNames = new[] { "title", "title_en-us", "title_fr-fr" }; + using (var indexer = CreateTestIndex(luceneDir, fieldNames)) + { + var pcq = CreatePublishedContentQuery(indexer); + + var results = pcq.Search("Products", culture, "TestIndex"); + + var ids = results.Select(x => x.Content.Id).ToArray(); + + return string.Join(", ", ids); + } + } + } + } +} diff --git a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs index b23b5bd6b7..26d85f60cf 100644 --- a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs @@ -1,5 +1,8 @@ using System; using System.Text; +using Examine.LuceneEngine; +using Lucene.Net.Analysis; +using Lucene.Net.Analysis.Standard; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -13,18 +16,19 @@ using Umbraco.Web; namespace Umbraco.Tests.Web { + [TestFixture] public class UmbracoHelperTests - { + { [TearDown] public void TearDown() { Current.Reset(); } - - + + // ------- Int32 conversion tests [Test] public static void Converting_Boxed_34_To_An_Int_Returns_34() diff --git a/src/Umbraco.Web/IPublishedContentQuery.cs b/src/Umbraco.Web/IPublishedContentQuery.cs index 76e7be5e97..8a8d678aba 100644 --- a/src/Umbraco.Web/IPublishedContentQuery.cs +++ b/src/Umbraco.Web/IPublishedContentQuery.cs @@ -39,10 +39,14 @@ namespace Umbraco.Web /// Optional culture. /// Optional index name. /// - /// When the is not specified, all cultures are searched. + /// + /// When the is not specified or is *, all cultures are searched. + /// To search for only invariant documents and fields use null. + /// When searching on a specific culture, all culture specific fields are searched for the provided culture and all invariant fields for all documents. + /// /// While enumerating results, the ambient culture is changed to be the searched culture. /// - IEnumerable Search(string term, string culture = null, string indexName = null); + IEnumerable Search(string term, string culture = "*", string indexName = null); /// /// Searches content. @@ -54,10 +58,14 @@ namespace Umbraco.Web /// Optional culture. /// Optional index name. /// - /// When the is not specified, all cultures are searched. + /// + /// When the is not specified or is *, all cultures are searched. + /// To search for only invariant documents and fields use null. + /// When searching on a specific culture, all culture specific fields are searched for the provided culture and all invariant fields for all documents. + /// /// While enumerating results, the ambient culture is changed to be the searched culture. /// - IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = null, string indexName = null); + IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = null); /// /// Executes the query and converts the results to PublishedSearchResult. diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 61180580cb..2dbe4de4c5 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -20,14 +20,22 @@ namespace Umbraco.Web { private readonly IPublishedSnapshot _publishedSnapshot; private readonly IVariationContextAccessor _variationContextAccessor; + private readonly IExamineManager _examineManager; + + [Obsolete("Use the constructor with all parameters instead")] + public PublishedContentQuery(IPublishedSnapshot publishedSnapshot, IVariationContextAccessor variationContextAccessor) + : this (publishedSnapshot, variationContextAccessor, ExamineManager.Instance) + { + } /// /// Initializes a new instance of the class. /// - public PublishedContentQuery(IPublishedSnapshot publishedSnapshot, IVariationContextAccessor variationContextAccessor) + public PublishedContentQuery(IPublishedSnapshot publishedSnapshot, IVariationContextAccessor variationContextAccessor, IExamineManager examineManager) { _publishedSnapshot = publishedSnapshot ?? throw new ArgumentNullException(nameof(publishedSnapshot)); _variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); + _examineManager = examineManager ?? throw new ArgumentNullException(nameof(examineManager)); } #region Content @@ -175,19 +183,19 @@ namespace Umbraco.Web #region Search /// - public IEnumerable Search(string term, string culture = null, string indexName = null) + public IEnumerable Search(string term, string culture = "*", string indexName = null) { return Search(term, 0, 0, out _, culture, indexName); } /// - public IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = null, string indexName = null) + public IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = "*", string indexName = null) { indexName = string.IsNullOrEmpty(indexName) ? Constants.UmbracoIndexes.ExternalIndexName : indexName; - if (!ExamineManager.Instance.TryGetIndex(indexName, out var index) || !(index is IUmbracoIndex umbIndex)) + if (!_examineManager.TryGetIndex(indexName, out var index) || !(index is IUmbracoIndex umbIndex)) throw new InvalidOperationException($"No index found by name {indexName} or is not of type {typeof(IUmbracoIndex)}"); var searcher = umbIndex.GetSearcher(); @@ -195,20 +203,28 @@ namespace Umbraco.Web // default to max 500 results var count = skip == 0 && take == 0 ? 500 : skip + take; - //set this to the specific culture or to the culture in the request - culture = culture ?? _variationContextAccessor.VariationContext.Culture; - ISearchResults results; - if (culture.IsNullOrWhiteSpace()) + if (culture == "*") { + //search everything + results = searcher.Search(term, count); } + else if (culture.IsNullOrWhiteSpace()) + { + //only search invariant + + var qry = searcher.CreateQuery().Field(UmbracoContentIndex.VariesByCultureFieldName, "n"); //must not vary by culture + qry = qry.And().ManagedQuery(term); + results = qry.Execute(count); + } else { + //search only the specified culture + //get all index fields suffixed with the culture name supplied - var cultureFields = umbIndex.GetCultureFields(culture); - var qry = searcher.CreateQuery().Field(UmbracoContentIndex.VariesByCultureFieldName, "y"); //must vary by culture - qry = qry.And().ManagedQuery(term, cultureFields.ToArray()); + var cultureFields = umbIndex.GetCultureAndInvariantFields(culture).ToArray(); + var qry = searcher.CreateQuery().ManagedQuery(term, cultureFields); results = qry.Execute(count); } @@ -304,7 +320,7 @@ namespace Umbraco.Web } } - + #endregion diff --git a/src/Umbraco.Web/UmbracoContextFactory.cs b/src/Umbraco.Web/UmbracoContextFactory.cs index 2a812036bf..11d8952fa6 100644 --- a/src/Umbraco.Web/UmbracoContextFactory.cs +++ b/src/Umbraco.Web/UmbracoContextFactory.cs @@ -53,7 +53,15 @@ namespace Umbraco.Web { // make sure we have a variation context if (_variationContextAccessor.VariationContext == null) + { + // TODO: By using _defaultCultureAccessor.DefaultCulture this means that the VariationContext will always return a variant culture, it will never + // return an empty string signifying that the culture is invariant. But does this matter? Are we actually expecting this to return an empty string + // for invariant routes? From what i can tell throughout the codebase is that whenever we are checking against the VariationContext.Culture we are + // also checking if the content type varies by culture or not. This is fine, however the code in the ctor of VariationContext is then misleading + // since it's assuming that the Culture can be empty (invariant) when in reality of a website this will never be empty since a real culture is always set here. _variationContextAccessor.VariationContext = new VariationContext(_defaultCultureAccessor.DefaultCulture); + } + var webSecurity = new WebSecurity(httpContext, _userService, _globalSettings); From 49b3351c72cf5e53f67f583d996d60b88ca52c22 Mon Sep 17 00:00:00 2001 From: Rasmus John Pedersen Date: Mon, 29 Jul 2019 12:59:29 +0200 Subject: [PATCH 200/776] Fix exception when stored media url is already absolute --- src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs | 13 +++++++++++++ src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs index 5af48e64b1..6489417dc7 100644 --- a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs @@ -81,6 +81,19 @@ namespace Umbraco.Tests.Routing Assert.AreEqual(expected, resolvedUrl); } + [Test] + public void Get_Media_Url_Returns_Absolute_Url_If_Stored_Url_Is_Absolute() + { + const string expected = "http://localhost/media/rfeiw584/test.jpg"; + + var umbracoContext = GetUmbracoContext("http://localhost", mediaUrlProviders: new[] { _mediaUrlProvider }); + var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, expected, null); + + var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, UrlMode.Relative); + + Assert.AreEqual(expected, resolvedUrl); + } + [Test] public void Get_Media_Url_Returns_Empty_String_When_PropertyType_Is_Not_Supported() { diff --git a/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs index 18d10e577d..02dc4ebf29 100644 --- a/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs @@ -45,6 +45,10 @@ namespace Umbraco.Web.Routing if (string.IsNullOrEmpty(path)) return null; + // the stored path is absolute so we just return it as is + if(Uri.IsWellFormedUriString(path, UriKind.Absolute)) + return new Uri(path); + Uri uri; if (current == null) From ca56655d07f86d5b21abb2f6d5560e98be2d05e5 Mon Sep 17 00:00:00 2001 From: Rasmus John Pedersen Date: Mon, 29 Jul 2019 12:59:29 +0200 Subject: [PATCH 201/776] Fix exception when stored media url is already absolute (cherry picked from commit 49b3351c72cf5e53f67f583d996d60b88ca52c22) --- src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs | 13 +++++++++++++ src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs index 5af48e64b1..6489417dc7 100644 --- a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs @@ -81,6 +81,19 @@ namespace Umbraco.Tests.Routing Assert.AreEqual(expected, resolvedUrl); } + [Test] + public void Get_Media_Url_Returns_Absolute_Url_If_Stored_Url_Is_Absolute() + { + const string expected = "http://localhost/media/rfeiw584/test.jpg"; + + var umbracoContext = GetUmbracoContext("http://localhost", mediaUrlProviders: new[] { _mediaUrlProvider }); + var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.UploadField, expected, null); + + var resolvedUrl = umbracoContext.UrlProvider.GetMediaUrl(publishedContent, UrlMode.Relative); + + Assert.AreEqual(expected, resolvedUrl); + } + [Test] public void Get_Media_Url_Returns_Empty_String_When_PropertyType_Is_Not_Supported() { diff --git a/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs index 18d10e577d..02dc4ebf29 100644 --- a/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultMediaUrlProvider.cs @@ -45,6 +45,10 @@ namespace Umbraco.Web.Routing if (string.IsNullOrEmpty(path)) return null; + // the stored path is absolute so we just return it as is + if(Uri.IsWellFormedUriString(path, UriKind.Absolute)) + return new Uri(path); + Uri uri; if (current == null) From 365ad2981d7cfcf7bfd01fa620bb53f1208e94db Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 30 Jul 2019 19:02:03 +1000 Subject: [PATCH 202/776] Fixing issue when changing a property type from invariant to variant and back again and the document always reporting pending changes. --- .../Persistence/Factories/PropertyFactory.cs | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index f1473b5888..f0937a3781 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.Factories public static IEnumerable BuildEntities(PropertyType[] propertyTypes, IReadOnlyCollection dtos, int publishedVersionId, ILanguageRepository languageRepository) { var properties = new List(); - var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable) x); + var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable)x); foreach (var propertyType in propertyTypes) { @@ -130,6 +130,9 @@ namespace Umbraco.Core.Persistence.Factories // publishing = deal with edit and published values foreach (var propertyValue in property.Values) { + var isInvariantValue = propertyValue.Culture == null; + var isCultureValue = propertyValue.Culture != null && propertyValue.Segment == null; + // deal with published value if (propertyValue.PublishedValue != null && publishedVersionId > 0) propertyDataDtos.Add(BuildDto(publishedVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue)); @@ -138,26 +141,34 @@ namespace Umbraco.Core.Persistence.Factories if (propertyValue.EditedValue != null) propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue)); + // property.Values will contain ALL of it's values, both variant and invariant which will be populated if the administrator has previously + // changed the property type to be variant vs invariant. + // We need to check for this scenario here because otherwise the editedCultures and edited flags + // will end up incorrectly so here we need to only process edited cultures based on the + // current value type and how the property varies. + + if (property.PropertyType.VariesByCulture() && isInvariantValue) continue; + if (!property.PropertyType.VariesByCulture() && isCultureValue) continue; + // use explicit equals here, else object comparison fails at comparing eg strings var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue); + edited |= !sameValues; - if (entityVariesByCulture // cultures can be edited, ie CultureNeutral is supported - && propertyValue.Culture != null && propertyValue.Segment == null // and value is CultureNeutral - && !sameValues) // and edited and published are different + if (entityVariesByCulture && !sameValues) { - editedCultures.Add(propertyValue.Culture); // report culture as edited - } + if (isCultureValue) + { + editedCultures.Add(propertyValue.Culture); // report culture as edited + } + else if (isInvariantValue) + { + // flag culture as edited if it contains an edited invariant property + if (defaultCulture == null) + defaultCulture = languageRepository.GetDefaultIsoCode(); - // flag culture as edited if it contains an edited invariant property - if (propertyValue.Culture == null //invariant property - && !sameValues // and edited and published are different - && entityVariesByCulture) //only when the entity is variant - { - if (defaultCulture == null) - defaultCulture = languageRepository.GetDefaultIsoCode(); - - editedCultures.Add(defaultCulture); + editedCultures.Add(defaultCulture); + } } } } @@ -167,7 +178,7 @@ namespace Umbraco.Core.Persistence.Factories { // not publishing = only deal with edit values if (propertyValue.EditedValue != null) - propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue)); + propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue)); } edited = true; } From 3930ae244c7c07f4d245c4f6c58d3ce767a9be90 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 24 Jul 2019 19:40:51 +1000 Subject: [PATCH 203/776] Created unit test to show the problem. --- .../Services/ContentServiceTests.cs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 222f40aeed..a719e04b2a 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -761,6 +761,61 @@ namespace Umbraco.Tests.Services } + [Test] + public void Unpublish_All_Cultures_Has_Unpublished_State() + { + // Arrange + + var langUk = new Language("en-GB") { IsDefault = true }; + var langFr = new Language("fr-FR"); + + ServiceContext.LocalizationService.Save(langFr); + ServiceContext.LocalizationService.Save(langUk); + + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + + IContent content = new Content("content", Constants.System.Root, contentType); + content.SetCultureName("content-fr", langFr.IsoCode); + content.SetCultureName("content-en", langUk.IsoCode); + + var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode }); + Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); + Assert.IsTrue(published.Success); + Assert.AreEqual(PublishedState.Published, content.PublishedState); + + //re-get + content = ServiceContext.ContentService.GetById(content.Id); + Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); + Assert.AreEqual(PublishedState.Published, content.PublishedState); + + var unpublished = ServiceContext.ContentService.Unpublish(content, langFr.IsoCode); + Assert.IsTrue(unpublished.Success); + Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result); + Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); + Assert.AreEqual(PublishedState.Published, content.PublishedState); //still published + + //re-get + content = ServiceContext.ContentService.GetById(content.Id); + Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); + + unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); + Assert.IsTrue(unpublished.Success); + Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result); + Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode)); + Assert.AreEqual(PublishedState.Unpublished, content.PublishedState); //the last culture was unpublished so the document should also reflect this + + //re-get + content = ServiceContext.ContentService.GetById(content.Id); + Assert.AreEqual(PublishedState.Unpublished, content.PublishedState); //just double checking + } + [Test] public void Pending_Invariant_Property_Changes_Affect_Default_Language_Edited_State() { From 1bfcc9053ebbe5f35321f89194b08e75d5837a31 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 24 Jul 2019 23:59:31 +1000 Subject: [PATCH 204/776] Fixes issues: we never properly used SuccessUnpublishMandatoryCulture, fixed issue of unpublishing last culture with a new status --- src/Umbraco.Core/Exceptions/PanicException.cs | 28 +++ .../Services/Implement/ContentService.cs | 172 +++++++++++++----- .../Services/PublishResultType.cs | 7 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Services/ContentServiceTests.cs | 38 +++- 5 files changed, 199 insertions(+), 47 deletions(-) create mode 100644 src/Umbraco.Core/Exceptions/PanicException.cs diff --git a/src/Umbraco.Core/Exceptions/PanicException.cs b/src/Umbraco.Core/Exceptions/PanicException.cs new file mode 100644 index 0000000000..4d41cafc65 --- /dev/null +++ b/src/Umbraco.Core/Exceptions/PanicException.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Exceptions +{ + /// + /// Internal exception that in theory should never ben thrown, it is only thrown in circumstances that should never happen + /// + [Serializable] + internal class PanicException : Exception + { + public PanicException() + { + } + + public PanicException(string message) : base(message) + { + } + + public PanicException(string message, Exception innerException) : base(message, innerException) + { + } + + protected PanicException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index e49dcf4a12..b4e5f26de5 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -886,6 +886,8 @@ namespace Umbraco.Core.Services.Implement { scope.WriteLock(Constants.Locks.ContentTree); + var allLangs = _languageRepository.GetMany().ToList(); + var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs); if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); @@ -894,13 +896,13 @@ namespace Umbraco.Core.Services.Implement // if culture is '*', then publish them all (including variants) //this will create the correct culture impact even if culture is * or null - var impact = CultureImpact.Create(culture, _languageRepository.IsDefault(culture), content); + var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content); // publish the culture(s) // we don't care about the response here, this response will be rechecked below but we need to set the culture info values now. content.PublishCulture(impact); - var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents); + var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents); scope.Complete(); return result; } @@ -921,6 +923,8 @@ namespace Umbraco.Core.Services.Implement { scope.WriteLock(Constants.Locks.ContentTree); + var allLangs = _languageRepository.GetMany().ToList(); + var evtMsgs = EventMessagesFactory.Get(); var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs); if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) @@ -939,14 +943,14 @@ namespace Umbraco.Core.Services.Implement if (cultures.Any(x => x == null || x == "*")) throw new InvalidOperationException("Only valid cultures are allowed to be used in this method, wildcards or nulls are not allowed"); - var impacts = cultures.Select(x => CultureImpact.Explicit(x, _languageRepository.IsDefault(x))); + var impacts = cultures.Select(x => CultureImpact.Explicit(x, IsDefaultCulture(allLangs, x))); // publish the culture(s) // we don't care about the response here, this response will be rechecked below but we need to set the culture info values now. foreach (var impact in impacts) content.PublishCulture(impact); - var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents); + var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents); scope.Complete(); return result; } @@ -986,6 +990,8 @@ namespace Umbraco.Core.Services.Implement { scope.WriteLock(Constants.Locks.ContentTree); + var allLangs = _languageRepository.GetMany().ToList(); + var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs); if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); @@ -993,22 +999,39 @@ namespace Umbraco.Core.Services.Implement // all cultures = unpublish whole if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null)) { + // It's important to understand that when the document varies by culture but the "*" is used, + // we are just unpublishing the whole document but leaving all of the culture's as-is. This is expected + // because we don't want to actually unpublish every culture and then the document, we just want everything + // to be non-routable so that when it's re-published all variants were as they were. + content.PublishedState = PublishedState.Unpublishing; } else { - // If the culture we want to unpublish was already unpublished, nothing to do. - // To check for that we need to lookup the persisted content item - var persisted = content.HasIdentity ? GetById(content.Id) : null; - - if (persisted != null && !persisted.IsCulturePublished(culture)) - return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content); - - // unpublish the culture + // unpublish the culture, this will change the document state to Publishing! ... which is expected because this will + // essentially be re-publishing the document with the requested culture removed. + // The call to CommitDocumentChangesInternal will perform all the checks like if this is a mandatory culture or the last culture being unpublished + // and will then unpublish the document accordingly. content.UnpublishCulture(culture); + + + //TODO: Move this logic into CommitDocumentChangesInternal, we are already looking up the item there + + //// We are unpublishing a culture but there's a chance that the user might be trying + //// to unpublish a culture that is already unpublished so we need to lookup the current + //// persisted item and check since it would be quicker to do that than continue and re-publish everything. + //var persisted = content.HasIdentity ? GetById(content.Id) : null; + + //if (persisted != null && !persisted.IsCulturePublished(culture)) + //{ + // //it was already unpublished + // //TODO: We then need to check if all cultures are unpublished + // return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content); + //} + } - var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId); + var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId); scope.Complete(); return result; } @@ -1047,15 +1070,35 @@ namespace Umbraco.Core.Services.Implement if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); - var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents); + var allLangs = _languageRepository.GetMany().ToList(); + + var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents); scope.Complete(); return result; } } + /// + /// Handles a lot of business logic cases for how the document should be persisted + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// Business logic cases such: as unpublishing a mandatory culture, or unpublishing the last culture, checking for pending scheduled publishing, etc... is dealt with in this method. + /// There is quite a lot of cases to take into account along with logic that needs to deal with scheduled saving/publishing, branch saving/publishing, etc... + /// + /// private PublishResult CommitDocumentChangesInternal(IScope scope, IContent content, - ContentSavingEventArgs saveEventArgs, - int userId = Constants.Security.SuperUserId, bool raiseEvents = true, bool branchOne = false, bool branchRoot = false) + ContentSavingEventArgs saveEventArgs, IReadOnlyCollection allLangs, + int userId = Constants.Security.SuperUserId, + bool raiseEvents = true, bool branchOne = false, bool branchRoot = false) { if (scope == null) throw new ArgumentNullException(nameof(scope)); if (content == null) throw new ArgumentNullException(nameof(content)); @@ -1070,8 +1113,8 @@ namespace Umbraco.Core.Services.Implement if (content.PublishedState != PublishedState.Publishing && content.PublishedState != PublishedState.Unpublishing) content.PublishedState = PublishedState.Publishing; - // state here is either Publishing or Unpublishing - // (even though, Publishing to unpublish a culture may end up unpublishing everything) + // State here is either Publishing or Unpublishing + // Publishing to unpublish a culture may end up unpublishing everything so these flags can be flipped later var publishing = content.PublishedState == PublishedState.Publishing; var unpublishing = content.PublishedState == PublishedState.Unpublishing; @@ -1090,6 +1133,9 @@ namespace Umbraco.Core.Services.Implement if (publishing) { + //TODO: Check if it's a culture being unpublished and that's why we are publishing the document. In that case + // we should check if the culture is already unpublished since it might be the user is trying to unpublish an already unpublished culture? + //determine cultures publishing/unpublishing which will be based on previous calls to content.PublishCulture and ClearPublishInfo culturesUnpublishing = content.GetCulturesUnpublishing(); culturesPublishing = variesByCulture @@ -1097,11 +1143,21 @@ namespace Umbraco.Core.Services.Implement : null; // ensure that the document can be published, and publish handling events, business rules, etc - publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs, saveEventArgs); + publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs, saveEventArgs, allLangs); if (publishResult.Success) { // note: StrategyPublish flips the PublishedState to Publishing! publishResult = StrategyPublish(content, culturesPublishing, culturesUnpublishing, evtMsgs); + + //check if a culture has been unpublished and if there are no cultures left, and then unpublish document as a whole + if (publishResult.Result == PublishResultType.SuccessUnpublishCulture && content.PublishCultureInfos.Count == 0) + { + publishing = false; + unpublishing = content.Published; // if not published yet, nothing to do + + // we may end up in a state where we won't publish nor unpublish + // keep going, though, as we want to save anyways + } } else { @@ -1186,17 +1242,34 @@ namespace Umbraco.Core.Services.Implement if (culturesUnpublishing != null) { - //If we are here, it means we tried unpublishing a culture but it was mandatory so now everything is unpublished - var langs = string.Join(", ", _languageRepository.GetMany() + // This will mean that that we unpublished a mandatory culture or we unpublished the last culture. + + var langs = string.Join(", ", allLangs .Where(x => culturesUnpublishing.InvariantContains(x.IsoCode)) .Select(x => x.CultureName)); Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs); - //log that the whole content item has been unpublished due to mandatory culture unpublished - Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (mandatory language unpublished)"); - } - else - Audit(AuditType.Unpublish, userId, content.Id); + if (publishResult == null) + throw new PanicException("publishResult == null - should not happen"); + + switch(publishResult.Result) + { + case PublishResultType.FailedPublishMandatoryCultureMissing: + //occurs when a mandatory culture was unpublished (which means we tried publishing the document without a mandatory culture) + + //log that the whole content item has been unpublished due to mandatory culture unpublished + Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (mandatory language unpublished)"); + return new PublishResult(PublishResultType.SuccessUnpublishMandatoryCulture, evtMsgs, content); + case PublishResultType.SuccessUnpublishCulture: + //occurs when the last culture is unpublished + + Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (last language unpublished)"); + return new PublishResult(PublishResultType.SuccessUnpublishLastCulture, evtMsgs, content); + } + + } + + Audit(AuditType.Unpublish, userId, content.Id); return new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content); } @@ -1236,7 +1309,7 @@ namespace Umbraco.Core.Services.Implement case PublishResultType.SuccessPublishCulture: if (culturesPublishing != null) { - var langs = string.Join(", ", _languageRepository.GetMany() + var langs = string.Join(", ", allLangs .Where(x => culturesPublishing.InvariantContains(x.IsoCode)) .Select(x => x.CultureName)); Audit(AuditType.PublishVariant, userId, content.Id, $"Published languages: {langs}", langs); @@ -1245,7 +1318,7 @@ namespace Umbraco.Core.Services.Implement case PublishResultType.SuccessUnpublishCulture: if (culturesUnpublishing != null) { - var langs = string.Join(", ", _languageRepository.GetMany() + var langs = string.Join(", ", allLangs .Where(x => culturesUnpublishing.InvariantContains(x.IsoCode)) .Select(x => x.CultureName)); Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs); @@ -1259,14 +1332,14 @@ namespace Umbraco.Core.Services.Implement // should not happen if (branchOne && !branchRoot) - throw new Exception("panic"); + throw new PanicException("branchOne && !branchRoot - should not happen"); //if publishing didn't happen or if it has failed, we still need to log which cultures were saved if (!branchOne && (publishResult == null || !publishResult.Success)) { if (culturesChanging != null) { - var langs = string.Join(", ", _languageRepository.GetMany() + var langs = string.Join(", ", allLangs .Where(x => culturesChanging.InvariantContains(x.IsoCode)) .Select(x => x.CultureName)); Audit(AuditType.SaveVariant, userId, content.Id, $"Saved languages: {langs}", langs); @@ -1297,6 +1370,8 @@ namespace Umbraco.Core.Services.Implement { scope.WriteLock(Constants.Locks.ContentTree); + var allLangs = _languageRepository.GetMany().ToList(); + foreach (var d in _documentRepository.GetContentForRelease(date)) { PublishResult result; @@ -1325,7 +1400,7 @@ namespace Umbraco.Core.Services.Implement //publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed Property[] invalidProperties = null; - var impact = CultureImpact.Explicit(culture, _languageRepository.IsDefault(culture)); + var impact = CultureImpact.Explicit(culture, IsDefaultCulture(allLangs, culture)); var tryPublish = d.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, impact); if (invalidProperties != null && invalidProperties.Length > 0) Logger.Warn("Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}", @@ -1340,7 +1415,7 @@ namespace Umbraco.Core.Services.Implement else if (!publishing) result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d); else - result = CommitDocumentChangesInternal(scope, d, saveEventArgs, d.WriterId); + result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId); if (result.Success == false) @@ -1390,7 +1465,7 @@ namespace Umbraco.Core.Services.Implement d.UnpublishCulture(c); } - result = CommitDocumentChangesInternal(scope, d, saveEventArgs, d.WriterId); + result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs, d.WriterId); if (result.Success == false) Logger.Error(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); yield return result; @@ -1416,7 +1491,7 @@ namespace Umbraco.Core.Services.Implement } // utility 'PublishCultures' func used by SaveAndPublishBranch - private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet culturesToPublish) + private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet culturesToPublish, IReadOnlyCollection allLangs) { //TODO: This does not support being able to return invalid property details to bubble up to the UI @@ -1426,7 +1501,7 @@ namespace Umbraco.Core.Services.Implement { return culturesToPublish.All(culture => { - var impact = CultureImpact.Create(culture, _languageRepository.IsDefault(culture), content); + var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content); return content.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(content, out _, impact); }); } @@ -1535,7 +1610,7 @@ namespace Umbraco.Core.Services.Implement internal IEnumerable SaveAndPublishBranch(IContent document, bool force, Func> shouldPublish, - Func, bool> publishCultures, + Func, IReadOnlyCollection, bool> publishCultures, int userId = Constants.Security.SuperUserId) { if (shouldPublish == null) throw new ArgumentNullException(nameof(shouldPublish)); @@ -1549,6 +1624,8 @@ namespace Umbraco.Core.Services.Implement { scope.WriteLock(Constants.Locks.ContentTree); + var allLangs = _languageRepository.GetMany().ToList(); + if (!document.HasIdentity) throw new InvalidOperationException("Cannot not branch-publish a new document."); @@ -1557,7 +1634,7 @@ namespace Umbraco.Core.Services.Implement throw new InvalidOperationException("Cannot mix PublishCulture and SaveAndPublishBranch."); // deal with the branch root - if it fails, abort - var result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId); + var result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId, allLangs); if (result != null) { results.Add(result); @@ -1588,7 +1665,7 @@ namespace Umbraco.Core.Services.Implement } // no need to check path here, parent has to be published here - result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId); + result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId, allLangs); if (result != null) { results.Add(result); @@ -1620,10 +1697,10 @@ namespace Umbraco.Core.Services.Implement // publishValues: a function publishing values (using the appropriate PublishCulture calls) private PublishResult SaveAndPublishBranchItem(IScope scope, IContent document, Func> shouldPublish, - Func, bool> publishCultures, + Func, IReadOnlyCollection, bool> publishCultures, bool isRoot, ICollection publishedDocuments, - EventMessages evtMsgs, int userId) + EventMessages evtMsgs, int userId, IReadOnlyCollection allLangs) { var culturesToPublish = shouldPublish(document); if (culturesToPublish == null) // null = do not include @@ -1636,13 +1713,13 @@ namespace Umbraco.Core.Services.Implement return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, document); // publish & check if values are valid - if (!publishCultures(document, culturesToPublish)) + if (!publishCultures(document, culturesToPublish, allLangs)) { //TODO: Based on this callback behavior there is no way to know which properties may have been invalid if this failed, see other results of FailedPublishContentInvalid return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document); } - var result = CommitDocumentChangesInternal(scope, document, saveEventArgs, userId, branchOne: true, branchRoot: isRoot); + var result = CommitDocumentChangesInternal(scope, document, saveEventArgs, allLangs, userId, branchOne: true, branchRoot: isRoot); if (result.Success) publishedDocuments.Add(document); return result; @@ -2343,6 +2420,9 @@ namespace Umbraco.Core.Services.Implement _auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.Document), message, parameters)); } + private bool IsDefaultCulture(IReadOnlyCollection langs, string culture) => langs.Any(x => x.IsDefault && x.IsoCode.InvariantEquals(culture)); + private bool IsMandatoryCulture(IReadOnlyCollection langs, string culture) => langs.Any(x => x.IsMandatory && x.IsoCode.InvariantEquals(culture)); + #endregion #region Event Handlers @@ -2497,7 +2577,9 @@ namespace Umbraco.Core.Services.Implement /// /// /// - private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList culturesPublishing, IReadOnlyCollection culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs) + private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList culturesPublishing, + IReadOnlyCollection culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs, + IReadOnlyCollection allLangs) { // raise Publishing event if (scope.Events.DispatchCancelable(Publishing, this, savingEventArgs.ToContentPublishingEventArgs())) @@ -2510,7 +2592,7 @@ namespace Umbraco.Core.Services.Implement var impactsToPublish = culturesPublishing == null ? new[] {CultureImpact.Invariant} //if it's null it's invariant - : culturesPublishing.Select(x => CultureImpact.Explicit(x, _languageRepository.IsDefault(x))).ToArray(); + : culturesPublishing.Select(x => CultureImpact.Explicit(x, allLangs.Any(lang => lang.IsoCode.InvariantEquals(x) && lang.IsMandatory))).ToArray(); // publish the culture(s) if (!impactsToPublish.All(content.PublishCulture)) @@ -2535,7 +2617,7 @@ namespace Umbraco.Core.Services.Implement return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content); // missing mandatory culture = cannot be published - var mandatoryCultures = _languageRepository.GetMany().Where(x => x.IsMandatory).Select(x => x.IsoCode); + var mandatoryCultures = allLangs.Where(x => x.IsMandatory).Select(x => x.IsoCode); var mandatoryMissing = mandatoryCultures.Any(x => !content.PublishedCultures.Contains(x, StringComparer.OrdinalIgnoreCase)); if (mandatoryMissing) return new PublishResult(PublishResultType.FailedPublishMandatoryCultureMissing, evtMsgs, content); diff --git a/src/Umbraco.Core/Services/PublishResultType.cs b/src/Umbraco.Core/Services/PublishResultType.cs index 1a2b52f9c9..66514a26fb 100644 --- a/src/Umbraco.Core/Services/PublishResultType.cs +++ b/src/Umbraco.Core/Services/PublishResultType.cs @@ -49,6 +49,11 @@ /// SuccessUnpublishMandatoryCulture = 6, + /// + /// The specified document culture was unpublished, and was the last published culture in the document, therefore the document itself was unpublished. + /// + SuccessUnpublishLastCulture = 8, + #endregion #region Success - Mixed @@ -115,7 +120,7 @@ /// /// The document could not be published because it has no publishing flags or values. /// - FailedPublishNothingToPublish = FailedPublish | 9, // TODO: in ContentService.StrategyCanPublish - weird + FailedPublishNothingToPublish = FailedPublish | 9, // TODO: in ContentService.StrategyCanPublish - weird - do we have a test for that case? /// /// The document could not be published because some mandatory cultures are missing. diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index ca9b7d4034..74af58b94f 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -204,6 +204,7 @@ + diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index a719e04b2a..07f4cdb661 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -806,7 +806,7 @@ namespace Umbraco.Tests.Services unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); Assert.IsTrue(unpublished.Success); - Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result); + Assert.AreEqual(PublishResultType.SuccessUnpublishLastCulture, unpublished.Result); Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode)); Assert.AreEqual(PublishedState.Unpublished, content.PublishedState); //the last culture was unpublished so the document should also reflect this @@ -816,6 +816,42 @@ namespace Umbraco.Tests.Services Assert.AreEqual(PublishedState.Unpublished, content.PublishedState); //just double checking } + [Test] + public void Unpublishing_Mandatory_Language_Unpublishes_Document() + { + // Arrange + + var langUk = new Language("en-GB") { IsDefault = true, IsMandatory = true }; + var langFr = new Language("fr-FR"); + + ServiceContext.LocalizationService.Save(langFr); + ServiceContext.LocalizationService.Save(langUk); + + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + + IContent content = new Content("content", Constants.System.Root, contentType); + content.SetCultureName("content-fr", langFr.IsoCode); + content.SetCultureName("content-en", langUk.IsoCode); + + var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode }); + Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); + Assert.IsTrue(published.Success); + Assert.AreEqual(PublishedState.Published, content.PublishedState); + + //re-get + content = ServiceContext.ContentService.GetById(content.Id); + + var unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //unpublish mandatory lang + Assert.IsTrue(unpublished.Success); + Assert.AreEqual(PublishResultType.SuccessUnpublishMandatoryCulture, unpublished.Result); + Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); //remains published + Assert.AreEqual(PublishedState.Unpublished, content.PublishedState); + } + [Test] public void Pending_Invariant_Property_Changes_Affect_Default_Language_Edited_State() { From 5a9ca8d09f30f396cdeed8fdebd961d93469b4ef Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 29 Jul 2019 11:37:11 +1000 Subject: [PATCH 205/776] wip added more tests and notes --- .../Models/ContentRepositoryExtensions.cs | 56 ++++++++++++++----- .../Services/Implement/ContentService.cs | 12 +++- .../Services/ContentServiceTests.cs | 42 ++++++++++++++ 3 files changed, 95 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs index bf7228ca47..f9efc60142 100644 --- a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs +++ b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs @@ -222,7 +222,13 @@ namespace Umbraco.Core.Models return true; } - public static void UnpublishCulture(this IContent content, string culture = "*") + /// + /// Returns false if the culture is already unpublished + /// + /// + /// + /// + public static bool UnpublishCulture(this IContent content, string culture = "*") { culture = culture.NullOrWhiteSpaceAsNull(); @@ -230,16 +236,31 @@ namespace Umbraco.Core.Models if (!content.ContentType.SupportsPropertyVariation(culture, "*", true)) throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\"."); - if (culture == "*") // all cultures + + var keepProcessing = true; + + if (culture == "*") + { + // all cultures content.ClearPublishInfos(); - else // one single culture - content.ClearPublishInfo(culture); + } + else + { + // one single culture + keepProcessing = content.ClearPublishInfo(culture); + } + - // property.PublishValues only publishes what is valid, variation-wise - foreach (var property in content.Properties) - property.UnpublishValues(culture); + if (keepProcessing) + { + // property.PublishValues only publishes what is valid, variation-wise + foreach (var property in content.Properties) + property.UnpublishValues(culture); - content.PublishedState = PublishedState.Publishing; + content.PublishedState = PublishedState.Publishing; + } + + return keepProcessing; } public static void ClearPublishInfos(this IContent content) @@ -247,15 +268,24 @@ namespace Umbraco.Core.Models content.PublishCultureInfos = null; } - public static void ClearPublishInfo(this IContent content, string culture) + /// + /// Returns false if the culture is already unpublished + /// + /// + /// + /// + public static bool ClearPublishInfo(this IContent content, string culture) { if (culture.IsNullOrWhiteSpace()) throw new ArgumentNullOrEmptyException(nameof(culture)); - content.PublishCultureInfos.Remove(culture); - - // set the culture to be dirty - it's been modified - content.TouchCulture(culture); + var removed = content.PublishCultureInfos.Remove(culture); + if (removed) + { + // set the culture to be dirty - it's been modified + content.TouchCulture(culture); + } + return removed; } /// diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index b4e5f26de5..91c26701b3 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -1012,7 +1012,9 @@ namespace Umbraco.Core.Services.Implement // essentially be re-publishing the document with the requested culture removed. // The call to CommitDocumentChangesInternal will perform all the checks like if this is a mandatory culture or the last culture being unpublished // and will then unpublish the document accordingly. - content.UnpublishCulture(culture); + var removed = content.UnpublishCulture(culture); + + //TODO: if !removed then there is really nothing to process and we should exit here with SuccessUnpublishAlready. //TODO: Move this logic into CommitDocumentChangesInternal, we are already looking up the item there @@ -2613,8 +2615,14 @@ namespace Umbraco.Core.Services.Implement if (culturesPublishing == null) throw new InvalidOperationException("Internal error, variesByCulture but culturesPublishing is null."); - if (content.Published && culturesPublishing.Count == 0 && culturesUnpublishing.Count == 0) // no published cultures = cannot be published + if (content.Published && culturesPublishing.Count == 0 && culturesUnpublishing.Count == 0) + { + // no published cultures = cannot be published + // This will occur if for example, a culture that is already unpublished is sent to be unpublished again, or vice versa, in that case + // there will be nothing to publish/unpublish. return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content); + } + // missing mandatory culture = cannot be published var mandatoryCultures = allLangs.Where(x => x.IsMandatory).Select(x => x.IsoCode); diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 07f4cdb661..a903bcedb0 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -852,6 +852,48 @@ namespace Umbraco.Tests.Services Assert.AreEqual(PublishedState.Unpublished, content.PublishedState); } + [Test] + public void Unpublishing_Already_Unpublished_Culture() + { + // Arrange + + var langUk = new Language("en-GB") { IsDefault = true }; + var langFr = new Language("fr-FR"); + + ServiceContext.LocalizationService.Save(langFr); + ServiceContext.LocalizationService.Save(langUk); + + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + + IContent content = new Content("content", Constants.System.Root, contentType); + content.SetCultureName("content-fr", langFr.IsoCode); + content.SetCultureName("content-en", langUk.IsoCode); + + var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode }); + Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); + Assert.IsTrue(published.Success); + Assert.AreEqual(PublishedState.Published, content.PublishedState); + + //re-get + content = ServiceContext.ContentService.GetById(content.Id); + + var unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); + Assert.IsTrue(unpublished.Success); + Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result); + Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode)); + + content = ServiceContext.ContentService.GetById(content.Id); + + unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //unpublish again + Assert.IsTrue(unpublished.Success); + Assert.AreEqual(PublishResultType.SuccessUnpublishAlready, unpublished.Result); + Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode)); + + } + [Test] public void Pending_Invariant_Property_Changes_Affect_Default_Language_Edited_State() { From 40a55cb9dfe9978d7dfbab5067f932e2c400803f Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 29 Jul 2019 15:28:50 +1000 Subject: [PATCH 206/776] Fixes issue/tests --- .../Services/Implement/ContentService.cs | 35 ++++++++----------- .../Services/PublishResultType.cs | 4 +-- .../Services/ContentServiceTests.cs | 8 +++++ 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 91c26701b3..a8b40b1bb1 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -937,6 +937,7 @@ namespace Umbraco.Core.Services.Implement //no cultures specified and doesn't vary, so publish it, else nothing to publish return !varies ? SaveAndPublish(content, userId: userId, raiseEvents: raiseEvents) + //TODO: Though we may not have cultures to publish, shouldn't we continue to Save in this case?? : new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content); } @@ -1005,39 +1006,33 @@ namespace Umbraco.Core.Services.Implement // to be non-routable so that when it's re-published all variants were as they were. content.PublishedState = PublishedState.Unpublishing; + var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId); + scope.Complete(); + return result; } else { - // unpublish the culture, this will change the document state to Publishing! ... which is expected because this will + // Unpublish the culture, this will change the document state to Publishing! ... which is expected because this will // essentially be re-publishing the document with the requested culture removed. // The call to CommitDocumentChangesInternal will perform all the checks like if this is a mandatory culture or the last culture being unpublished // and will then unpublish the document accordingly. + // If the result of this is false it means there was no culture to unpublish (i.e. it was already unpublished or it did not exist) var removed = content.UnpublishCulture(culture); - //TODO: if !removed then there is really nothing to process and we should exit here with SuccessUnpublishAlready. + //save and publish any changes + var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId); + scope.Complete(); - //TODO: Move this logic into CommitDocumentChangesInternal, we are already looking up the item there - - //// We are unpublishing a culture but there's a chance that the user might be trying - //// to unpublish a culture that is already unpublished so we need to lookup the current - //// persisted item and check since it would be quicker to do that than continue and re-publish everything. - //var persisted = content.HasIdentity ? GetById(content.Id) : null; - - //if (persisted != null && !persisted.IsCulturePublished(culture)) - //{ - // //it was already unpublished - // //TODO: We then need to check if all cultures are unpublished - // return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content); - //} + // In one case the result will be PublishStatusType.FailedPublishNothingToPublish which means that no cultures + // were specified to be published which will be the case when removed is false. In that case + // we want to swap the result type to PublishResultType.SuccessUnpublishAlready (that was the expectation before). + if (result.Result == PublishResultType.FailedPublishNothingToPublish && !removed) + return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content); + return result; } - - var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId); - scope.Complete(); - return result; } - } /// diff --git a/src/Umbraco.Core/Services/PublishResultType.cs b/src/Umbraco.Core/Services/PublishResultType.cs index 66514a26fb..66c1e38267 100644 --- a/src/Umbraco.Core/Services/PublishResultType.cs +++ b/src/Umbraco.Core/Services/PublishResultType.cs @@ -118,9 +118,9 @@ FailedPublishContentInvalid = FailedPublish | 8, /// - /// The document could not be published because it has no publishing flags or values. + /// The document could not be published because it has no publishing flags or values or if its a variant document, no cultures were specified to be published. /// - FailedPublishNothingToPublish = FailedPublish | 9, // TODO: in ContentService.StrategyCanPublish - weird - do we have a test for that case? + FailedPublishNothingToPublish = FailedPublish | 9, /// /// The document could not be published because some mandatory cultures are missing. diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index a903bcedb0..44b2eec66d 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -887,11 +887,19 @@ namespace Umbraco.Tests.Services content = ServiceContext.ContentService.GetById(content.Id); + //Change some data since Unpublish should always Save + content.SetCultureName("content-en-updated", langUk.IsoCode); + //var saveResult = ServiceContext.ContentService.Save(content); + //content = ServiceContext.ContentService.GetById(content.Id); + unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //unpublish again Assert.IsTrue(unpublished.Success); Assert.AreEqual(PublishResultType.SuccessUnpublishAlready, unpublished.Result); Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode)); + content = ServiceContext.ContentService.GetById(content.Id); + //ensure that even though the culture was already unpublished that the data was still persisted + Assert.AreEqual("content-en-updated", content.GetCultureName(langUk.IsoCode)); } [Test] From 415916c527a93944196ed93136a950b607c9d42b Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 29 Jul 2019 16:09:45 +1000 Subject: [PATCH 207/776] Fixes an issue with the content service + test --- .../Services/Implement/ContentService.cs | 7 +-- .../Services/ContentServiceTests.cs | 43 ++++++++++++++++++- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index a8b40b1bb1..b1aa92c4c8 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -932,13 +932,10 @@ namespace Umbraco.Core.Services.Implement var varies = content.ContentType.VariesByCulture(); - if (cultures.Length == 0) + if (cultures.Length == 0 && !varies) { //no cultures specified and doesn't vary, so publish it, else nothing to publish - return !varies - ? SaveAndPublish(content, userId: userId, raiseEvents: raiseEvents) - //TODO: Though we may not have cultures to publish, shouldn't we continue to Save in this case?? - : new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content); + return SaveAndPublish(content, userId: userId, raiseEvents: raiseEvents); } if (cultures.Any(x => x == null || x == "*")) diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 44b2eec66d..b8ed0e1f41 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -889,8 +889,6 @@ namespace Umbraco.Tests.Services //Change some data since Unpublish should always Save content.SetCultureName("content-en-updated", langUk.IsoCode); - //var saveResult = ServiceContext.ContentService.Save(content); - //content = ServiceContext.ContentService.GetById(content.Id); unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //unpublish again Assert.IsTrue(unpublished.Success); @@ -902,6 +900,47 @@ namespace Umbraco.Tests.Services Assert.AreEqual("content-en-updated", content.GetCultureName(langUk.IsoCode)); } + [Test] + public void Publishing_No_Cultures_Still_Saves() + { + // Arrange + + var langUk = new Language("en-GB") { IsDefault = true }; + var langFr = new Language("fr-FR"); + + ServiceContext.LocalizationService.Save(langFr); + ServiceContext.LocalizationService.Save(langUk); + + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + + IContent content = new Content("content", Constants.System.Root, contentType); + content.SetCultureName("content-fr", langFr.IsoCode); + content.SetCultureName("content-en", langUk.IsoCode); + + var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode }); + Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); + Assert.IsTrue(published.Success); + Assert.AreEqual(PublishedState.Published, content.PublishedState); + + //re-get + content = ServiceContext.ContentService.GetById(content.Id); + + //Change some data since SaveAndPublish should always Save + content.SetCultureName("content-en-updated", langUk.IsoCode); + + var saved = ServiceContext.ContentService.SaveAndPublish(content, new string [] { }); //save without cultures + Assert.AreEqual(PublishResultType.FailedPublishNothingToPublish, saved.Result); + + //re-get + content = ServiceContext.ContentService.GetById(content.Id); + //ensure that even though nothing was published that the data was still persisted + Assert.AreEqual("content-en-updated", content.GetCultureName(langUk.IsoCode)); + } + + [Test] public void Pending_Invariant_Property_Changes_Affect_Default_Language_Edited_State() { From a2ba0f6a4c9a91c0a183b6f60d2be78ad989fd93 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 29 Jul 2019 17:06:09 +1000 Subject: [PATCH 208/776] more tests - failing that need fixing --- .../Services/ContentServiceTests.cs | 151 +++++++++--------- 1 file changed, 75 insertions(+), 76 deletions(-) diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index b8ed0e1f41..7954f0f024 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -717,21 +717,8 @@ namespace Umbraco.Tests.Services [Test] public void Can_Unpublish_Content_Variation() { - // Arrange + var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType); - var langUk = new Language("en-GB") { IsDefault = true }; - var langFr = new Language("fr-FR"); - - ServiceContext.LocalizationService.Save(langFr); - ServiceContext.LocalizationService.Save(langUk); - - var contentType = MockedContentTypes.CreateBasicContentType(); - contentType.Variations = ContentVariation.Culture; - ServiceContext.ContentTypeService.Save(contentType); - - IContent content = new Content("content", Constants.System.Root, contentType); - content.SetCultureName("content-fr", langFr.IsoCode); - content.SetCultureName("content-en", langUk.IsoCode); content.PublishCulture(CultureImpact.Explicit(langFr.IsoCode, langFr.IsDefault)); content.PublishCulture(CultureImpact.Explicit(langUk.IsoCode, langUk.IsDefault)); Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); @@ -761,25 +748,52 @@ namespace Umbraco.Tests.Services } + [Test] + public void Can_Publish_Culture_After_Last_Culture_Unpublished() + { + var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType); + + var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode }); + Assert.AreEqual(PublishedState.Published, content.PublishedState); + + //re-get + content = ServiceContext.ContentService.GetById(content.Id); + + var unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //first culture + Assert.IsTrue(unpublished.Success); + Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result); + Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); + + content = ServiceContext.ContentService.GetById(content.Id); + + unpublished = ServiceContext.ContentService.Unpublish(content, langFr.IsoCode); //last culture + Assert.IsTrue(unpublished.Success); + Assert.AreEqual(PublishResultType.SuccessUnpublishLastCulture, unpublished.Result); + Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode)); + + content = ServiceContext.ContentService.GetById(content.Id); + + published = ServiceContext.ContentService.SaveAndPublish(content, langUk.IsoCode); + Assert.AreEqual(PublishedState.Published, content.PublishedState); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); + Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); + + content = ServiceContext.ContentService.GetById(content.Id); //reget + Assert.AreEqual(PublishedState.Published, content.PublishedState); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); + Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); + + } + + + [Test] public void Unpublish_All_Cultures_Has_Unpublished_State() { - // Arrange + var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType); - var langUk = new Language("en-GB") { IsDefault = true }; - var langFr = new Language("fr-FR"); - - ServiceContext.LocalizationService.Save(langFr); - ServiceContext.LocalizationService.Save(langUk); - - var contentType = MockedContentTypes.CreateBasicContentType(); - contentType.Variations = ContentVariation.Culture; - ServiceContext.ContentTypeService.Save(contentType); - - IContent content = new Content("content", Constants.System.Root, contentType); - content.SetCultureName("content-fr", langFr.IsoCode); - content.SetCultureName("content-en", langUk.IsoCode); - var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode }); Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); @@ -792,7 +806,7 @@ namespace Umbraco.Tests.Services Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); Assert.AreEqual(PublishedState.Published, content.PublishedState); - var unpublished = ServiceContext.ContentService.Unpublish(content, langFr.IsoCode); + var unpublished = ServiceContext.ContentService.Unpublish(content, langFr.IsoCode); //first culture Assert.IsTrue(unpublished.Success); Assert.AreEqual(PublishResultType.SuccessUnpublishCulture, unpublished.Result); Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); @@ -804,7 +818,7 @@ namespace Umbraco.Tests.Services Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); - unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); + unpublished = ServiceContext.ContentService.Unpublish(content, langUk.IsoCode); //last culture Assert.IsTrue(unpublished.Success); Assert.AreEqual(PublishResultType.SuccessUnpublishLastCulture, unpublished.Result); Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); @@ -814,13 +828,13 @@ namespace Umbraco.Tests.Services //re-get content = ServiceContext.ContentService.GetById(content.Id); Assert.AreEqual(PublishedState.Unpublished, content.PublishedState); //just double checking + Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode)); } [Test] public void Unpublishing_Mandatory_Language_Unpublishes_Document() { - // Arrange - var langUk = new Language("en-GB") { IsDefault = true, IsMandatory = true }; var langFr = new Language("fr-FR"); @@ -855,21 +869,7 @@ namespace Umbraco.Tests.Services [Test] public void Unpublishing_Already_Unpublished_Culture() { - // Arrange - - var langUk = new Language("en-GB") { IsDefault = true }; - var langFr = new Language("fr-FR"); - - ServiceContext.LocalizationService.Save(langFr); - ServiceContext.LocalizationService.Save(langUk); - - var contentType = MockedContentTypes.CreateBasicContentType(); - contentType.Variations = ContentVariation.Culture; - ServiceContext.ContentTypeService.Save(contentType); - - IContent content = new Content("content", Constants.System.Root, contentType); - content.SetCultureName("content-fr", langFr.IsoCode); - content.SetCultureName("content-en", langUk.IsoCode); + var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType); var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode }); Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); @@ -903,21 +903,7 @@ namespace Umbraco.Tests.Services [Test] public void Publishing_No_Cultures_Still_Saves() { - // Arrange - - var langUk = new Language("en-GB") { IsDefault = true }; - var langFr = new Language("fr-FR"); - - ServiceContext.LocalizationService.Save(langFr); - ServiceContext.LocalizationService.Save(langUk); - - var contentType = MockedContentTypes.CreateBasicContentType(); - contentType.Variations = ContentVariation.Culture; - ServiceContext.ContentTypeService.Save(contentType); - - IContent content = new Content("content", Constants.System.Root, contentType); - content.SetCultureName("content-fr", langFr.IsoCode); - content.SetCultureName("content-en", langUk.IsoCode); + var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType); var published = ServiceContext.ContentService.SaveAndPublish(content, new[] { langFr.IsoCode, langUk.IsoCode }); Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); @@ -991,17 +977,7 @@ namespace Umbraco.Tests.Services [Test] public void Can_Publish_Content_Variation_And_Detect_Changed_Cultures() { - // Arrange - - var langGB = new Language("en-GB") { IsDefault = true }; - var langFr = new Language("fr-FR"); - - ServiceContext.LocalizationService.Save(langFr); - ServiceContext.LocalizationService.Save(langGB); - - var contentType = MockedContentTypes.CreateBasicContentType(); - contentType.Variations = ContentVariation.Culture; - ServiceContext.ContentTypeService.Save(contentType); + CreateEnglishAndFrenchDocumentType(out var langUk, out var langFr, out var contentType); IContent content = new Content("content", Constants.System.Root, contentType); content.SetCultureName("content-fr", langFr.IsoCode); @@ -1012,8 +988,8 @@ namespace Umbraco.Tests.Services //re-get content = ServiceContext.ContentService.GetById(content.Id); - content.SetCultureName("content-en", langGB.IsoCode); - published = ServiceContext.ContentService.SaveAndPublish(content, langGB.IsoCode); + content.SetCultureName("content-en", langUk.IsoCode); + published = ServiceContext.ContentService.SaveAndPublish(content, langUk.IsoCode); //audit log will only show that english was published lastLog = ServiceContext.AuditService.GetLogs(content.Id).Last(); Assert.AreEqual($"Published languages: English (United Kingdom)", lastLog.Comment); @@ -3193,5 +3169,28 @@ namespace Umbraco.Tests.Services var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; } + + private void CreateEnglishAndFrenchDocumentType(out Language langUk, out Language langFr, out ContentType contentType) + { + langUk = new Language("en-GB") { IsDefault = true }; + langFr = new Language("fr-FR"); + ServiceContext.LocalizationService.Save(langFr); + ServiceContext.LocalizationService.Save(langUk); + + contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + } + + private IContent CreateEnglishAndFrenchDocument(out Language langUk, out Language langFr, out ContentType contentType) + { + CreateEnglishAndFrenchDocumentType(out langUk, out langFr, out contentType); + + IContent content = new Content("content", Constants.System.Root, contentType); + content.SetCultureName("content-fr", langFr.IsoCode); + content.SetCultureName("content-en", langUk.IsoCode); + + return content; + } } } From 45227357fd38b1498f4d53dbabb88de8520a6d6b Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 29 Jul 2019 17:41:37 +1000 Subject: [PATCH 209/776] adds notes on why this is failing --- src/Umbraco.Tests/Services/ContentServiceTests.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 7954f0f024..01eb9a0e27 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -829,6 +829,13 @@ namespace Umbraco.Tests.Services content = ServiceContext.ContentService.GetById(content.Id); Assert.AreEqual(PublishedState.Unpublished, content.PublishedState); //just double checking Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); + //TODO: This fails!? Why? Because in the ContentService.CommitDocumentChangesInternal method when we detect it's the last lang being unpublished, + // we swap the unpublishing flag to true which means we end up setting the property content.PublishedState = PublishedState.Unpublishing, because of this + // inside of the DocumentRepository we treat many things differently including not publishing the removed culture. + // So how do we fix that? Well when we unpublish the last culture we want to both save the unpublishing of the culture AND unpublish the document, the easy + // way to do this will be to perform a double save operation, though that's not the prettiest way to do it. Ideally we take care of this all in one process + // in the DocumentRepository but that might require another transitory status like PublishedState.UnpublishingLastCulture ... though that's not pretty either. + // It might be possible in some other magicaly way, i'm just leaving these notes here mostly for myself :) Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode)); } From b5b4ee79b406ade86d78a6ea0578683ee426c260 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 30 Jul 2019 16:00:42 +1000 Subject: [PATCH 210/776] Adds notes, fixes logic with a double save --- .../TagsPropertyEditorAttribute.cs | 1 + .../Services/Implement/ContentService.cs | 34 +++++++++++++------ .../Services/ContentServiceTests.cs | 4 +++ 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs index 2439c7c02e..6549f1a233 100644 --- a/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs @@ -55,6 +55,7 @@ namespace Umbraco.Core.PropertyEditors /// /// Gets the type of the dynamic configuration provider. /// + //TODO: This is not used and should be implemented in a nicer way, see https://github.com/umbraco/Umbraco-CMS/issues/6017#issuecomment-516253562 public Type TagsConfigurationProviderType { get; } } } diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index b1aa92c4c8..7b003d0a26 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -1125,6 +1125,18 @@ namespace Umbraco.Core.Services.Implement var changeType = isNew ? TreeChangeTypes.RefreshNode : TreeChangeTypes.RefreshBranch; var previouslyPublished = content.HasIdentity && content.Published; + //inline method to persist the document with the documentRepository since this logic could be called a couple times below + void SaveDocument(IContent c) + { + // save, always + if (c.HasIdentity == false) + c.CreatorId = userId; + c.WriterId = userId; + + // saving does NOT change the published version, unless PublishedState is Publishing or Unpublishing + _documentRepository.Save(c); + } + if (publishing) { //TODO: Check if it's a culture being unpublished and that's why we are publishing the document. In that case @@ -1146,11 +1158,15 @@ namespace Umbraco.Core.Services.Implement //check if a culture has been unpublished and if there are no cultures left, and then unpublish document as a whole if (publishResult.Result == PublishResultType.SuccessUnpublishCulture && content.PublishCultureInfos.Count == 0) { - publishing = false; - unpublishing = content.Published; // if not published yet, nothing to do + // This is a special case! We are unpublishing the last culture and to persist that we need to re-publish without any cultures + // so the state needs to remain Publishing to do that. However, we then also need to unpublish the document and to do that + // the state needs to be Unpublishing and it cannot be both. This state is used within the documentRepository to know how to + // persist certain things. So before proceeding below, we need to save the Publishing state to publish no cultures, then we can + // mark the document for Unpublishing. + SaveDocument(content); - // we may end up in a state where we won't publish nor unpublish - // keep going, though, as we want to save anyways + //set the flag to unpublish and continue + unpublishing = content.Published; // if not published yet, nothing to do } } else @@ -1212,13 +1228,8 @@ namespace Umbraco.Core.Services.Implement } } - // save, always - if (content.HasIdentity == false) - content.CreatorId = userId; - content.WriterId = userId; - - // saving does NOT change the published version, unless PublishedState is Publishing or Unpublishing - _documentRepository.Save(content); + //Persist the document + SaveDocument(content); // raise the Saved event, always if (raiseEvents) @@ -2758,6 +2769,7 @@ namespace Umbraco.Core.Services.Implement { var attempt = new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content); + //TODO: What is this check?? we just created this attempt and of course it is Success?! if (attempt.Success == false) return attempt; diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 01eb9a0e27..10bb63211b 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -836,6 +836,10 @@ namespace Umbraco.Tests.Services // way to do this will be to perform a double save operation, though that's not the prettiest way to do it. Ideally we take care of this all in one process // in the DocumentRepository but that might require another transitory status like PublishedState.UnpublishingLastCulture ... though that's not pretty either. // It might be possible in some other magicaly way, i'm just leaving these notes here mostly for myself :) + // UPDATE - ok, i 'think' instead of doing a check inside of CommitDocumentChangesInternal for the last culture being unpublished, we could actually do this check + // directly inside of the DocumentRepository + // UPDATE 2 - ok that won't work because there is some business logic that needs to occur in CommitDocumentChangesInternal when it needs to be unpublished, this will + // be tricky without doing a double save, hrm. Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode)); } From 27bc309c557f9b0adeca9f587feed9a199f80e36 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 30 Jul 2019 18:49:48 +1000 Subject: [PATCH 211/776] Removes unused code --- .../Implement/ContentRepositoryBase.cs | 35 ++++++++++--------- .../Services/Implement/ContentService.cs | 3 -- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index aeb4c3774f..60beaca018 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -512,20 +512,21 @@ namespace Umbraco.Core.Persistence.Repositories.Implement foreach (var a in allPropertyDataDtos) a.PropertyTypeDto = indexedPropertyTypeDtos[a.PropertyTypeId]; - // prefetch configuration for tag properties - var tagEditors = new Dictionary(); - foreach (var propertyTypeDto in indexedPropertyTypeDtos.Values) - { - var editorAlias = propertyTypeDto.DataTypeDto.EditorAlias; - var editorAttribute = PropertyEditors[editorAlias].GetTagAttribute(); - if (editorAttribute == null) continue; - var tagConfigurationSource = propertyTypeDto.DataTypeDto.Configuration; - var tagConfiguration = string.IsNullOrWhiteSpace(tagConfigurationSource) - ? new TagConfiguration() - : JsonConvert.DeserializeObject(tagConfigurationSource); - if (tagConfiguration.Delimiter == default) tagConfiguration.Delimiter = editorAttribute.Delimiter; - tagEditors[editorAlias] = tagConfiguration; - } + //TODO: This logic was here but then we never used the data being passed to GetPropertyCollections, this will save a bit of CPU without it! + //// prefetch configuration for tag properties + //var tagEditors = new Dictionary(); + //foreach (var propertyTypeDto in indexedPropertyTypeDtos.Values) + //{ + // var editorAlias = propertyTypeDto.DataTypeDto.EditorAlias; + // var editorAttribute = PropertyEditors[editorAlias].GetTagAttribute(); + // if (editorAttribute == null) continue; + // var tagConfigurationSource = propertyTypeDto.DataTypeDto.Configuration; + // var tagConfiguration = string.IsNullOrWhiteSpace(tagConfigurationSource) + // ? new TagConfiguration() + // : JsonConvert.DeserializeObject(tagConfigurationSource); + // if (tagConfiguration.Delimiter == default) tagConfiguration.Delimiter = editorAttribute.Delimiter; + // tagEditors[editorAlias] = tagConfiguration; + //} // now we have // - the definitions @@ -533,10 +534,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // - tag editors // and we need to build the proper property collections - return GetPropertyCollections(temps, allPropertyDataDtos, tagEditors); + return GetPropertyCollections(temps, allPropertyDataDtos + /*, tagEditors*/); } - private IDictionary GetPropertyCollections(List> temps, IEnumerable allPropertyDataDtos, Dictionary tagConfigurations) + private IDictionary GetPropertyCollections(List> temps, IEnumerable allPropertyDataDtos + /*, Dictionary tagConfigurations*/) where T : class, IContentBase { var result = new Dictionary(); diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 7b003d0a26..5492f97409 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -1139,9 +1139,6 @@ namespace Umbraco.Core.Services.Implement if (publishing) { - //TODO: Check if it's a culture being unpublished and that's why we are publishing the document. In that case - // we should check if the culture is already unpublished since it might be the user is trying to unpublish an already unpublished culture? - //determine cultures publishing/unpublishing which will be based on previous calls to content.PublishCulture and ClearPublishInfo culturesUnpublishing = content.GetCulturesUnpublishing(); culturesPublishing = variesByCulture From 20d28ea2159f90b833ea14f8317f644763fbb3ea Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 30 Jul 2019 19:08:37 +1000 Subject: [PATCH 212/776] removes commented out code --- .../Implement/ContentRepositoryBase.cs | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 60beaca018..7ab73f3f2d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -512,34 +512,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement foreach (var a in allPropertyDataDtos) a.PropertyTypeDto = indexedPropertyTypeDtos[a.PropertyTypeId]; - //TODO: This logic was here but then we never used the data being passed to GetPropertyCollections, this will save a bit of CPU without it! - //// prefetch configuration for tag properties - //var tagEditors = new Dictionary(); - //foreach (var propertyTypeDto in indexedPropertyTypeDtos.Values) - //{ - // var editorAlias = propertyTypeDto.DataTypeDto.EditorAlias; - // var editorAttribute = PropertyEditors[editorAlias].GetTagAttribute(); - // if (editorAttribute == null) continue; - // var tagConfigurationSource = propertyTypeDto.DataTypeDto.Configuration; - // var tagConfiguration = string.IsNullOrWhiteSpace(tagConfigurationSource) - // ? new TagConfiguration() - // : JsonConvert.DeserializeObject(tagConfigurationSource); - // if (tagConfiguration.Delimiter == default) tagConfiguration.Delimiter = editorAttribute.Delimiter; - // tagEditors[editorAlias] = tagConfiguration; - //} - // now we have // - the definitions // - all property data dtos - // - tag editors + // - tag editors (Actually ... no we don't since i removed that code, but we don't need them anyways it seems) // and we need to build the proper property collections - return GetPropertyCollections(temps, allPropertyDataDtos - /*, tagEditors*/); + return GetPropertyCollections(temps, allPropertyDataDtos); } - private IDictionary GetPropertyCollections(List> temps, IEnumerable allPropertyDataDtos - /*, Dictionary tagConfigurations*/) + private IDictionary GetPropertyCollections(List> temps, IEnumerable allPropertyDataDtos) where T : class, IContentBase { var result = new Dictionary(); From 108416638c837ad25a52dab9a142db909d789d46 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 30 Jul 2019 22:28:06 +1000 Subject: [PATCH 213/776] removes commented out code --- src/Umbraco.Tests/Services/ContentServiceTests.cs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 10bb63211b..39bebbe90b 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -828,18 +828,7 @@ namespace Umbraco.Tests.Services //re-get content = ServiceContext.ContentService.GetById(content.Id); Assert.AreEqual(PublishedState.Unpublished, content.PublishedState); //just double checking - Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); - //TODO: This fails!? Why? Because in the ContentService.CommitDocumentChangesInternal method when we detect it's the last lang being unpublished, - // we swap the unpublishing flag to true which means we end up setting the property content.PublishedState = PublishedState.Unpublishing, because of this - // inside of the DocumentRepository we treat many things differently including not publishing the removed culture. - // So how do we fix that? Well when we unpublish the last culture we want to both save the unpublishing of the culture AND unpublish the document, the easy - // way to do this will be to perform a double save operation, though that's not the prettiest way to do it. Ideally we take care of this all in one process - // in the DocumentRepository but that might require another transitory status like PublishedState.UnpublishingLastCulture ... though that's not pretty either. - // It might be possible in some other magicaly way, i'm just leaving these notes here mostly for myself :) - // UPDATE - ok, i 'think' instead of doing a check inside of CommitDocumentChangesInternal for the last culture being unpublished, we could actually do this check - // directly inside of the DocumentRepository - // UPDATE 2 - ok that won't work because there is some business logic that needs to occur in CommitDocumentChangesInternal when it needs to be unpublished, this will - // be tricky without doing a double save, hrm. + Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode)); } From 16d8f8846e1656ffb799c65a9404ffece60dc851 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 30 Jul 2019 22:40:15 +1000 Subject: [PATCH 214/776] Adds PanicException with details instead of throwing "panic" strings --- src/Umbraco.Core/Exceptions/PanicException.cs | 28 +++++++++++++++++++ src/Umbraco.Core/Mapping/UmbracoMapper.cs | 5 ++-- .../DataTypes/RenamingPreValueMigrator.cs | 3 +- .../Models/PublishedContent/ModelType.cs | 4 +-- .../Implement/ContentTypeCommonRepository.cs | 3 +- .../Implement/ContentTypeRepository.cs | 3 +- .../Implement/MediaTypeRepository.cs | 3 +- .../Implement/MemberTypeRepository.cs | 3 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Testing/TestOptionAttributeBase.cs | 5 ++-- .../Mapping/ContentTypeMapDefinition.cs | 3 +- .../MultipleTextStringPropertyEditor.cs | 3 +- .../PublishedCache/NuCache/ContentStore.cs | 3 +- .../PublishedCache/NuCache/SnapDictionary.cs | 3 +- 14 files changed, 55 insertions(+), 15 deletions(-) create mode 100644 src/Umbraco.Core/Exceptions/PanicException.cs diff --git a/src/Umbraco.Core/Exceptions/PanicException.cs b/src/Umbraco.Core/Exceptions/PanicException.cs new file mode 100644 index 0000000000..4d41cafc65 --- /dev/null +++ b/src/Umbraco.Core/Exceptions/PanicException.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Core.Exceptions +{ + /// + /// Internal exception that in theory should never ben thrown, it is only thrown in circumstances that should never happen + /// + [Serializable] + internal class PanicException : Exception + { + public PanicException() + { + } + + public PanicException(string message) : base(message) + { + } + + public PanicException(string message, Exception innerException) : base(message, innerException) + { + } + + protected PanicException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/src/Umbraco.Core/Mapping/UmbracoMapper.cs b/src/Umbraco.Core/Mapping/UmbracoMapper.cs index 8915ebcf74..976f50b499 100644 --- a/src/Umbraco.Core/Mapping/UmbracoMapper.cs +++ b/src/Umbraco.Core/Mapping/UmbracoMapper.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Exceptions; namespace Umbraco.Core.Mapping { @@ -259,7 +260,7 @@ namespace Umbraco.Core.Mapping if (typeof(TTarget).IsArray) { var elementType = typeof(TTarget).GetElementType(); - if (elementType == null) throw new Exception("panic"); + if (elementType == null) throw new PanicException("elementType == null which should never occur"); var targetArray = Array.CreateInstance(elementType, targetList.Count); targetList.CopyTo(targetArray, 0); target = targetArray; @@ -382,7 +383,7 @@ namespace Umbraco.Core.Mapping { if (type.IsArray) return type.GetElementType(); if (type.IsGenericType) return type.GenericTypeArguments[0]; - throw new Exception("panic"); + throw new PanicException($"Could not get enumerable or array type from {type}"); } /// diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RenamingPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RenamingPreValueMigrator.cs index c04e7c8fda..89a71fdaf4 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RenamingPreValueMigrator.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RenamingPreValueMigrator.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Umbraco.Core.Exceptions; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes { @@ -20,7 +21,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes case "Umbraco.NoEdit": return Constants.PropertyEditors.Aliases.Label; default: - throw new Exception("panic"); + throw new PanicException($"The alias {editorAlias} is not supported"); } } } diff --git a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs index 540abda2c5..318ccc916e 100644 --- a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs @@ -75,7 +75,7 @@ namespace Umbraco.Core.Models.PublishedContent return type; var def = type.GetGenericTypeDefinition(); if (def == null) - throw new InvalidOperationException("panic"); + throw new PanicException($"The type {type} has not generic type definition"); var args = type.GetGenericArguments().Select(x => Map(x, modelTypes, true)).ToArray(); return def.MakeGenericType(args); @@ -114,7 +114,7 @@ namespace Umbraco.Core.Models.PublishedContent return type.FullName; var def = type.GetGenericTypeDefinition(); if (def == null) - throw new InvalidOperationException("panic"); + throw new PanicException($"The type {type} has not generic type definition"); var args = type.GetGenericArguments().Select(x => MapToName(x, map, true)).ToArray(); var defFullName = def.FullName.Substring(0, def.FullName.IndexOf('`')); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs index 6b751eb8ff..4393d365f8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using NPoco; using Umbraco.Core.Cache; +using Umbraco.Core.Exceptions; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; @@ -90,7 +91,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement contentType = ContentTypeFactory.BuildContentTypeEntity(contentTypeDto); else if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.MemberType) contentType = ContentTypeFactory.BuildMemberTypeEntity(contentTypeDto); - else throw new Exception("panic"); + else throw new PanicException($"The node object type {contentTypeDto.NodeDto.NodeObjectType} is not supported"); contentTypes.Add(contentType.Id, contentType); // map allowed content types diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs index 9d77eb0990..38988657d1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using NPoco; using Umbraco.Core.Cache; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; @@ -56,7 +57,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { // the cache policy will always want everything // even GetMany(ids) gets everything and filters afterwards - if (ids.Any()) throw new Exception("panic"); + if (ids.Any()) throw new PanicException("There can be no ids specified"); return CommonRepository.GetAllTypes().OfType(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs index 1abc75cf3a..604f18cdbd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using NPoco; using Umbraco.Core.Cache; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; @@ -50,7 +51,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { // the cache policy will always want everything // even GetMany(ids) gets everything and filters afterwards - if (ids.Any()) throw new Exception("panic"); + if (ids.Any()) throw new PanicException("There can be no ids specified"); return CommonRepository.GetAllTypes().OfType(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs index d96854743e..395723a9f1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using NPoco; using Umbraco.Core.Cache; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; @@ -57,7 +58,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { // the cache policy will always want everything // even GetMany(ids) gets everything and filters afterwards - if (ids.Any()) throw new Exception("panic"); + if (ids.Any()) throw new PanicException("There can be no ids specified"); return CommonRepository.GetAllTypes().OfType(); } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index ca9b7d4034..74af58b94f 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -204,6 +204,7 @@ + diff --git a/src/Umbraco.Tests/Testing/TestOptionAttributeBase.cs b/src/Umbraco.Tests/Testing/TestOptionAttributeBase.cs index db7dd25152..9f72a022f3 100644 --- a/src/Umbraco.Tests/Testing/TestOptionAttributeBase.cs +++ b/src/Umbraco.Tests/Testing/TestOptionAttributeBase.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Reflection; using NUnit.Framework; +using Umbraco.Core.Exceptions; namespace Umbraco.Tests.Testing { @@ -29,7 +30,7 @@ namespace Umbraco.Tests.Testing var methodName = test.MethodName; var type = Type.GetType(typeName, true); if (type == null) - throw new Exception("panic"); // makes no sense + throw new PanicException($"Could not resolve the type from type name {typeName}"); // makes no sense var methodInfo = type.GetMethod(methodName); // what about overloads? var options = GetTestOptions(methodInfo); return options; @@ -53,7 +54,7 @@ namespace Umbraco.Tests.Testing { if (other == null) throw new ArgumentNullException(nameof(other)); if (!(Merge((TestOptionAttributeBase) other) is TOptions merged)) - throw new Exception("panic"); + throw new PanicException("Could not merge test options"); return merged; } diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs index fc029eabe4..528d5f6de5 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Web.Models.ContentEditing; using Umbraco.Core.Services; +using Umbraco.Core.Exceptions; namespace Umbraco.Web.Models.Mapping { @@ -577,7 +578,7 @@ namespace Umbraco.Web.Models.Mapping udiType = Constants.UdiEntityType.DocumentType; break; default: - throw new Exception("panic"); + throw new PanicException($"Source is of type {source.GetType()} which isn't supported here"); } return Udi.Create(udiType, source.Key); diff --git a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs index 141bdea7b6..abb4eae13f 100644 --- a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs @@ -2,6 +2,7 @@ using System.Linq; using Newtonsoft.Json.Linq; using Umbraco.Core; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; @@ -56,7 +57,7 @@ namespace Umbraco.Web.PropertyEditors } if (!(editorValue.DataTypeConfiguration is MultipleTextStringConfiguration config)) - throw new Exception("panic"); + throw new PanicException($"editorValue.DataTypeConfiguration is {editorValue.DataTypeConfiguration.GetType()} but must be {typeof(MultipleTextStringConfiguration)}"); var max = config.Maximum; //The legacy property editor saved this data as new line delimited! strange but we have to maintain that. diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 2d501fa3b5..8e2cf7bc3c 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using CSharpTest.Net.Collections; using Umbraco.Core; +using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Scoping; @@ -1044,7 +1045,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (_genObj == null) _genObjs.Enqueue(_genObj = new GenObj(snapGen)); else if (_genObj.Gen != snapGen) - throw new Exception("panic"); + throw new PanicException($"The generation {_genObj.Gen} does not equal the snapshot generation {snapGen}"); } else { diff --git a/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs b/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs index c5b1df1206..9671949ff0 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Umbraco.Core.Exceptions; using Umbraco.Core.Scoping; using Umbraco.Web.PublishedCache.NuCache.Snap; @@ -371,7 +372,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // if we have one already, ensure it's consistent else if (_genObj.Gen != snapGen) - throw new Exception("panic"); + throw new PanicException($"The generation {_genObj.Gen} does not equal the snapshot generation {snapGen}"); } else { From 6482d849e31a949c40deaa8418d9ec6fc8a07321 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 30 Jul 2019 22:59:33 +1000 Subject: [PATCH 215/776] Moves test to different fixture --- .../Services/ContentTypeServiceTests.cs | 355 +---------------- .../ContentTypeServiceVariantsTests.cs | 356 ++++++++++++++++++ 2 files changed, 357 insertions(+), 354 deletions(-) diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index a0b5f01a1f..f2a4368ae4 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -53,360 +53,7 @@ namespace Umbraco.Tests.Services Assert.IsTrue(contentType.IsElement); } - [Test] - public void Change_Content_Type_Variation_Clears_Redirects() - { - //create content type with a property type that varies by culture - var contentType = MockedContentTypes.CreateBasicContentType(); - contentType.Variations = ContentVariation.Nothing; - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) - { - Alias = "title", - Name = "Title", - Description = "", - Mandatory = false, - SortOrder = 1, - DataTypeId = -88, - Variations = ContentVariation.Nothing - }); - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); - ServiceContext.ContentTypeService.Save(contentType); - var contentType2 = MockedContentTypes.CreateBasicContentType("test"); - ServiceContext.ContentTypeService.Save(contentType2); - - //create some content of this content type - IContent doc = MockedContent.CreateBasicContent(contentType); - doc.Name = "Hello1"; - ServiceContext.ContentService.Save(doc); - - IContent doc2 = MockedContent.CreateBasicContent(contentType2); - ServiceContext.ContentService.Save(doc2); - - ServiceContext.RedirectUrlService.Register("hello/world", doc.Key); - ServiceContext.RedirectUrlService.Register("hello2/world2", doc2.Key); - - Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count()); - Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count()); - - //change variation - contentType.Variations = ContentVariation.Culture; - ServiceContext.ContentTypeService.Save(contentType); - - Assert.AreEqual(0, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count()); - Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count()); - - } - - [Test] - public void Change_Content_Type_From_Invariant_Variant() - { - //create content type with a property type that varies by culture - var contentType = MockedContentTypes.CreateBasicContentType(); - contentType.Variations = ContentVariation.Nothing; - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) - { - Alias = "title", - Name = "Title", - Description = "", - Mandatory = false, - SortOrder = 1, - DataTypeId = -88, - Variations = ContentVariation.Nothing - }); - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); - ServiceContext.ContentTypeService.Save(contentType); - - //create some content of this content type - IContent doc = MockedContent.CreateBasicContent(contentType); - doc.Name = "Hello1"; - doc.SetValue("title", "hello world"); - ServiceContext.ContentService.Save(doc); - - Assert.AreEqual("Hello1", doc.Name); - Assert.AreEqual("hello world", doc.GetValue("title")); - - //change the content type to be variant, we will also update the name here to detect the copy changes - doc.Name = "Hello2"; - ServiceContext.ContentService.Save(doc); - contentType.Variations = ContentVariation.Culture; - ServiceContext.ContentTypeService.Save(contentType); - doc = ServiceContext.ContentService.GetById(doc.Id); //re-get - - Assert.AreEqual("Hello2", doc.GetCultureName("en-US")); - Assert.AreEqual("hello world", doc.GetValue("title")); //We are not checking against en-US here because properties will remain invariant - - //change back property type to be invariant, we will also update the name here to detect the copy changes - doc.SetCultureName("Hello3", "en-US"); - ServiceContext.ContentService.Save(doc); - contentType.Variations = ContentVariation.Nothing; - ServiceContext.ContentTypeService.Save(contentType); - doc = ServiceContext.ContentService.GetById(doc.Id); //re-get - - Assert.AreEqual("Hello3", doc.Name); - Assert.AreEqual("hello world", doc.GetValue("title")); - } - - [Test] - public void Change_Content_Type_From_Variant_Invariant() - { - //create content type with a property type that varies by culture - var contentType = MockedContentTypes.CreateBasicContentType(); - contentType.Variations = ContentVariation.Culture; - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) - { - Alias = "title", - Name = "Title", - Description = "", - Mandatory = false, - SortOrder = 1, - DataTypeId = -88, - Variations = ContentVariation.Culture - }); - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); - ServiceContext.ContentTypeService.Save(contentType); - - //create some content of this content type - IContent doc = MockedContent.CreateBasicContent(contentType); - doc.SetCultureName("Hello1", "en-US"); - doc.SetValue("title", "hello world", "en-US"); - ServiceContext.ContentService.Save(doc); - - Assert.AreEqual("Hello1", doc.GetCultureName("en-US")); - Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); - - //change the content type to be invariant, we will also update the name here to detect the copy changes - doc.SetCultureName("Hello2", "en-US"); - ServiceContext.ContentService.Save(doc); - contentType.Variations = ContentVariation.Nothing; - ServiceContext.ContentTypeService.Save(contentType); - doc = ServiceContext.ContentService.GetById(doc.Id); //re-get - - Assert.AreEqual("Hello2", doc.Name); - Assert.AreEqual("hello world", doc.GetValue("title")); - - //change back property type to be variant, we will also update the name here to detect the copy changes - doc.Name = "Hello3"; - ServiceContext.ContentService.Save(doc); - contentType.Variations = ContentVariation.Culture; - ServiceContext.ContentTypeService.Save(contentType); - doc = ServiceContext.ContentService.GetById(doc.Id); //re-get - - //at this stage all property types were switched to invariant so even though the variant value - //exists it will not be returned because the property type is invariant, - //so this check proves that null will be returned - Assert.IsNull(doc.GetValue("title", "en-US")); - - //we can now switch the property type to be variant and the value can be returned again - contentType.PropertyTypes.First().Variations = ContentVariation.Culture; - ServiceContext.ContentTypeService.Save(contentType); - doc = ServiceContext.ContentService.GetById(doc.Id); //re-get - - Assert.AreEqual("Hello3", doc.GetCultureName("en-US")); - Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); - - } - - [Test] - public void Change_Property_Type_From_Invariant_Variant() - { - //create content type with a property type that varies by culture - var contentType = MockedContentTypes.CreateBasicContentType(); - contentType.Variations = ContentVariation.Nothing; - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) - { - Alias = "title", - Name = "Title", - Description = "", - Mandatory = false, - SortOrder = 1, - DataTypeId = -88, - Variations = ContentVariation.Nothing - }); - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); - ServiceContext.ContentTypeService.Save(contentType); - - //create some content of this content type - IContent doc = MockedContent.CreateBasicContent(contentType); - doc.Name = "Home"; - doc.SetValue("title", "hello world"); - ServiceContext.ContentService.Save(doc); - - Assert.AreEqual("hello world", doc.GetValue("title")); - - //change the property type to be variant - contentType.PropertyTypes.First().Variations = ContentVariation.Culture; - ServiceContext.ContentTypeService.Save(contentType); - doc = ServiceContext.ContentService.GetById(doc.Id); //re-get - - Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); - - //change back property type to be invariant - contentType.PropertyTypes.First().Variations = ContentVariation.Nothing; - ServiceContext.ContentTypeService.Save(contentType); - doc = ServiceContext.ContentService.GetById(doc.Id); //re-get - - Assert.AreEqual("hello world", doc.GetValue("title")); - } - - [Test] - public void Change_Property_Type_From_Variant_Invariant() - { - //create content type with a property type that varies by culture - var contentType = MockedContentTypes.CreateBasicContentType(); - contentType.Variations = ContentVariation.Culture; - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) - { - Alias = "title", - Name = "Title", - Description = "", - Mandatory = false, - SortOrder = 1, - DataTypeId = -88, - Variations = ContentVariation.Culture - }); - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); - ServiceContext.ContentTypeService.Save(contentType); - - //create some content of this content type - IContent doc = MockedContent.CreateBasicContent(contentType); - doc.SetCultureName("Home", "en-US"); - doc.SetValue("title", "hello world", "en-US"); - ServiceContext.ContentService.Save(doc); - - Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); - - //change the property type to be invariant - contentType.PropertyTypes.First().Variations = ContentVariation.Nothing; - ServiceContext.ContentTypeService.Save(contentType); - doc = ServiceContext.ContentService.GetById(doc.Id); //re-get - - Assert.AreEqual("hello world", doc.GetValue("title")); - - //change back property type to be variant - contentType.PropertyTypes.First().Variations = ContentVariation.Culture; - ServiceContext.ContentTypeService.Save(contentType); - doc = ServiceContext.ContentService.GetById(doc.Id); //re-get - - Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); - } - - [Test] - public void Change_Property_Type_From_Variant_Invariant_On_A_Composition() - { - //create content type with a property type that varies by culture - var contentType = MockedContentTypes.CreateBasicContentType(); - contentType.Variations = ContentVariation.Culture; - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) - { - Alias = "title", - Name = "Title", - Description = "", - Mandatory = false, - SortOrder = 1, - DataTypeId = -88, - Variations = ContentVariation.Culture - }); - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); - ServiceContext.ContentTypeService.Save(contentType); - - //compose this from the other one - var contentType2 = MockedContentTypes.CreateBasicContentType("test"); - contentType2.Variations = ContentVariation.Culture; - contentType2.AddContentType(contentType); - ServiceContext.ContentTypeService.Save(contentType2); - - //create some content of this content type - IContent doc = MockedContent.CreateBasicContent(contentType); - doc.SetCultureName("Home", "en-US"); - doc.SetValue("title", "hello world", "en-US"); - ServiceContext.ContentService.Save(doc); - - IContent doc2 = MockedContent.CreateBasicContent(contentType2); - doc2.SetCultureName("Home", "en-US"); - doc2.SetValue("title", "hello world", "en-US"); - ServiceContext.ContentService.Save(doc2); - - //change the property type to be invariant - contentType.PropertyTypes.First().Variations = ContentVariation.Nothing; - ServiceContext.ContentTypeService.Save(contentType); - doc = ServiceContext.ContentService.GetById(doc.Id); //re-get - doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get - - Assert.AreEqual("hello world", doc.GetValue("title")); - Assert.AreEqual("hello world", doc2.GetValue("title")); - - //change back property type to be variant - contentType.PropertyTypes.First().Variations = ContentVariation.Culture; - ServiceContext.ContentTypeService.Save(contentType); - doc = ServiceContext.ContentService.GetById(doc.Id); //re-get - doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get - - Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); - Assert.AreEqual("hello world", doc2.GetValue("title", "en-US")); - } - - [Test] - public void Change_Content_Type_From_Variant_Invariant_On_A_Composition() - { - //create content type with a property type that varies by culture - var contentType = MockedContentTypes.CreateBasicContentType(); - contentType.Variations = ContentVariation.Culture; - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) - { - Alias = "title", - Name = "Title", - Description = "", - Mandatory = false, - SortOrder = 1, - DataTypeId = -88, - Variations = ContentVariation.Culture - }); - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); - ServiceContext.ContentTypeService.Save(contentType); - - //compose this from the other one - var contentType2 = MockedContentTypes.CreateBasicContentType("test"); - contentType2.Variations = ContentVariation.Culture; - contentType2.AddContentType(contentType); - ServiceContext.ContentTypeService.Save(contentType2); - - //create some content of this content type - IContent doc = MockedContent.CreateBasicContent(contentType); - doc.SetCultureName("Home", "en-US"); - doc.SetValue("title", "hello world", "en-US"); - ServiceContext.ContentService.Save(doc); - - IContent doc2 = MockedContent.CreateBasicContent(contentType2); - doc2.SetCultureName("Home", "en-US"); - doc2.SetValue("title", "hello world", "en-US"); - ServiceContext.ContentService.Save(doc2); - - //change the content type to be invariant - contentType.Variations = ContentVariation.Nothing; - ServiceContext.ContentTypeService.Save(contentType); - doc = ServiceContext.ContentService.GetById(doc.Id); //re-get - doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get - - Assert.AreEqual("hello world", doc.GetValue("title")); - Assert.AreEqual("hello world", doc2.GetValue("title")); - - //change back content type to be variant - contentType.Variations = ContentVariation.Culture; - ServiceContext.ContentTypeService.Save(contentType); - doc = ServiceContext.ContentService.GetById(doc.Id); //re-get - doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get - - //this will be null because the doc type was changed back to variant but it's property types don't get changed back - Assert.IsNull(doc.GetValue("title", "en-US")); - Assert.IsNull(doc2.GetValue("title", "en-US")); - } + [Test] public void Deleting_Content_Type_With_Hierarchy_Of_Content_Items_Moves_Orphaned_Content_To_Recycle_Bin() diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 18ea95cd98..2748502e2c 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -17,6 +17,7 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Core.Sync; +using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.Testing; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.NuCache; @@ -106,6 +107,361 @@ namespace Umbraco.Tests.Services } } + [Test] + public void Change_Content_Type_Variation_Clears_Redirects() + { + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Nothing; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Nothing + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + ServiceContext.ContentTypeService.Save(contentType); + var contentType2 = MockedContentTypes.CreateBasicContentType("test"); + ServiceContext.ContentTypeService.Save(contentType2); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.Name = "Hello1"; + ServiceContext.ContentService.Save(doc); + + IContent doc2 = MockedContent.CreateBasicContent(contentType2); + ServiceContext.ContentService.Save(doc2); + + ServiceContext.RedirectUrlService.Register("hello/world", doc.Key); + ServiceContext.RedirectUrlService.Register("hello2/world2", doc2.Key); + + Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count()); + Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count()); + + //change variation + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + + Assert.AreEqual(0, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count()); + Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count()); + + } + + [Test] + public void Change_Content_Type_From_Invariant_Variant() + { + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Nothing; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Nothing + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + ServiceContext.ContentTypeService.Save(contentType); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.Name = "Hello1"; + doc.SetValue("title", "hello world"); + ServiceContext.ContentService.Save(doc); + + Assert.AreEqual("Hello1", doc.Name); + Assert.AreEqual("hello world", doc.GetValue("title")); + + //change the content type to be variant, we will also update the name here to detect the copy changes + doc.Name = "Hello2"; + ServiceContext.ContentService.Save(doc); + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("Hello2", doc.GetCultureName("en-US")); + Assert.AreEqual("hello world", doc.GetValue("title")); //We are not checking against en-US here because properties will remain invariant + + //change back property type to be invariant, we will also update the name here to detect the copy changes + doc.SetCultureName("Hello3", "en-US"); + ServiceContext.ContentService.Save(doc); + contentType.Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("Hello3", doc.Name); + Assert.AreEqual("hello world", doc.GetValue("title")); + } + + [Test] + public void Change_Content_Type_From_Variant_Invariant() + { + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Culture + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + ServiceContext.ContentTypeService.Save(contentType); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.SetCultureName("Hello1", "en-US"); + doc.SetValue("title", "hello world", "en-US"); + ServiceContext.ContentService.Save(doc); + + Assert.AreEqual("Hello1", doc.GetCultureName("en-US")); + Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + + //change the content type to be invariant, we will also update the name here to detect the copy changes + doc.SetCultureName("Hello2", "en-US"); + ServiceContext.ContentService.Save(doc); + contentType.Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("Hello2", doc.Name); + Assert.AreEqual("hello world", doc.GetValue("title")); + + //change back property type to be variant, we will also update the name here to detect the copy changes + doc.Name = "Hello3"; + ServiceContext.ContentService.Save(doc); + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + //at this stage all property types were switched to invariant so even though the variant value + //exists it will not be returned because the property type is invariant, + //so this check proves that null will be returned + Assert.IsNull(doc.GetValue("title", "en-US")); + + //we can now switch the property type to be variant and the value can be returned again + contentType.PropertyTypes.First().Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("Hello3", doc.GetCultureName("en-US")); + Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + + } + + [Test] + public void Change_Property_Type_From_Invariant_Variant() + { + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Nothing; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Nothing + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + ServiceContext.ContentTypeService.Save(contentType); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.Name = "Home"; + doc.SetValue("title", "hello world"); + ServiceContext.ContentService.Save(doc); + + Assert.AreEqual("hello world", doc.GetValue("title")); + + //change the property type to be variant + contentType.PropertyTypes.First().Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + + //change back property type to be invariant + contentType.PropertyTypes.First().Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title")); + } + + [Test] + public void Change_Property_Type_From_Variant_Invariant() + { + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Culture + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + ServiceContext.ContentTypeService.Save(contentType); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.SetCultureName("Home", "en-US"); + doc.SetValue("title", "hello world", "en-US"); + ServiceContext.ContentService.Save(doc); + + Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + + //change the property type to be invariant + contentType.PropertyTypes.First().Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title")); + + //change back property type to be variant + contentType.PropertyTypes.First().Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + } + + [Test] + public void Change_Property_Type_From_Variant_Invariant_On_A_Composition() + { + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Culture + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + ServiceContext.ContentTypeService.Save(contentType); + + //compose this from the other one + var contentType2 = MockedContentTypes.CreateBasicContentType("test"); + contentType2.Variations = ContentVariation.Culture; + contentType2.AddContentType(contentType); + ServiceContext.ContentTypeService.Save(contentType2); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.SetCultureName("Home", "en-US"); + doc.SetValue("title", "hello world", "en-US"); + ServiceContext.ContentService.Save(doc); + + IContent doc2 = MockedContent.CreateBasicContent(contentType2); + doc2.SetCultureName("Home", "en-US"); + doc2.SetValue("title", "hello world", "en-US"); + ServiceContext.ContentService.Save(doc2); + + //change the property type to be invariant + contentType.PropertyTypes.First().Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title")); + Assert.AreEqual("hello world", doc2.GetValue("title")); + + //change back property type to be variant + contentType.PropertyTypes.First().Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + Assert.AreEqual("hello world", doc2.GetValue("title", "en-US")); + } + + [Test] + public void Change_Content_Type_From_Variant_Invariant_On_A_Composition() + { + //create content type with a property type that varies by culture + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + var contentCollection = new PropertyTypeCollection(true); + contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) + { + Alias = "title", + Name = "Title", + Description = "", + Mandatory = false, + SortOrder = 1, + DataTypeId = -88, + Variations = ContentVariation.Culture + }); + contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + ServiceContext.ContentTypeService.Save(contentType); + + //compose this from the other one + var contentType2 = MockedContentTypes.CreateBasicContentType("test"); + contentType2.Variations = ContentVariation.Culture; + contentType2.AddContentType(contentType); + ServiceContext.ContentTypeService.Save(contentType2); + + //create some content of this content type + IContent doc = MockedContent.CreateBasicContent(contentType); + doc.SetCultureName("Home", "en-US"); + doc.SetValue("title", "hello world", "en-US"); + ServiceContext.ContentService.Save(doc); + + IContent doc2 = MockedContent.CreateBasicContent(contentType2); + doc2.SetCultureName("Home", "en-US"); + doc2.SetValue("title", "hello world", "en-US"); + ServiceContext.ContentService.Save(doc2); + + //change the content type to be invariant + contentType.Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get + + Assert.AreEqual("hello world", doc.GetValue("title")); + Assert.AreEqual("hello world", doc2.GetValue("title")); + + //change back content type to be variant + contentType.Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get + + //this will be null because the doc type was changed back to variant but it's property types don't get changed back + Assert.IsNull(doc.GetValue("title", "en-US")); + Assert.IsNull(doc2.GetValue("title", "en-US")); + } + [Test] public void Change_Variations_SimpleContentType_VariantToInvariantAndBack() { From 5c8cd6027518acf45e2d960b66b28f564952a33b Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 31 Jul 2019 00:13:40 +1000 Subject: [PATCH 216/776] Writes up a test to show the issue with edited culture flags, this shows that the current fix works but there's still an issue --- .../ContentTypeServiceVariantsTests.cs | 138 +++++++++++++++--- 1 file changed, 121 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 2748502e2c..f5fa4e8795 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -468,10 +468,7 @@ namespace Umbraco.Tests.Services // one simple content type, variant, with both variant and invariant properties // can change it to invariant and back - var languageEn = new Language("en") { IsDefault = true }; - ServiceContext.LocalizationService.Save(languageEn); - var languageFr = new Language("fr"); - ServiceContext.LocalizationService.Save(languageFr); + CreateFrenchAndEnglishLangs(); var contentType = new ContentType(-1) { @@ -499,7 +496,7 @@ namespace Umbraco.Tests.Services contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" }); ServiceContext.ContentTypeService.Save(contentType); - var document = (IContent) new Content("document", -1, contentType); + var document = (IContent)new Content("document", -1, contentType); document.SetCultureName("doc1en", "en"); document.SetCultureName("doc1fr", "fr"); document.SetValue("value1", "v1en", "en"); @@ -682,10 +679,7 @@ namespace Umbraco.Tests.Services // one simple content type, variant, with both variant and invariant properties // can change an invariant property to variant and back - var languageEn = new Language("en") { IsDefault = true }; - ServiceContext.LocalizationService.Save(languageEn); - var languageFr = new Language("fr"); - ServiceContext.LocalizationService.Save(languageFr); + CreateFrenchAndEnglishLangs(); var contentType = new ContentType(-1) { @@ -785,6 +779,114 @@ namespace Umbraco.Tests.Services "{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'en','seg':'','val':'v2'}]},'cultureData':"); } + [Test] + public void Change_Variations_SimpleContentType_VariantPropertyToInvariantAndBack_While_Publishing() + { + // one simple content type, variant, with both variant and invariant properties + // can change an invariant property to variant and back + + CreateFrenchAndEnglishLangs(); + + var contentType = new ContentType(-1) + { + Alias = "contentType", + Name = "contentType", + Variations = ContentVariation.Culture + }; + + var properties = new PropertyTypeCollection(true) + { + new PropertyType("value1", ValueStorageType.Ntext) + { + Alias = "value1", + DataTypeId = -88, + Variations = ContentVariation.Culture + } + }; + + contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" }); + ServiceContext.ContentTypeService.Save(contentType); + + var document = (IContent)new Content("document", -1, contentType); + document.SetCultureName("doc1en", "en"); + document.SetCultureName("doc1fr", "fr"); + document.SetValue("value1", "v1en", "en"); + document.SetValue("value1", "v1fr", "fr"); + ServiceContext.ContentService.Save(document); + + //at this stage there will be 4 property values stored in the DB for "value1" against "en" and "fr" for both edited/published versions, + //for the published values, these will be null + + document = ServiceContext.ContentService.GetById(document.Id); + Assert.AreEqual("doc1en", document.Name); + Assert.AreEqual("doc1en", document.GetCultureName("en")); + Assert.AreEqual("doc1fr", document.GetCultureName("fr")); + Assert.AreEqual("v1en", document.GetValue("value1", "en")); + Assert.AreEqual("v1fr", document.GetValue("value1", "fr")); + Assert.IsTrue(document.IsCultureEdited("en")); //This will be true because the edited value isn't the same as the published value + Assert.IsTrue(document.IsCultureEdited("fr")); //This will be true because the edited value isn't the same as the published value + Assert.IsTrue(document.Edited); + + // switch property type to Nothing + contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + + document = ServiceContext.ContentService.GetById(document.Id); + Assert.IsTrue(document.Edited); + + // publish the document + document.SetValue("value1", "v1inv"); //update the invariant value + ServiceContext.ContentService.SaveAndPublish(document); + + //at this stage there will be 6 property values stored in the DB for "value1" against "en", "fr" and null for both edited/published versions, + //for the published values for the cultures, these will still be null but the invariant edited/published values will both be "v1inv". + + document = ServiceContext.ContentService.GetById(document.Id); + Assert.AreEqual("doc1en", document.Name); + Assert.AreEqual("doc1en", document.GetCultureName("en")); + Assert.AreEqual("doc1fr", document.GetCultureName("fr")); + Assert.IsNull(document.GetValue("value1", "en")); //The values are there but the business logic returns null + Assert.IsNull(document.GetValue("value1", "fr")); //The values are there but the business logic returns null + Assert.AreEqual("v1inv", document.GetValue("value1")); + Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, the culture shouldn't be marked as edited because the invariant property value is published + Assert.IsFalse(document.IsCultureEdited("fr")); //This returns false, the culture shouldn't be marked as edited because the invariant property value is published + Assert.IsFalse(document.Edited); + + // switch property back to Culture + contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); + + document = ServiceContext.ContentService.GetById(document.Id); + Assert.AreEqual("v1inv", document.GetValue("value1", "en")); //The invariant property value gets copied over to the default language + Assert.AreEqual("v1fr", document.GetValue("value1", "fr")); + Assert.IsFalse(document.IsCultureEdited("en")); //The invariant published AND edited values are copied over to the default language + //TODO: This fails - for some reason in the DB, the DocumentCultureVariationDto Edited flag is set to false for the FR culture + // I'm assuming this is somehow done during the ContentTypeService.Save method when changing variation. It should not be false but instead + // true because the values for it's edited vs published version are different. Perhaps it's false because that is the default boolean value and + // this row gets deleted somewhere along the way? + Assert.IsTrue(document.IsCultureEdited("fr")); //The previously existing french values are there and there is no published value + Assert.IsTrue(document.Edited); + + // publish again + document.SetValue("value1", "v1en2", "en"); //update the value now that it's variant again + document.SetValue("value1", "v1fr2", "fr"); //update the value now that it's variant again + ServiceContext.ContentService.SaveAndPublish(document); + + //at this stage there will be 4 property values stored in the DB for "value1" against "en", "fr" for both edited/published versions, + //with the change back to Culture, it deletes the invariant property values. + + document = ServiceContext.ContentService.GetById(document.Id); + Assert.AreEqual("doc1en", document.Name); + Assert.AreEqual("doc1en", document.GetCultureName("en")); + Assert.AreEqual("doc1fr", document.GetCultureName("fr")); + Assert.AreEqual("v1en2", document.GetValue("value1", "en")); + Assert.AreEqual("v1fr2", document.GetValue("value1", "fr")); + Assert.IsNull(document.GetValue("value1")); //The value is there but the business logic returns null + Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, the variant property value has been published + Assert.IsFalse(document.IsCultureEdited("fr")); //This returns false, the variant property value has been published + Assert.IsFalse(document.Edited); + } + [Test] public void Change_Variations_ComposedContentType_1() { @@ -793,10 +895,7 @@ namespace Umbraco.Tests.Services // can change the composing content type to invariant and back // can change the composed content type to invariant and back - var languageEn = new Language("en") { IsDefault = true }; - ServiceContext.LocalizationService.Save(languageEn); - var languageFr = new Language("fr"); - ServiceContext.LocalizationService.Save(languageFr); + CreateFrenchAndEnglishLangs(); var composing = new ContentType(-1) { @@ -925,10 +1024,7 @@ namespace Umbraco.Tests.Services // can change the composing content type to invariant and back // can change the variant composed content type to invariant and back - var languageEn = new Language("en") { IsDefault = true }; - ServiceContext.LocalizationService.Save(languageEn); - var languageFr = new Language("fr"); - ServiceContext.LocalizationService.Save(languageFr); + CreateFrenchAndEnglishLangs(); var composing = new ContentType(-1) { @@ -1110,5 +1206,13 @@ namespace Umbraco.Tests.Services AssertJsonStartsWith(document2.Id, "{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':"); } + + private void CreateFrenchAndEnglishLangs() + { + var languageEn = new Language("en") { IsDefault = true }; + ServiceContext.LocalizationService.Save(languageEn); + var languageFr = new Language("fr"); + ServiceContext.LocalizationService.Save(languageFr); + } } } From 87e7cec02eb2d7eb062aaef2c37f226fa5c286b6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 31 Jul 2019 18:30:34 +1000 Subject: [PATCH 217/776] WIP - commiting what i have. Solved part of the problem but there are others. --- .../Persistence/Factories/PropertyFactory.cs | 21 +- .../Implement/ContentTypeRepository.cs | 4 +- .../Implement/ContentTypeRepositoryBase.cs | 263 +++++++++++++++++- .../Implement/DocumentRepository.cs | 25 +- .../Implement/MediaTypeRepository.cs | 4 +- .../Implement/MemberTypeRepository.cs | 4 +- .../Repositories/ContentTypeRepositoryTest.cs | 9 +- .../Repositories/DocumentRepositoryTest.cs | 2 +- .../Repositories/DomainRepositoryTest.cs | 2 +- .../Repositories/MediaRepositoryTest.cs | 3 +- .../Repositories/MediaTypeRepositoryTest.cs | 3 +- .../Repositories/MemberRepositoryTest.cs | 3 +- .../Repositories/MemberTypeRepositoryTest.cs | 3 +- .../PublicAccessRepositoryTest.cs | 2 +- .../Repositories/TagRepositoryTest.cs | 5 +- .../Repositories/TemplateRepositoryTest.cs | 2 +- .../Repositories/UserRepositoryTest.cs | 5 +- .../Services/ContentServicePerformanceTest.cs | 12 +- .../Services/ContentServiceTests.cs | 2 +- .../ContentTypeServiceVariantsTests.cs | 31 ++- 20 files changed, 344 insertions(+), 61 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index f0937a3781..4e7dc1e982 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -105,9 +105,14 @@ namespace Umbraco.Core.Persistence.Factories /// /// out parameter indicating that one or more properties have been edited /// out parameter containing a collection of edited cultures when the contentVariation varies by culture + /// + /// out parameter containing a collection of edited cultures that are currently persisted in the database which is used to maintain the edited state + /// of each culture value (in cases where variance is being switched) + /// /// public static IEnumerable BuildDtos(ContentVariation contentVariation, int currentVersionId, int publishedVersionId, IEnumerable properties, - ILanguageRepository languageRepository, out bool edited, out HashSet editedCultures) + ILanguageRepository languageRepository, out bool edited, + out HashSet editedCultures) { var propertyDataDtos = new List(); edited = false; @@ -141,14 +146,14 @@ namespace Umbraco.Core.Persistence.Factories if (propertyValue.EditedValue != null) propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue)); - // property.Values will contain ALL of it's values, both variant and invariant which will be populated if the administrator has previously - // changed the property type to be variant vs invariant. - // We need to check for this scenario here because otherwise the editedCultures and edited flags - // will end up incorrectly so here we need to only process edited cultures based on the - // current value type and how the property varies. + //// property.Values will contain ALL of it's values, both variant and invariant which will be populated if the administrator has previously + //// changed the property type to be variant vs invariant. + //// We need to check for this scenario here because otherwise the editedCultures and edited flags + //// will end up incorrectly so here we need to only process edited cultures based on the + //// current value type and how the property varies. - if (property.PropertyType.VariesByCulture() && isInvariantValue) continue; - if (!property.PropertyType.VariesByCulture() && isCultureValue) continue; + //if (property.PropertyType.VariesByCulture() && isInvariantValue) continue; + //if (!property.PropertyType.VariesByCulture() && isCultureValue) continue; // use explicit equals here, else object comparison fails at comparing eg strings var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs index 9d77eb0990..8b84145027 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs @@ -18,8 +18,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// internal class ContentTypeRepository : ContentTypeRepositoryBase, IContentTypeRepository { - public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository) - : base(scopeAccessor, cache, logger, commonRepository) + public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository) + : base(scopeAccessor, cache, logger, commonRepository, languageRepository) { } protected override bool SupportsPublishing => ContentType.SupportsPublishingConst; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 22c9244d8f..756435692a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -15,6 +15,7 @@ using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; using Umbraco.Core.Services; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -26,14 +27,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement internal abstract class ContentTypeRepositoryBase : NPocoRepositoryBase, IReadRepository where TEntity : class, IContentTypeComposition { - protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository) + protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository) : base(scopeAccessor, cache, logger) { CommonRepository = commonRepository; + LanguageRepository = languageRepository; } protected IContentTypeCommonRepository CommonRepository { get; } - + protected ILanguageRepository LanguageRepository { get; } protected abstract bool SupportsPublishing { get; } public IEnumerable> Move(TEntity moving, EntityContainer container) @@ -646,10 +648,16 @@ AND umbracoNode.id <> @id", case ContentVariation.Culture: CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL); CopyTagData(null, defaultLanguageId, propertyTypeIds, impactedL); + RenormalizeDocumentCultureVariations(propertyTypeIds, impactedL); + //TODO: Here we need to normalize the umbracoDocumentCultureVariation table for it's edited flags which are calculated based + //on changed property or name values break; case ContentVariation.Nothing: CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL); CopyTagData(defaultLanguageId, null, propertyTypeIds, impactedL); + RenormalizeDocumentCultureVariations(propertyTypeIds, impactedL); + //TODO: Here we need to normalize the umbracoDocumentCultureVariation table for it's edited flags which are calculated based + //on changed property or name values break; case ContentVariation.CultureAndSegment: case ContentVariation.Segment: @@ -659,6 +667,55 @@ AND umbracoNode.id <> @id", } } + //private HashSet GetEditedCultures(ContentVariation contentVariation, int currentVersionId, int publishedVersionId, IEnumerable properties) + //{ + // HashSet editedCultures = null; // don't allocate unless necessary + // string defaultCulture = null; //don't allocate unless necessary + + // var entityVariesByCulture = contentVariation.VariesByCulture(); + + // // create dtos for each property values, but only for values that do actually exist + // // ie have a non-null value, everything else is just ignored and won't have a db row + + // foreach (var property in properties) + // { + // if (property.PropertyType.SupportsPublishing) + // { + // //create the resulting hashset if it's not created and the entity varies by culture + // if (entityVariesByCulture && editedCultures == null) + // editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase); + + // // publishing = deal with edit and published values + // foreach (var propertyValue in property.Values) + // { + // var isInvariantValue = propertyValue.Culture == null; + // var isCultureValue = propertyValue.Culture != null && propertyValue.Segment == null; + + // // use explicit equals here, else object comparison fails at comparing eg strings + // var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue); + + // if (entityVariesByCulture && !sameValues) + // { + // if (isCultureValue) + // { + // editedCultures.Add(propertyValue.Culture); // report culture as edited + // } + // else if (isInvariantValue) + // { + // // flag culture as edited if it contains an edited invariant property + // if (defaultCulture == null) + // defaultCulture = languageRepository.GetDefaultIsoCode(); + + // editedCultures.Add(defaultCulture); + // } + // } + // } + // } + // } + + // return editedCultures; + //} + /// /// Moves variant data for a content type variation change. /// @@ -963,6 +1020,208 @@ AND umbracoNode.id <> @id", Database.Execute(sqlDelete); } + + } + + /// + /// Re-normalizes the edited value in the umbracoDocumentCultureVariation table when property variations are changed + /// + /// + /// + /// + /// If this is not done, then in some cases the "edited" value for a particular culture for a document will remain true when it should be false + /// if the property was changed to invariant. In order to do this we need to recalculate this value based on the values stored for each + /// property, culture and current/published version. The end result is to update the edited value in the umbracoDocumentCultureVariation table so we + /// make sure to join this table with the lookups so that only relevant data is returned. + /// + private void RenormalizeDocumentCultureVariations(IReadOnlyCollection propertyTypeIds, IReadOnlyCollection contentTypeIds = null) + { + + var defaultLang = LanguageRepository.GetDefaultId(); + + //This will build up a query to get the property values of both the current and the published version so that we can check + //based on the current variance of each item to see if it's 'edited' value should be true/false. + + var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0); + if (whereInArgsCount > 2000) + throw new NotSupportedException("Too many property/content types."); + + var propertySql = Sql() + .Select() + .AndSelect(x => x.NodeId, x => x.Current) + .AndSelect(x => x.Published) + .AndSelect(x => x.Variations) + .From() + .InnerJoin().On((left, right) => left.Id == right.VersionId) + .InnerJoin().On((left, right) => left.Id == right.PropertyTypeId); + + if (contentTypeIds != null) + { + propertySql.InnerJoin().On((c, cversion) => c.NodeId == cversion.NodeId); + } + + propertySql.LeftJoin().On((docversion, cversion) => cversion.Id == docversion.Id) + .Where((docversion, cversion) => cversion.Current || docversion.Published) + .WhereIn(x => x.PropertyTypeId, propertyTypeIds); + + if (contentTypeIds != null) + { + propertySql.WhereIn(x => x.ContentTypeId, contentTypeIds); + } + + propertySql + .OrderBy(x => x.NodeId) + .OrderBy(x => x.PropertyTypeId, x => x.LanguageId, x => x.VersionId); + + //keep track of this node/lang to mark or unmark as edited + var editedVersions = new Dictionary<(int nodeId, int? langId), bool>(); + + var nodeId = -1; + var propertyTypeId = -1; + PropertyValueVersionDto pubRow = null; + + //This is a QUERY we are not fetching this all into memory so we cannot make any changes during this iteration, we are just collecting data. + //Published data will always come before Current data based on the version id sort. + //There will only be one published row (max) and one current row per property. + foreach (var row in Database.Query(propertySql)) + { + //make sure to reset on each node/property change + if (nodeId != row.NodeId || propertyTypeId != row.PropertyTypeId) + { + nodeId = row.NodeId; + propertyTypeId = row.PropertyTypeId; + pubRow = null; + } + + if (row.Published) + pubRow = row; + + if (row.Current) + { + var propVariations = (ContentVariation)row.Variations; + + //if this prop doesn't vary but the row has a lang assigned or vice versa, flag this as not edited + if (!propVariations.VariesByCulture() && row.LanguageId.HasValue + || propVariations.VariesByCulture() && !row.LanguageId.HasValue) + { + //Flag this as not edited for this node/lang if the key doesn't exist + if (!editedVersions.TryGetValue((row.NodeId, row.LanguageId), out _)) + editedVersions.Add((row.NodeId, row.LanguageId), false); + } + else if (pubRow == null) + { + //this would mean that that this property is 'edited' since there is no published version + editedVersions.Add((row.NodeId, row.LanguageId), true); + } + //compare the property values, if they differ from versions then flag the current version as edited + else if (IsPropertyValueChanged(pubRow, row)) + { + //Here we would check if the property is invariant, in which case the edited language should be indicated by the default lang + editedVersions[(row.NodeId, !propVariations.VariesByCulture() ? defaultLang : row.LanguageId)] = true; + } + + //reset + pubRow = null; + } + } + + //lookup all matching rows in umbracoDocumentCultureVariation + var docCultureVariationsToUpdate = Database.Fetch( + Sql().Select().From() + .WhereIn(x => x.LanguageId, editedVersions.Keys.Select(x => x.langId).ToList()) + .WhereIn(x => x.NodeId, editedVersions.Keys.Select(x => x.nodeId))) + //convert to dictionary with the same key type + .ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x); + + foreach (var ev in editedVersions) + { + if (docCultureVariationsToUpdate.TryGetValue(ev.Key, out var docVariations)) + { + //check if it needs updating + if (docVariations.Edited != ev.Value) + { + docVariations.Edited = ev.Value; + Database.Update(docVariations); + } + } + else + { + //the row doesn't exist but needs creating + //TODO: Does this ever happen?? Need to see if we can test this + } + } + + ////Generate SQL to lookup the current name vs the publish name for each language + //var nameSql = Sql() + // .Select("cv1", x => x.NodeId, x => Alias(x.Id, "currentVersion")) + // .AndSelect("cvcv1", x => x.LanguageId, x => Alias(x.Name, "currentName")) + // .AndSelect("cvcv2", x => Alias(x.Name, "publishedName")) + // .AndSelect("dv", x => Alias(x.Id, "publishedVersion")) + // .AndSelect("dcv", x => x.Id, x => x.Edited) + // .From("cvcv1") + // .InnerJoin("cv1") + // .On((left, right) => left.Id == right.VersionId, "cv1", "cvcv1") + // .InnerJoin("dcv") + // .On((left, right, other) => left.NodeId == right.NodeId && left.LanguageId == other.LanguageId, "dcv", "cv1", "cvcv1") + // .LeftJoin(nested => + // nested.InnerJoin("dv") + // .On((left, right) => left.Id == right.Id && right.Published, "cv2", "dv"), "cv2") + // .On((left, right) => left.NodeId == right.NodeId, "cv1", "cv2") + // .LeftJoin("cvcv2") + // .On((left, right, other) => left.VersionId == right.Id && left.LanguageId == other.LanguageId, "cvcv2", "cv2", "cvcv1") + // .Where(x => x.Current, "cv1") + // .OrderBy("cv1.nodeId, cvcv1.versionId, cvcv1.languageId"); + + //var names = Database.Fetch(nameSql); + + } + + private static bool IsPropertyValueChanged(PropertyValueVersionDto pubRow, PropertyValueVersionDto row) + { + return !pubRow.TextValue.IsNullOrWhiteSpace() && pubRow.TextValue != row.TextValue + || !pubRow.VarcharValue.IsNullOrWhiteSpace() && pubRow.VarcharValue != row.VarcharValue + || pubRow.DateValue.HasValue && pubRow.DateValue != row.DateValue + || pubRow.DecimalValue.HasValue && pubRow.DecimalValue != row.DecimalValue + || pubRow.IntValue.HasValue && pubRow.IntValue != row.IntValue; + } + + private class NameCompareDto + { + public int NodeId { get; set; } + public int CurrentVersion { get; set; } + public int LanguageId { get; set; } + public string CurrentName { get; set; } + public string PublishedName { get; set; } + public int? PublishedVersion { get; set; } + public int Id { get; set; } // the Id of the DocumentCultureVariationDto + public bool Edited { get; set; } + } + + private class PropertyValueVersionDto + { + public int VersionId { get; set; } + public int PropertyTypeId { get; set; } + public int? LanguageId { get; set; } + public string Segment { get; set; } + public int? IntValue { get; set; } + + private decimal? _decimalValue; + [Column("decimalValue")] + public decimal? DecimalValue + { + get => _decimalValue; + set => _decimalValue = value?.Normalize(); + } + + public DateTime? DateValue { get; set; } + public string VarcharValue { get; set; } + public string TextValue { get; set; } + + public int NodeId { get; set; } + public bool Current { get; set; } + public bool Published { get; set; } + + public byte Variations { get; set; } } private void DeletePropertyType(int contentTypeId, int propertyTypeId) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 30a2927cc8..0dbfc61b8c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -386,7 +386,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing)); // insert document variations - Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures)); + Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures)); } // refresh content @@ -571,7 +571,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing)); // insert document variations - Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures)); + Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures)); } // refresh content @@ -1297,25 +1297,30 @@ namespace Umbraco.Core.Persistence.Repositories.Implement }; } - private IEnumerable GetDocumentVariationDtos(IContent content, bool publishing, HashSet editedCultures) + private IEnumerable GetDocumentVariationDtos(IContent content, HashSet editedCultures) { var allCultures = content.AvailableCultures.Union(content.PublishedCultures); // union = distinct foreach (var culture in allCultures) - yield return new DocumentCultureVariationDto + { + var dto = new DocumentCultureVariationDto { NodeId = content.Id, LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."), Culture = culture, Name = content.GetCultureName(culture) ?? content.GetPublishName(culture), - - // note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem - Available = content.IsCultureAvailable(culture), - Published = content.IsCulturePublished(culture), - Edited = content.IsCultureAvailable(culture) && - (!content.IsCulturePublished(culture) || (editedCultures != null && editedCultures.Contains(culture))) + Published = content.IsCulturePublished(culture) }; + + // note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem + + dto.Edited = content.IsCultureAvailable(culture) && + (!content.IsCulturePublished(culture) || (editedCultures != null && editedCultures.Contains(culture))); + + yield return dto; + } + } private class ContentVariation diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs index 1abc75cf3a..a2c7cc3f44 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaTypeRepository.cs @@ -17,8 +17,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// internal class MediaTypeRepository : ContentTypeRepositoryBase, IMediaTypeRepository { - public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository) - : base(scopeAccessor, cache, logger, commonRepository) + public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository) + : base(scopeAccessor, cache, logger, commonRepository, languageRepository) { } protected override bool SupportsPublishing => MediaType.SupportsPublishingConst; diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs index d96854743e..b4d6033b9b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberTypeRepository.cs @@ -18,8 +18,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// internal class MemberTypeRepository : ContentTypeRepositoryBase, IMemberTypeRepository { - public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository) - : base(scopeAccessor, cache, logger, commonRepository) + public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository) + : base(scopeAccessor, cache, logger, commonRepository, languageRepository) { } protected override bool SupportsPublishing => MemberType.SupportsPublishingConst; diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 53f150f140..f953b9cce6 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -29,10 +29,11 @@ namespace Umbraco.Tests.Persistence.Repositories private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out ContentTypeRepository contentTypeRepository) { + var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger); var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(scopeAccessor, AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled); - contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository); + contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository); var languageRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger); var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; @@ -40,9 +41,10 @@ namespace Umbraco.Tests.Persistence.Repositories private ContentTypeRepository CreateRepository(IScopeAccessor scopeAccessor) { + var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger); var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled); - var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository); + var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository); return contentTypeRepository; } @@ -50,7 +52,8 @@ namespace Umbraco.Tests.Persistence.Repositories { var templateRepository = new TemplateRepository(scopeAccessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled); - var contentTypeRepository = new MediaTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository); + var langRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger); + var contentTypeRepository = new MediaTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository); return contentTypeRepository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index fd797662c0..4d62ec8301 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -67,8 +67,8 @@ namespace Umbraco.Tests.Persistence.Repositories templateRepository = new TemplateRepository(scopeAccessor, appCaches, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger); var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches); - contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, commonRepository); var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger); + contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository); var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs index f00b2fd046..628f8d75a7 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DomainRepositoryTest.cs @@ -23,8 +23,8 @@ namespace Umbraco.Tests.Persistence.Repositories var templateRepository = new TemplateRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); - contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, commonRepository); languageRepository = new LanguageRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); + contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, commonRepository, languageRepository); documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); var domainRepository = new DomainRepository(accessor, Core.Cache.AppCaches.Disabled, Logger); return domainRepository; diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index 1d9cf6d022..e2123df9e3 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -38,7 +38,8 @@ namespace Umbraco.Tests.Persistence.Repositories var templateRepository = new TemplateRepository(scopeAccessor, appCaches, Logger, TestObjects.GetFileSystemsMock()); var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches); - mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository); + var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger); + mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository); var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger); var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of()); return repository; diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs index f302d1d992..bb3286daed 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -23,7 +23,8 @@ namespace Umbraco.Tests.Persistence.Repositories var cacheHelper = AppCaches.Disabled; var templateRepository = new TemplateRepository((IScopeAccessor)provider, cacheHelper, Logger, TestObjects.GetFileSystemsMock()); var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, templateRepository, AppCaches); - return new MediaTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository); + var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches, Logger); + return new MediaTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository); } private EntityContainerRepository CreateContainerRepository(IScopeProvider provider) diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index a5f7f08f22..17b16ad7ab 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -31,7 +31,8 @@ namespace Umbraco.Tests.Persistence.Repositories var accessor = (IScopeAccessor) provider; var templateRepository = Mock.Of(); var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); - memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository); + var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger); + memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository); memberGroupRepository = new MemberGroupRepository(accessor, AppCaches.Disabled, Logger); var tagRepo = new TagRepository(accessor, AppCaches.Disabled, Logger); var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of()); diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs index 79e8e43804..4b9f3096ce 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -24,7 +24,8 @@ namespace Umbraco.Tests.Persistence.Repositories { var templateRepository = Mock.Of(); var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, templateRepository, AppCaches); - return new MemberTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of(), commonRepository); + var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Mock.Of()); + return new MemberTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of(), commonRepository, languageRepository); } [Test] diff --git a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs index 803eff25af..56041c24aa 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/PublicAccessRepositoryTest.cs @@ -308,8 +308,8 @@ namespace Umbraco.Tests.Persistence.Repositories var templateRepository = new TemplateRepository(accessor, AppCaches, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, AppCaches, Logger); var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); - contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository); var languageRepository = new LanguageRepository(accessor, AppCaches, Logger); + contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository); var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs index b6cc4dc50d..e3de2c2892 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs @@ -956,8 +956,8 @@ namespace Umbraco.Tests.Persistence.Repositories var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled); - contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository); var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger); + contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository); var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; } @@ -968,7 +968,8 @@ namespace Umbraco.Tests.Persistence.Repositories var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled); - mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository); + var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger); + mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository); var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of()); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index 13cbd463fb..b0f9a5335b 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -239,8 +239,8 @@ namespace Umbraco.Tests.Persistence.Repositories var tagRepository = new TagRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository(ScopeProvider, templateRepository, AppCaches); - var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, commonRepository); var languageRepository = new LanguageRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger); + var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, commonRepository, languageRepository); var contentRepo = new DocumentRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage"); diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index b550091591..3e5919d7f3 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -26,7 +26,8 @@ namespace Umbraco.Tests.Persistence.Repositories var accessor = (IScopeAccessor) provider; var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); - mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of(), commonRepository); + var languageRepository = new LanguageRepository(accessor, AppCaches, Logger); + mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of(), commonRepository, languageRepository); var tagRepository = new TagRepository(accessor, AppCaches, Mock.Of()); var repository = new MediaRepository(accessor, AppCaches, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of()); return repository; @@ -44,8 +45,8 @@ namespace Umbraco.Tests.Persistence.Repositories templateRepository = new TemplateRepository(accessor, AppCaches, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, AppCaches, Logger); var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); - contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository); var languageRepository = new LanguageRepository(accessor, AppCaches, Logger); + contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository); var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; } diff --git a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs index ed5e6073ac..ef80672baf 100644 --- a/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs +++ b/src/Umbraco.Tests/Services/ContentServicePerformanceTest.cs @@ -166,8 +166,8 @@ namespace Umbraco.Tests.Services var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository); var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository); var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); // Act @@ -200,8 +200,8 @@ namespace Umbraco.Tests.Services var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository); var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository); var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); // Act @@ -232,8 +232,8 @@ namespace Umbraco.Tests.Services var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository((IScopeAccessor) provider, tRepository, AppCaches); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository); - var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); + var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository); var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); // Act @@ -267,8 +267,8 @@ namespace Umbraco.Tests.Services var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches); - var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository); - var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger); + var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger); + var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository); var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository); // Act diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 222f40aeed..7acfd994d3 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -3008,8 +3008,8 @@ namespace Umbraco.Tests.Services var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock()); var tagRepository = new TagRepository(accessor, AppCaches.Disabled, Logger); var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches); - contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository); var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger); + contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository); var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository); return repository; } diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index f5fa4e8795..53a39ffe40 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -810,12 +810,18 @@ namespace Umbraco.Tests.Services var document = (IContent)new Content("document", -1, contentType); document.SetCultureName("doc1en", "en"); document.SetCultureName("doc1fr", "fr"); - document.SetValue("value1", "v1en", "en"); - document.SetValue("value1", "v1fr", "fr"); - ServiceContext.ContentService.Save(document); + document.SetValue("value1", "v1en-init", "en"); + document.SetValue("value1", "v1fr-init", "fr"); + ServiceContext.ContentService.SaveAndPublish(document); //all values are published which means the document is not 'edited' - //at this stage there will be 4 property values stored in the DB for "value1" against "en" and "fr" for both edited/published versions, - //for the published values, these will be null + document = ServiceContext.ContentService.GetById(document.Id); + Assert.IsFalse(document.IsCultureEdited("en")); + Assert.IsFalse(document.IsCultureEdited("fr")); + Assert.IsFalse(document.Edited); + + document.SetValue("value1", "v1en", "en"); //change the property culture value, so now this culture will be edited + document.SetValue("value1", "v1fr", "fr"); //change the property culture value, so now this culture will be edited + ServiceContext.ContentService.Save(document); document = ServiceContext.ContentService.GetById(document.Id); Assert.AreEqual("doc1en", document.Name); @@ -829,18 +835,17 @@ namespace Umbraco.Tests.Services // switch property type to Nothing contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing; - ServiceContext.ContentTypeService.Save(contentType); + ServiceContext.ContentTypeService.Save(contentType); //This is going to have to re-normalize the "Edited" flag document = ServiceContext.ContentService.GetById(document.Id); + Assert.IsTrue(document.IsCultureEdited("en")); //This will remain true because there is now a pending change for the invariant property data which is flagged under the default lang + Assert.IsFalse(document.IsCultureEdited("fr")); //This will be false because nothing has changed for this culture and the property no longer reflects variant changes Assert.IsTrue(document.Edited); - // publish the document - document.SetValue("value1", "v1inv"); //update the invariant value + //update the invariant value and publish + document.SetValue("value1", "v1inv"); ServiceContext.ContentService.SaveAndPublish(document); - //at this stage there will be 6 property values stored in the DB for "value1" against "en", "fr" and null for both edited/published versions, - //for the published values for the cultures, these will still be null but the invariant edited/published values will both be "v1inv". - document = ServiceContext.ContentService.GetById(document.Id); Assert.AreEqual("doc1en", document.Name); Assert.AreEqual("doc1en", document.GetCultureName("en")); @@ -848,8 +853,8 @@ namespace Umbraco.Tests.Services Assert.IsNull(document.GetValue("value1", "en")); //The values are there but the business logic returns null Assert.IsNull(document.GetValue("value1", "fr")); //The values are there but the business logic returns null Assert.AreEqual("v1inv", document.GetValue("value1")); - Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, the culture shouldn't be marked as edited because the invariant property value is published - Assert.IsFalse(document.IsCultureEdited("fr")); //This returns false, the culture shouldn't be marked as edited because the invariant property value is published + Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, everything is published - however in the DB this is not the case for the "en" version of the property + Assert.IsFalse(document.IsCultureEdited("fr")); //This returns false, everything is published Assert.IsFalse(document.Edited); // switch property back to Culture From ff952a6df169bbbe11d8dad29c653c7433fcbf05 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 1 Aug 2019 15:54:44 +1000 Subject: [PATCH 218/776] Passes test! Now to add more tests/assertions and then the logic to renormalize based on name changes. --- .../Persistence/Factories/PropertyFactory.cs | 23 ++-- .../Implement/ContentTypeRepositoryBase.cs | 112 +++++++++++------- .../Implement/DocumentRepository.cs | 2 +- .../ContentTypeServiceVariantsTests.cs | 21 ++-- 4 files changed, 95 insertions(+), 63 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index 4e7dc1e982..33dabe1b24 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -104,10 +104,9 @@ namespace Umbraco.Core.Persistence.Factories /// The properties to map /// /// out parameter indicating that one or more properties have been edited - /// out parameter containing a collection of edited cultures when the contentVariation varies by culture - /// - /// out parameter containing a collection of edited cultures that are currently persisted in the database which is used to maintain the edited state - /// of each culture value (in cases where variance is being switched) + /// + /// Out parameter containing a collection of edited cultures when the contentVariation varies by culture. + /// The value of this will be used to populate the edited cultures in the umbracoDocumentCultureVariation table. /// /// public static IEnumerable BuildDtos(ContentVariation contentVariation, int currentVersionId, int publishedVersionId, IEnumerable properties, @@ -146,14 +145,16 @@ namespace Umbraco.Core.Persistence.Factories if (propertyValue.EditedValue != null) propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue)); - //// property.Values will contain ALL of it's values, both variant and invariant which will be populated if the administrator has previously - //// changed the property type to be variant vs invariant. - //// We need to check for this scenario here because otherwise the editedCultures and edited flags - //// will end up incorrectly so here we need to only process edited cultures based on the - //// current value type and how the property varies. + // property.Values will contain ALL of it's values, both variant and invariant which will be populated if the + // administrator has previously changed the property type to be variant vs invariant. + // We need to check for this scenario here because otherwise the editedCultures and edited flags + // will end up incorrectly set in the umbracoDocumentCultureVariation table so here we need to + // only process edited cultures based on the current value type and how the property varies. + // The above logic will still persist the currently saved property value for each culture in case the admin + // decides to swap the property's variance again, in which case the edited flag will be recalculated. - //if (property.PropertyType.VariesByCulture() && isInvariantValue) continue; - //if (!property.PropertyType.VariesByCulture() && isCultureValue) continue; + if (property.PropertyType.VariesByCulture() && isInvariantValue || !property.PropertyType.VariesByCulture() && isCultureValue) + continue; // use explicit equals here, else object comparison fails at comparing eg strings var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 756435692a..b29565a35e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -648,14 +648,14 @@ AND umbracoNode.id <> @id", case ContentVariation.Culture: CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL); CopyTagData(null, defaultLanguageId, propertyTypeIds, impactedL); - RenormalizeDocumentCultureVariations(propertyTypeIds, impactedL); + RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL); //TODO: Here we need to normalize the umbracoDocumentCultureVariation table for it's edited flags which are calculated based //on changed property or name values break; case ContentVariation.Nothing: CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL); CopyTagData(defaultLanguageId, null, propertyTypeIds, impactedL); - RenormalizeDocumentCultureVariations(propertyTypeIds, impactedL); + RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL); //TODO: Here we need to normalize the umbracoDocumentCultureVariation table for it's edited flags which are calculated based //on changed property or name values break; @@ -1024,19 +1024,17 @@ AND umbracoNode.id <> @id", } /// - /// Re-normalizes the edited value in the umbracoDocumentCultureVariation table when property variations are changed + /// Re-normalizes the edited value in the umbracoDocumentCultureVariation and umbracoDocument table when variations are changed /// /// /// /// /// If this is not done, then in some cases the "edited" value for a particular culture for a document will remain true when it should be false /// if the property was changed to invariant. In order to do this we need to recalculate this value based on the values stored for each - /// property, culture and current/published version. The end result is to update the edited value in the umbracoDocumentCultureVariation table so we - /// make sure to join this table with the lookups so that only relevant data is returned. + /// property, culture and current/published version. /// - private void RenormalizeDocumentCultureVariations(IReadOnlyCollection propertyTypeIds, IReadOnlyCollection contentTypeIds = null) + private void RenormalizeDocumentEditedFlags(IReadOnlyCollection propertyTypeIds, IReadOnlyCollection contentTypeIds = null) { - var defaultLang = LanguageRepository.GetDefaultId(); //This will build up a query to get the property values of both the current and the published version so that we can check @@ -1073,14 +1071,16 @@ AND umbracoNode.id <> @id", .OrderBy(x => x.NodeId) .OrderBy(x => x.PropertyTypeId, x => x.LanguageId, x => x.VersionId); - //keep track of this node/lang to mark or unmark as edited - var editedVersions = new Dictionary<(int nodeId, int? langId), bool>(); - + //keep track of this node/lang to mark or unmark a culture as edited + var editedLanguageVersions = new Dictionary<(int nodeId, int? langId), bool>(); + //keep track of which node to mark or unmark as edited + var editedDocument = new Dictionary(); var nodeId = -1; var propertyTypeId = -1; + PropertyValueVersionDto pubRow = null; - //This is a QUERY we are not fetching this all into memory so we cannot make any changes during this iteration, we are just collecting data. + //This is a reader (Query), we are not fetching this all into memory so we cannot make any changes during this iteration, we are just collecting data. //Published data will always come before Current data based on the version id sort. //There will only be one published row (max) and one current row per property. foreach (var row in Database.Query(propertySql)) @@ -1105,19 +1105,24 @@ AND umbracoNode.id <> @id", || propVariations.VariesByCulture() && !row.LanguageId.HasValue) { //Flag this as not edited for this node/lang if the key doesn't exist - if (!editedVersions.TryGetValue((row.NodeId, row.LanguageId), out _)) - editedVersions.Add((row.NodeId, row.LanguageId), false); + if (!editedLanguageVersions.TryGetValue((row.NodeId, row.LanguageId), out _)) + editedLanguageVersions.Add((row.NodeId, row.LanguageId), false); + + //mark as false if the item doesn't exist, else coerce to true + editedDocument[row.NodeId] = editedDocument.TryGetValue(row.NodeId, out var edited) ? (edited |= false) : false; } else if (pubRow == null) { //this would mean that that this property is 'edited' since there is no published version - editedVersions.Add((row.NodeId, row.LanguageId), true); + editedLanguageVersions.Add((row.NodeId, row.LanguageId), true); + editedDocument[row.NodeId] = true; } //compare the property values, if they differ from versions then flag the current version as edited else if (IsPropertyValueChanged(pubRow, row)) { //Here we would check if the property is invariant, in which case the edited language should be indicated by the default lang - editedVersions[(row.NodeId, !propVariations.VariesByCulture() ? defaultLang : row.LanguageId)] = true; + editedLanguageVersions[(row.NodeId, !propVariations.VariesByCulture() ? defaultLang : row.LanguageId)] = true; + editedDocument[row.NodeId] = true; } //reset @@ -1125,32 +1130,6 @@ AND umbracoNode.id <> @id", } } - //lookup all matching rows in umbracoDocumentCultureVariation - var docCultureVariationsToUpdate = Database.Fetch( - Sql().Select().From() - .WhereIn(x => x.LanguageId, editedVersions.Keys.Select(x => x.langId).ToList()) - .WhereIn(x => x.NodeId, editedVersions.Keys.Select(x => x.nodeId))) - //convert to dictionary with the same key type - .ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x); - - foreach (var ev in editedVersions) - { - if (docCultureVariationsToUpdate.TryGetValue(ev.Key, out var docVariations)) - { - //check if it needs updating - if (docVariations.Edited != ev.Value) - { - docVariations.Edited = ev.Value; - Database.Update(docVariations); - } - } - else - { - //the row doesn't exist but needs creating - //TODO: Does this ever happen?? Need to see if we can test this - } - } - ////Generate SQL to lookup the current name vs the publish name for each language //var nameSql = Sql() // .Select("cv1", x => x.NodeId, x => Alias(x.Id, "currentVersion")) @@ -1172,8 +1151,57 @@ AND umbracoNode.id <> @id", // .Where(x => x.Current, "cv1") // .OrderBy("cv1.nodeId, cvcv1.versionId, cvcv1.languageId"); - //var names = Database.Fetch(nameSql); + ////This is a reader (Query), we are not fetching this all into memory so we cannot make any changes during this iteration, we are just collecting data. + //foreach (var name in Database.Query(nameSql)) + //{ + // if (name.CurrentName != name.PublishedName) + // { + // } + //} + + //lookup all matching rows in umbracoDocumentCultureVariation + var docCultureVariationsToUpdate = editedLanguageVersions.InGroupsOf(2000) + .SelectMany(_ => Database.Fetch( + Sql().Select().From() + .WhereIn(x => x.LanguageId, editedLanguageVersions.Keys.Select(x => x.langId).ToList()) + .WhereIn(x => x.NodeId, editedLanguageVersions.Keys.Select(x => x.nodeId)))) + //convert to dictionary with the same key type + .ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x); + + var toUpdate = new List(); + foreach (var ev in editedLanguageVersions) + { + if (docCultureVariationsToUpdate.TryGetValue(ev.Key, out var docVariations)) + { + //check if it needs updating + if (docVariations.Edited != ev.Value) + { + docVariations.Edited = ev.Value; + toUpdate.Add(docVariations); + } + } + else + { + //the row doesn't exist but needs creating + //TODO: Does this ever happen?? Need to see if we can test this + throw new PanicException($"The existing DocumentCultureVariationDto was not found for node {ev.Key.nodeId} and language {ev.Key.langId}"); + } + } + + //Now bulk update the table DocumentCultureVariationDto, once for edited = true, another for edited = false + foreach (var editValue in toUpdate.GroupBy(x => x.Edited)) + { + Database.Execute(Sql().Update(u => u.Set(x => x.Edited, editValue.Key)) + .WhereIn(x => x.Id, editValue.Select(x => x.Id))); + } + + //Now bulk update the umbracoDocument table + foreach(var editValue in editedDocument.GroupBy(x => x.Value)) + { + Database.Execute(Sql().Update(u => u.Set(x => x.Edited, editValue.Key)) + .WhereIn(x => x.NodeId, editValue.Select(x => x.Key))); + } } private static bool IsPropertyValueChanged(PropertyValueVersionDto pubRow, PropertyValueVersionDto row) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 0dbfc61b8c..c18921b59e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -511,7 +511,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement entity.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id documentVersionDto.Published = false; // non-published version - Database.Insert(documentVersionDto); + Database.Insert(documentVersionDto); } // replace the property data (rather than updating) diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 53a39ffe40..2fed37c389 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -828,12 +828,14 @@ namespace Umbraco.Tests.Services Assert.AreEqual("doc1en", document.GetCultureName("en")); Assert.AreEqual("doc1fr", document.GetCultureName("fr")); Assert.AreEqual("v1en", document.GetValue("value1", "en")); + Assert.AreEqual("v1en-init", document.GetValue("value1", "en", published: true)); Assert.AreEqual("v1fr", document.GetValue("value1", "fr")); + Assert.AreEqual("v1fr-init", document.GetValue("value1", "fr", published: true)); Assert.IsTrue(document.IsCultureEdited("en")); //This will be true because the edited value isn't the same as the published value Assert.IsTrue(document.IsCultureEdited("fr")); //This will be true because the edited value isn't the same as the published value Assert.IsTrue(document.Edited); - // switch property type to Nothing + // switch property type to Invariant contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing; ServiceContext.ContentTypeService.Save(contentType); //This is going to have to re-normalize the "Edited" flag @@ -852,9 +854,12 @@ namespace Umbraco.Tests.Services Assert.AreEqual("doc1fr", document.GetCultureName("fr")); Assert.IsNull(document.GetValue("value1", "en")); //The values are there but the business logic returns null Assert.IsNull(document.GetValue("value1", "fr")); //The values are there but the business logic returns null + Assert.IsNull(document.GetValue("value1", "en", published: true)); //The values are there but the business logic returns null + Assert.IsNull(document.GetValue("value1", "fr", published: true)); //The values are there but the business logic returns null Assert.AreEqual("v1inv", document.GetValue("value1")); - Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, everything is published - however in the DB this is not the case for the "en" version of the property - Assert.IsFalse(document.IsCultureEdited("fr")); //This returns false, everything is published + Assert.AreEqual("v1inv", document.GetValue("value1", published: true)); + Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, everything is published + Assert.IsFalse(document.IsCultureEdited("fr")); //This will be false because nothing has changed for this culture and the property no longer reflects variant changes Assert.IsFalse(document.Edited); // switch property back to Culture @@ -863,14 +868,12 @@ namespace Umbraco.Tests.Services document = ServiceContext.ContentService.GetById(document.Id); Assert.AreEqual("v1inv", document.GetValue("value1", "en")); //The invariant property value gets copied over to the default language - Assert.AreEqual("v1fr", document.GetValue("value1", "fr")); + Assert.AreEqual("v1inv", document.GetValue("value1", "en", published: true)); + Assert.AreEqual("v1fr", document.GetValue("value1", "fr")); //values are still retained + Assert.AreEqual("v1fr-init", document.GetValue("value1", "fr", published: true)); //values are still retained Assert.IsFalse(document.IsCultureEdited("en")); //The invariant published AND edited values are copied over to the default language - //TODO: This fails - for some reason in the DB, the DocumentCultureVariationDto Edited flag is set to false for the FR culture - // I'm assuming this is somehow done during the ContentTypeService.Save method when changing variation. It should not be false but instead - // true because the values for it's edited vs published version are different. Perhaps it's false because that is the default boolean value and - // this row gets deleted somewhere along the way? Assert.IsTrue(document.IsCultureEdited("fr")); //The previously existing french values are there and there is no published value - Assert.IsTrue(document.Edited); + Assert.IsTrue(document.Edited); //Will be flagged edited again because the french culture had pending changes // publish again document.SetValue("value1", "v1en2", "en"); //update the value now that it's variant again From 10958158781737e970ceca2d6a845a8d9110333e Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 1 Aug 2019 16:09:52 +1000 Subject: [PATCH 219/776] removes notes --- src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 2fed37c389..0c54cf5975 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -880,9 +880,6 @@ namespace Umbraco.Tests.Services document.SetValue("value1", "v1fr2", "fr"); //update the value now that it's variant again ServiceContext.ContentService.SaveAndPublish(document); - //at this stage there will be 4 property values stored in the DB for "value1" against "en", "fr" for both edited/published versions, - //with the change back to Culture, it deletes the invariant property values. - document = ServiceContext.ContentService.GetById(document.Id); Assert.AreEqual("doc1en", document.Name); Assert.AreEqual("doc1en", document.GetCultureName("en")); From 9538981730d71698e90a00aba92fea700b587eef Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 1 Aug 2019 18:49:05 +1000 Subject: [PATCH 220/776] Adds more assertions and tests and validates variations --- src/Umbraco.Core/Models/IContentTypeBase.cs | 2 +- .../Implement/ContentTypeRepositoryBase.cs | 68 ++-- .../Implement/DocumentRepository.cs | 10 +- .../ContentTypeServiceVariantsTests.cs | 290 ++++++------------ 4 files changed, 133 insertions(+), 237 deletions(-) diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs index 5f1fe6ed49..ed87c5f320 100644 --- a/src/Umbraco.Core/Models/IContentTypeBase.cs +++ b/src/Umbraco.Core/Models/IContentTypeBase.cs @@ -101,7 +101,7 @@ namespace Umbraco.Core.Models PropertyGroupCollection PropertyGroups { get; set; } /// - /// Gets all local property types belonging to a group, across all local property groups. + /// Gets all local property types all local property groups or ungrouped. /// IEnumerable PropertyTypes { get; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index b29565a35e..8f88c71bf0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -100,6 +100,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected void PersistNewBaseContentType(IContentTypeComposition entity) { + ValidateVariations(entity); + var dto = ContentTypeFactory.BuildContentTypeDto(entity); //Cannot add a duplicate content type @@ -165,11 +167,11 @@ AND umbracoNode.nodeObjectType = @objectType", foreach (var allowedContentType in entity.AllowedContentTypes) { Database.Insert(new ContentTypeAllowedContentTypeDto - { - Id = entity.Id, - AllowedId = allowedContentType.Id.Value, - SortOrder = allowedContentType.SortOrder - }); + { + Id = entity.Id, + AllowedId = allowedContentType.Id.Value, + SortOrder = allowedContentType.SortOrder + }); } @@ -216,6 +218,8 @@ AND umbracoNode.nodeObjectType = @objectType", protected void PersistUpdatedBaseContentType(IContentTypeComposition entity) { + ValidateVariations(entity); + var dto = ContentTypeFactory.BuildContentTypeDto(entity); // ensure the alias is not used already @@ -372,7 +376,7 @@ AND umbracoNode.id <> @id", foreach (var propertyGroup in entity.PropertyGroups) { // insert or update group - var groupDto = PropertyGroupFactory.BuildGroupDto(propertyGroup,entity.Id); + var groupDto = PropertyGroupFactory.BuildGroupDto(propertyGroup, entity.Id); var groupId = propertyGroup.HasIdentity ? Database.Update(groupDto) : Convert.ToInt32(Database.Insert(groupDto)); @@ -390,7 +394,7 @@ AND umbracoNode.id <> @id", //check if the content type variation has been changed var contentTypeVariationDirty = entity.IsPropertyDirty("Variations"); - var oldContentTypeVariation = (ContentVariation) dtoPk.Variations; + var oldContentTypeVariation = (ContentVariation)dtoPk.Variations; var newContentTypeVariation = entity.Variations; var contentTypeVariationChanging = contentTypeVariationDirty && oldContentTypeVariation != newContentTypeVariation; if (contentTypeVariationChanging) @@ -451,7 +455,7 @@ AND umbracoNode.id <> @id", // via composition, with their original variations (ie not filtered by this // content type variations - we need this true value to make decisions. - foreach (var propertyType in ((ContentTypeCompositionBase) entity).RawComposedPropertyTypes) + foreach (var propertyType in ((ContentTypeCompositionBase)entity).RawComposedPropertyTypes) { if (propertyType.VariesBySegment() || newContentTypeVariation.VariesBySegment()) throw new NotSupportedException(); // TODO: support this @@ -520,6 +524,19 @@ AND umbracoNode.id <> @id", CommonRepository.ClearCache(); // always } + /// + /// Ensures that no property types are flagged for a variance that is not supported by the content type itself + /// + /// + private void ValidateVariations(IContentTypeComposition entity) + { + //if the entity does not vary at all, then the property cannot have a variance value greater than it + if (entity.Variations == ContentVariation.Nothing) + foreach (var prop in entity.PropertyTypes) + if (prop.Variations > entity.Variations) + throw new InvalidOperationException($"The property {prop.Alias} cannot have variations of {prop.Variations} with the content type variations of {entity.Variations}"); + } + private IEnumerable GetImpactedContentTypes(IContentTypeComposition contentType, IEnumerable all) { var impact = new List(); @@ -527,12 +544,12 @@ AND umbracoNode.id <> @id", var tree = new Dictionary>(); foreach (var x in all) - foreach (var y in x.ContentTypeComposition) - { - if (!tree.TryGetValue(y.Id, out var list)) - list = tree[y.Id] = new List(); - list.Add(x); - } + foreach (var y in x.ContentTypeComposition) + { + if (!tree.TryGetValue(y.Id, out var list)) + list = tree[y.Id] = new List(); + list.Add(x); + } var nset = new List(); do @@ -574,7 +591,7 @@ AND umbracoNode.id <> @id", // new property type, ignore if (!oldVariations.TryGetValue(propertyType.Id, out var oldVariationB)) continue; - var oldVariation = (ContentVariation) oldVariationB; // NPoco cannot fetch directly + var oldVariation = (ContentVariation)oldVariationB; // NPoco cannot fetch directly // only those property types that *actually* changed var newVariation = propertyType.Variations; @@ -638,7 +655,7 @@ AND umbracoNode.id <> @id", var impactedL = impacted.Select(x => x.Id).ToList(); //Group by the "To" variation so we can bulk update in the correct batches - foreach(var grouping in propertyTypeChanges.GroupBy(x => x.Value.ToVariation)) + foreach (var grouping in propertyTypeChanges.GroupBy(x => x.Value.ToVariation)) { var propertyTypeIds = grouping.Select(x => x.Key).ToList(); var toVariation = grouping.Key; @@ -1037,8 +1054,8 @@ AND umbracoNode.id <> @id", { var defaultLang = LanguageRepository.GetDefaultId(); - //This will build up a query to get the property values of both the current and the published version so that we can check - //based on the current variance of each item to see if it's 'edited' value should be true/false. + //This will build up a query to get the property values of both the current and the published version so that we can check + //based on the current variance of each item to see if it's 'edited' value should be true/false. var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0); if (whereInArgsCount > 2000) @@ -1077,7 +1094,7 @@ AND umbracoNode.id <> @id", var editedDocument = new Dictionary(); var nodeId = -1; var propertyTypeId = -1; - + PropertyValueVersionDto pubRow = null; //This is a reader (Query), we are not fetching this all into memory so we cannot make any changes during this iteration, we are just collecting data. @@ -1087,7 +1104,7 @@ AND umbracoNode.id <> @id", { //make sure to reset on each node/property change if (nodeId != row.NodeId || propertyTypeId != row.PropertyTypeId) - { + { nodeId = row.NodeId; propertyTypeId = row.PropertyTypeId; pubRow = null; @@ -1114,7 +1131,7 @@ AND umbracoNode.id <> @id", else if (pubRow == null) { //this would mean that that this property is 'edited' since there is no published version - editedLanguageVersions.Add((row.NodeId, row.LanguageId), true); + editedLanguageVersions[(row.NodeId, row.LanguageId)] = true; editedDocument[row.NodeId] = true; } //compare the property values, if they differ from versions then flag the current version as edited @@ -1181,10 +1198,9 @@ AND umbracoNode.id <> @id", toUpdate.Add(docVariations); } } - else + else if (ev.Key.langId.HasValue) { - //the row doesn't exist but needs creating - //TODO: Does this ever happen?? Need to see if we can test this + //This should never happen! If a property culture is flagged as edited then the culture must exist at the document level throw new PanicException($"The existing DocumentCultureVariationDto was not found for node {ev.Key.nodeId} and language {ev.Key.langId}"); } } @@ -1197,7 +1213,7 @@ AND umbracoNode.id <> @id", } //Now bulk update the umbracoDocument table - foreach(var editValue in editedDocument.GroupBy(x => x.Value)) + foreach (var editValue in editedDocument.GroupBy(x => x.Value)) { Database.Execute(Sql().Update(u => u.Set(x => x.Edited, editValue.Key)) .WhereIn(x => x.NodeId, editValue.Select(x => x.Key))); @@ -1226,7 +1242,7 @@ AND umbracoNode.id <> @id", } private class PropertyValueVersionDto - { + { public int VersionId { get; set; } public int PropertyTypeId { get; set; } public int? LanguageId { get; set; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index c18921b59e..344557d815 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -1310,14 +1310,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Name = content.GetCultureName(culture) ?? content.GetPublishName(culture), Available = content.IsCultureAvailable(culture), - Published = content.IsCulturePublished(culture) + Published = content.IsCulturePublished(culture), + // note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem + Edited = content.IsCultureAvailable(culture) && + (!content.IsCulturePublished(culture) || (editedCultures != null && editedCultures.Contains(culture))) }; - // note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem - - dto.Edited = content.IsCultureAvailable(culture) && - (!content.IsCulturePublished(culture) || (editedCultures != null && editedCultures.Contains(culture))); - yield return dto; } diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 0c54cf5975..4b8262d4a5 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -110,7 +110,6 @@ namespace Umbraco.Tests.Services [Test] public void Change_Content_Type_Variation_Clears_Redirects() { - //create content type with a property type that varies by culture var contentType = MockedContentTypes.CreateBasicContentType(); contentType.Variations = ContentVariation.Nothing; var contentCollection = new PropertyTypeCollection(true); @@ -154,8 +153,7 @@ namespace Umbraco.Tests.Services [Test] public void Change_Content_Type_From_Invariant_Variant() - { - //create content type with a property type that varies by culture + { var contentType = MockedContentTypes.CreateBasicContentType(); contentType.Variations = ContentVariation.Nothing; var contentCollection = new PropertyTypeCollection(true); @@ -205,7 +203,6 @@ namespace Umbraco.Tests.Services [Test] public void Change_Content_Type_From_Variant_Invariant() { - //create content type with a property type that varies by culture var contentType = MockedContentTypes.CreateBasicContentType(); contentType.Variations = ContentVariation.Culture; var contentCollection = new PropertyTypeCollection(true); @@ -264,39 +261,49 @@ namespace Umbraco.Tests.Services } [Test] - public void Change_Property_Type_From_Invariant_Variant() + public void Change_Property_Type_From_To_Variant_On_Invariant_Content_Type() { - //create content type with a property type that varies by culture var contentType = MockedContentTypes.CreateBasicContentType(); contentType.Variations = ContentVariation.Nothing; - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) - { - Alias = "title", - Name = "Title", - Description = "", - Mandatory = false, - SortOrder = 1, - DataTypeId = -88, - Variations = ContentVariation.Nothing - }); - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + var properties = CreatePropertyCollection(("title", ContentVariation.Nothing)); + contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" }); + ServiceContext.ContentTypeService.Save(contentType); + + //change the property type to be variant + contentType.PropertyTypes.First().Variations = ContentVariation.Culture; + + //Cannot change a property type to be variant if the content type itself is not variant + Assert.Throws(() => ServiceContext.ContentTypeService.Save(contentType)); + } + + [Test] + public void Change_Property_Type_From_Invariant_Variant() + { + var contentType = MockedContentTypes.CreateBasicContentType(); + contentType.Variations = ContentVariation.Culture; + var properties = CreatePropertyCollection(("title", ContentVariation.Nothing)); + contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" }); ServiceContext.ContentTypeService.Save(contentType); //create some content of this content type IContent doc = MockedContent.CreateBasicContent(contentType); - doc.Name = "Home"; + doc.SetCultureName("Home", "en-US"); doc.SetValue("title", "hello world"); ServiceContext.ContentService.Save(doc); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get Assert.AreEqual("hello world", doc.GetValue("title")); - + Assert.IsTrue(doc.IsCultureEdited("en-US")); //invariant prop changes show up on default lang + Assert.IsTrue(doc.Edited); + //change the property type to be variant contentType.PropertyTypes.First().Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(contentType); doc = ServiceContext.ContentService.GetById(doc.Id); //re-get Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + Assert.IsTrue(doc.IsCultureEdited("en-US")); + Assert.IsTrue(doc.Edited); //change back property type to be invariant contentType.PropertyTypes.First().Variations = ContentVariation.Nothing; @@ -304,6 +311,8 @@ namespace Umbraco.Tests.Services doc = ServiceContext.ContentService.GetById(doc.Id); //re-get Assert.AreEqual("hello world", doc.GetValue("title")); + Assert.IsTrue(doc.IsCultureEdited("en-US")); //invariant prop changes show up on default lang + Assert.IsTrue(doc.Edited); } [Test] @@ -470,28 +479,11 @@ namespace Umbraco.Tests.Services CreateFrenchAndEnglishLangs(); - var contentType = new ContentType(-1) - { - Alias = "contentType", - Name = "contentType", - Variations = ContentVariation.Culture - }; + var contentType = CreateContentType(ContentVariation.Culture); - var properties = new PropertyTypeCollection(true) - { - new PropertyType("value1", ValueStorageType.Ntext) - { - Alias = "value1", - DataTypeId = -88, - Variations = ContentVariation.Culture - }, - new PropertyType("value2", ValueStorageType.Ntext) - { - Alias = "value2", - DataTypeId = -88, - Variations = ContentVariation.Nothing - } - }; + var properties = CreatePropertyCollection( + ("value1", ContentVariation.Culture), + ("value2", ContentVariation.Nothing)); contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" }); ServiceContext.ContentTypeService.Save(contentType); @@ -579,28 +571,11 @@ namespace Umbraco.Tests.Services var languageFr = new Language("fr"); ServiceContext.LocalizationService.Save(languageFr); - var contentType = new ContentType(-1) - { - Alias = "contentType", - Name = "contentType", - Variations = ContentVariation.Nothing - }; + var contentType = CreateContentType(ContentVariation.Nothing); - var properties = new PropertyTypeCollection(true) - { - new PropertyType("value1", ValueStorageType.Ntext) - { - Alias = "value1", - DataTypeId = -88, - Variations = ContentVariation.Nothing - }, - new PropertyType("value2", ValueStorageType.Ntext) - { - Alias = "value2", - DataTypeId = -88, - Variations = ContentVariation.Nothing - } - }; + var properties = CreatePropertyCollection( + ("value1", ContentVariation.Nothing), + ("value2", ContentVariation.Nothing)); contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" }); ServiceContext.ContentTypeService.Save(contentType); @@ -681,28 +656,11 @@ namespace Umbraco.Tests.Services CreateFrenchAndEnglishLangs(); - var contentType = new ContentType(-1) - { - Alias = "contentType", - Name = "contentType", - Variations = ContentVariation.Culture - }; + var contentType = CreateContentType(ContentVariation.Culture); - var properties = new PropertyTypeCollection(true) - { - new PropertyType("value1", ValueStorageType.Ntext) - { - Alias = "value1", - DataTypeId = -88, - Variations = ContentVariation.Culture - }, - new PropertyType("value2", ValueStorageType.Ntext) - { - Alias = "value2", - DataTypeId = -88, - Variations = ContentVariation.Nothing - } - }; + var properties = CreatePropertyCollection( + ("value1", ContentVariation.Culture), + ("value2", ContentVariation.Nothing)); contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" }); ServiceContext.ContentTypeService.Save(contentType); @@ -780,29 +738,16 @@ namespace Umbraco.Tests.Services } [Test] - public void Change_Variations_SimpleContentType_VariantPropertyToInvariantAndBack_While_Publishing() + public void Change_Property_Variations_From_Variant_To_Invariant_And_Ensure_Edited_Values_Are_Renormalized() { // one simple content type, variant, with both variant and invariant properties // can change an invariant property to variant and back CreateFrenchAndEnglishLangs(); - var contentType = new ContentType(-1) - { - Alias = "contentType", - Name = "contentType", - Variations = ContentVariation.Culture - }; + var contentType = CreateContentType(ContentVariation.Culture); - var properties = new PropertyTypeCollection(true) - { - new PropertyType("value1", ValueStorageType.Ntext) - { - Alias = "value1", - DataTypeId = -88, - Variations = ContentVariation.Culture - } - }; + var properties = CreatePropertyCollection(("value1", ContentVariation.Culture)); contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" }); ServiceContext.ContentTypeService.Save(contentType); @@ -902,54 +847,20 @@ namespace Umbraco.Tests.Services CreateFrenchAndEnglishLangs(); - var composing = new ContentType(-1) - { - Alias = "composing", - Name = "composing", - Variations = ContentVariation.Culture - }; + var composing = CreateContentType(ContentVariation.Culture, "composing"); - var properties1 = new PropertyTypeCollection(true) - { - new PropertyType("value11", ValueStorageType.Ntext) - { - Alias = "value11", - DataTypeId = -88, - Variations = ContentVariation.Culture - }, - new PropertyType("value12", ValueStorageType.Ntext) - { - Alias = "value12", - DataTypeId = -88, - Variations = ContentVariation.Nothing - } - }; + var properties1 = CreatePropertyCollection( + ("value11", ContentVariation.Culture), + ("value12", ContentVariation.Nothing)); composing.PropertyGroups.Add(new PropertyGroup(properties1) { Name = "Content" }); ServiceContext.ContentTypeService.Save(composing); - var composed = new ContentType(-1) - { - Alias = "composed", - Name = "composed", - Variations = ContentVariation.Culture - }; + var composed = CreateContentType(ContentVariation.Culture, "composed"); - var properties2 = new PropertyTypeCollection(true) - { - new PropertyType("value21", ValueStorageType.Ntext) - { - Alias = "value21", - DataTypeId = -88, - Variations = ContentVariation.Culture - }, - new PropertyType("value22", ValueStorageType.Ntext) - { - Alias = "value22", - DataTypeId = -88, - Variations = ContentVariation.Nothing - } - }; + var properties2 = CreatePropertyCollection( + ("value21", ContentVariation.Culture), + ("value22", ContentVariation.Nothing)); composed.PropertyGroups.Add(new PropertyGroup(properties2) { Name = "Content" }); composed.AddContentType(composing); @@ -1031,81 +942,30 @@ namespace Umbraco.Tests.Services CreateFrenchAndEnglishLangs(); - var composing = new ContentType(-1) - { - Alias = "composing", - Name = "composing", - Variations = ContentVariation.Culture - }; + var composing = CreateContentType(ContentVariation.Culture, "composing"); - var properties1 = new PropertyTypeCollection(true) - { - new PropertyType("value11", ValueStorageType.Ntext) - { - Alias = "value11", - DataTypeId = -88, - Variations = ContentVariation.Culture - }, - new PropertyType("value12", ValueStorageType.Ntext) - { - Alias = "value12", - DataTypeId = -88, - Variations = ContentVariation.Nothing - } - }; + var properties1 = CreatePropertyCollection( + ("value11", ContentVariation.Culture), + ("value12", ContentVariation.Nothing)); composing.PropertyGroups.Add(new PropertyGroup(properties1) { Name = "Content" }); ServiceContext.ContentTypeService.Save(composing); - var composed1 = new ContentType(-1) - { - Alias = "composed1", - Name = "composed1", - Variations = ContentVariation.Culture - }; + var composed1 = CreateContentType(ContentVariation.Culture, "composed1"); - var properties2 = new PropertyTypeCollection(true) - { - new PropertyType("value21", ValueStorageType.Ntext) - { - Alias = "value21", - DataTypeId = -88, - Variations = ContentVariation.Culture - }, - new PropertyType("value22", ValueStorageType.Ntext) - { - Alias = "value22", - DataTypeId = -88, - Variations = ContentVariation.Nothing - } - }; + var properties2 = CreatePropertyCollection( + ("value21", ContentVariation.Culture), + ("value22", ContentVariation.Nothing)); composed1.PropertyGroups.Add(new PropertyGroup(properties2) { Name = "Content" }); composed1.AddContentType(composing); ServiceContext.ContentTypeService.Save(composed1); - var composed2 = new ContentType(-1) - { - Alias = "composed2", - Name = "composed2", - Variations = ContentVariation.Nothing - }; + var composed2 = CreateContentType(ContentVariation.Nothing, "composed2"); - var properties3 = new PropertyTypeCollection(true) - { - new PropertyType("value31", ValueStorageType.Ntext) - { - Alias = "value31", - DataTypeId = -88, - Variations = ContentVariation.Nothing - }, - new PropertyType("value32", ValueStorageType.Ntext) - { - Alias = "value32", - DataTypeId = -88, - Variations = ContentVariation.Nothing - } - }; + var properties3 = CreatePropertyCollection( + ("value31", ContentVariation.Nothing), + ("value32", ContentVariation.Nothing)); composed2.PropertyGroups.Add(new PropertyGroup(properties3) { Name = "Content" }); composed2.AddContentType(composing); @@ -1219,5 +1079,27 @@ namespace Umbraco.Tests.Services var languageFr = new Language("fr"); ServiceContext.LocalizationService.Save(languageFr); } + + private IContentType CreateContentType(ContentVariation variance, string alias = "contentType") => new ContentType(-1) + { + Alias = alias, + Name = alias, + Variations = variance + }; + + private PropertyTypeCollection CreatePropertyCollection(params (string alias, ContentVariation variance)[] props) + { + var propertyCollection = new PropertyTypeCollection(true); + + foreach (var (alias, variance) in props) + propertyCollection.Add(new PropertyType(alias, ValueStorageType.Ntext) + { + Alias = alias, + DataTypeId = -88, + Variations = variance + }); + + return propertyCollection; + } } } From 2e83ac9282384dbcd621582d8316b61a2af19880 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 1 Aug 2019 18:54:19 +1000 Subject: [PATCH 221/776] Cleans up some tests --- .../ContentTypeServiceVariantsTests.cs | 84 +++---------------- 1 file changed, 12 insertions(+), 72 deletions(-) diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 4b8262d4a5..39d71feee9 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -112,18 +112,8 @@ namespace Umbraco.Tests.Services { var contentType = MockedContentTypes.CreateBasicContentType(); contentType.Variations = ContentVariation.Nothing; - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) - { - Alias = "title", - Name = "Title", - Description = "", - Mandatory = false, - SortOrder = 1, - DataTypeId = -88, - Variations = ContentVariation.Nothing - }); - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + var properties = CreatePropertyCollection(("title", ContentVariation.Nothing)); + contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" }); ServiceContext.ContentTypeService.Save(contentType); var contentType2 = MockedContentTypes.CreateBasicContentType("test"); ServiceContext.ContentTypeService.Save(contentType2); @@ -156,18 +146,8 @@ namespace Umbraco.Tests.Services { var contentType = MockedContentTypes.CreateBasicContentType(); contentType.Variations = ContentVariation.Nothing; - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) - { - Alias = "title", - Name = "Title", - Description = "", - Mandatory = false, - SortOrder = 1, - DataTypeId = -88, - Variations = ContentVariation.Nothing - }); - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + var properties = CreatePropertyCollection(("title", ContentVariation.Nothing)); + contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" }); ServiceContext.ContentTypeService.Save(contentType); //create some content of this content type @@ -205,18 +185,8 @@ namespace Umbraco.Tests.Services { var contentType = MockedContentTypes.CreateBasicContentType(); contentType.Variations = ContentVariation.Culture; - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) - { - Alias = "title", - Name = "Title", - Description = "", - Mandatory = false, - SortOrder = 1, - DataTypeId = -88, - Variations = ContentVariation.Culture - }); - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + var properties = CreatePropertyCollection(("title", ContentVariation.Culture)); + contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" }); ServiceContext.ContentTypeService.Save(contentType); //create some content of this content type @@ -321,18 +291,8 @@ namespace Umbraco.Tests.Services //create content type with a property type that varies by culture var contentType = MockedContentTypes.CreateBasicContentType(); contentType.Variations = ContentVariation.Culture; - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) - { - Alias = "title", - Name = "Title", - Description = "", - Mandatory = false, - SortOrder = 1, - DataTypeId = -88, - Variations = ContentVariation.Culture - }); - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + var properties = CreatePropertyCollection(("title", ContentVariation.Culture)); + contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" }); ServiceContext.ContentTypeService.Save(contentType); //create some content of this content type @@ -364,18 +324,8 @@ namespace Umbraco.Tests.Services //create content type with a property type that varies by culture var contentType = MockedContentTypes.CreateBasicContentType(); contentType.Variations = ContentVariation.Culture; - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) - { - Alias = "title", - Name = "Title", - Description = "", - Mandatory = false, - SortOrder = 1, - DataTypeId = -88, - Variations = ContentVariation.Culture - }); - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + var properties = CreatePropertyCollection(("title", ContentVariation.Culture)); + contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" }); ServiceContext.ContentTypeService.Save(contentType); //compose this from the other one @@ -420,18 +370,8 @@ namespace Umbraco.Tests.Services //create content type with a property type that varies by culture var contentType = MockedContentTypes.CreateBasicContentType(); contentType.Variations = ContentVariation.Culture; - var contentCollection = new PropertyTypeCollection(true); - contentCollection.Add(new PropertyType("test", ValueStorageType.Ntext) - { - Alias = "title", - Name = "Title", - Description = "", - Mandatory = false, - SortOrder = 1, - DataTypeId = -88, - Variations = ContentVariation.Culture - }); - contentType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Content", SortOrder = 1 }); + var properties = CreatePropertyCollection(("title", ContentVariation.Culture)); + contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" }); ServiceContext.ContentTypeService.Save(contentType); //compose this from the other one From ded1a22e45e2d2e168f76f388358382d032bc3a6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 1 Aug 2019 19:01:41 +1000 Subject: [PATCH 222/776] fixes validation check --- .../Repositories/Implement/ContentTypeRepositoryBase.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 8f88c71bf0..2705bab39b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -532,9 +532,15 @@ AND umbracoNode.id <> @id", { //if the entity does not vary at all, then the property cannot have a variance value greater than it if (entity.Variations == ContentVariation.Nothing) + { foreach (var prop in entity.PropertyTypes) - if (prop.Variations > entity.Variations) + { + if (prop.IsPropertyDirty(nameof(prop.Variations)) && prop.Variations > entity.Variations) throw new InvalidOperationException($"The property {prop.Alias} cannot have variations of {prop.Variations} with the content type variations of {entity.Variations}"); + } + + } + } private IEnumerable GetImpactedContentTypes(IContentTypeComposition contentType, IEnumerable all) From 9efec9244b51250f85a9ae3eaeb7192356ea5707 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 1 Aug 2019 19:09:19 +1000 Subject: [PATCH 223/776] removes commented out code --- .../Implement/ContentTypeRepositoryBase.cs | 87 +------------------ 1 file changed, 2 insertions(+), 85 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 2705bab39b..f2efb03ba4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -671,16 +671,12 @@ AND umbracoNode.id <> @id", case ContentVariation.Culture: CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL); CopyTagData(null, defaultLanguageId, propertyTypeIds, impactedL); - RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL); - //TODO: Here we need to normalize the umbracoDocumentCultureVariation table for it's edited flags which are calculated based - //on changed property or name values + RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL); break; case ContentVariation.Nothing: CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL); CopyTagData(defaultLanguageId, null, propertyTypeIds, impactedL); - RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL); - //TODO: Here we need to normalize the umbracoDocumentCultureVariation table for it's edited flags which are calculated based - //on changed property or name values + RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL); break; case ContentVariation.CultureAndSegment: case ContentVariation.Segment: @@ -690,55 +686,6 @@ AND umbracoNode.id <> @id", } } - //private HashSet GetEditedCultures(ContentVariation contentVariation, int currentVersionId, int publishedVersionId, IEnumerable properties) - //{ - // HashSet editedCultures = null; // don't allocate unless necessary - // string defaultCulture = null; //don't allocate unless necessary - - // var entityVariesByCulture = contentVariation.VariesByCulture(); - - // // create dtos for each property values, but only for values that do actually exist - // // ie have a non-null value, everything else is just ignored and won't have a db row - - // foreach (var property in properties) - // { - // if (property.PropertyType.SupportsPublishing) - // { - // //create the resulting hashset if it's not created and the entity varies by culture - // if (entityVariesByCulture && editedCultures == null) - // editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase); - - // // publishing = deal with edit and published values - // foreach (var propertyValue in property.Values) - // { - // var isInvariantValue = propertyValue.Culture == null; - // var isCultureValue = propertyValue.Culture != null && propertyValue.Segment == null; - - // // use explicit equals here, else object comparison fails at comparing eg strings - // var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue); - - // if (entityVariesByCulture && !sameValues) - // { - // if (isCultureValue) - // { - // editedCultures.Add(propertyValue.Culture); // report culture as edited - // } - // else if (isInvariantValue) - // { - // // flag culture as edited if it contains an edited invariant property - // if (defaultCulture == null) - // defaultCulture = languageRepository.GetDefaultIsoCode(); - - // editedCultures.Add(defaultCulture); - // } - // } - // } - // } - // } - - // return editedCultures; - //} - /// /// Moves variant data for a content type variation change. /// @@ -1153,36 +1100,6 @@ AND umbracoNode.id <> @id", } } - ////Generate SQL to lookup the current name vs the publish name for each language - //var nameSql = Sql() - // .Select("cv1", x => x.NodeId, x => Alias(x.Id, "currentVersion")) - // .AndSelect("cvcv1", x => x.LanguageId, x => Alias(x.Name, "currentName")) - // .AndSelect("cvcv2", x => Alias(x.Name, "publishedName")) - // .AndSelect("dv", x => Alias(x.Id, "publishedVersion")) - // .AndSelect("dcv", x => x.Id, x => x.Edited) - // .From("cvcv1") - // .InnerJoin("cv1") - // .On((left, right) => left.Id == right.VersionId, "cv1", "cvcv1") - // .InnerJoin("dcv") - // .On((left, right, other) => left.NodeId == right.NodeId && left.LanguageId == other.LanguageId, "dcv", "cv1", "cvcv1") - // .LeftJoin(nested => - // nested.InnerJoin("dv") - // .On((left, right) => left.Id == right.Id && right.Published, "cv2", "dv"), "cv2") - // .On((left, right) => left.NodeId == right.NodeId, "cv1", "cv2") - // .LeftJoin("cvcv2") - // .On((left, right, other) => left.VersionId == right.Id && left.LanguageId == other.LanguageId, "cvcv2", "cv2", "cvcv1") - // .Where(x => x.Current, "cv1") - // .OrderBy("cv1.nodeId, cvcv1.versionId, cvcv1.languageId"); - - ////This is a reader (Query), we are not fetching this all into memory so we cannot make any changes during this iteration, we are just collecting data. - //foreach (var name in Database.Query(nameSql)) - //{ - // if (name.CurrentName != name.PublishedName) - // { - - // } - //} - //lookup all matching rows in umbracoDocumentCultureVariation var docCultureVariationsToUpdate = editedLanguageVersions.InGroupsOf(2000) .SelectMany(_ => Database.Fetch( From 600a29514f17d8917e751ec87f8540da1803d89a Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 1 Aug 2019 21:27:31 +1000 Subject: [PATCH 224/776] more asserts --- .../ContentTypeServiceVariantsTests.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 39d71feee9..381261173b 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -156,8 +156,12 @@ namespace Umbraco.Tests.Services doc.SetValue("title", "hello world"); ServiceContext.ContentService.Save(doc); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get + Assert.AreEqual("Hello1", doc.Name); Assert.AreEqual("hello world", doc.GetValue("title")); + Assert.IsTrue(doc.Edited); + Assert.IsFalse (doc.IsCultureEdited("en-US")); //change the content type to be variant, we will also update the name here to detect the copy changes doc.Name = "Hello2"; @@ -168,6 +172,8 @@ namespace Umbraco.Tests.Services Assert.AreEqual("Hello2", doc.GetCultureName("en-US")); Assert.AreEqual("hello world", doc.GetValue("title")); //We are not checking against en-US here because properties will remain invariant + Assert.IsTrue(doc.Edited); + Assert.IsTrue(doc.IsCultureEdited("en-US")); //change back property type to be invariant, we will also update the name here to detect the copy changes doc.SetCultureName("Hello3", "en-US"); @@ -178,6 +184,8 @@ namespace Umbraco.Tests.Services Assert.AreEqual("Hello3", doc.Name); Assert.AreEqual("hello world", doc.GetValue("title")); + Assert.IsTrue(doc.Edited); + Assert.IsFalse(doc.IsCultureEdited("en-US")); } [Test] @@ -195,8 +203,11 @@ namespace Umbraco.Tests.Services doc.SetValue("title", "hello world", "en-US"); ServiceContext.ContentService.Save(doc); + doc = ServiceContext.ContentService.GetById(doc.Id); //re-get Assert.AreEqual("Hello1", doc.GetCultureName("en-US")); Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + Assert.IsTrue(doc.Edited); + Assert.IsTrue(doc.IsCultureEdited("en-US")); //change the content type to be invariant, we will also update the name here to detect the copy changes doc.SetCultureName("Hello2", "en-US"); @@ -207,6 +218,8 @@ namespace Umbraco.Tests.Services Assert.AreEqual("Hello2", doc.Name); Assert.AreEqual("hello world", doc.GetValue("title")); + Assert.IsTrue(doc.Edited); + Assert.IsFalse(doc.IsCultureEdited("en-US")); //change back property type to be variant, we will also update the name here to detect the copy changes doc.Name = "Hello3"; @@ -219,6 +232,8 @@ namespace Umbraco.Tests.Services //exists it will not be returned because the property type is invariant, //so this check proves that null will be returned Assert.IsNull(doc.GetValue("title", "en-US")); + Assert.IsTrue(doc.Edited); + Assert.IsTrue(doc.IsCultureEdited("en-US")); // this is true because the name change is copied to the default language //we can now switch the property type to be variant and the value can be returned again contentType.PropertyTypes.First().Variations = ContentVariation.Culture; @@ -227,9 +242,12 @@ namespace Umbraco.Tests.Services Assert.AreEqual("Hello3", doc.GetCultureName("en-US")); Assert.AreEqual("hello world", doc.GetValue("title", "en-US")); + Assert.IsTrue(doc.Edited); + Assert.IsTrue(doc.IsCultureEdited("en-US")); } + [Test] public void Change_Property_Type_From_To_Variant_On_Invariant_Content_Type() { From be30bb0ad0706d9a146afd85498d22aa01b055a0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 1 Aug 2019 21:41:43 +1000 Subject: [PATCH 225/776] adds test --- .../ContentTypeServiceVariantsTests.cs | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 381261173b..956de186be 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -795,6 +795,85 @@ namespace Umbraco.Tests.Services Assert.IsFalse(document.Edited); } + [Test] + public void Change_Property_Variations_From_Invariant_To_Variant_And_Ensure_Edited_Values_Are_Renormalized() + { + // one simple content type, variant, with both variant and invariant properties + // can change an invariant property to variant and back + + CreateFrenchAndEnglishLangs(); + + var contentType = CreateContentType(ContentVariation.Culture); + + var properties = CreatePropertyCollection(("value1", ContentVariation.Nothing)); + + contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" }); + ServiceContext.ContentTypeService.Save(contentType); + + var document = (IContent)new Content("document", -1, contentType); + document.SetCultureName("doc1en", "en"); + document.SetCultureName("doc1fr", "fr"); + document.SetValue("value1", "v1en-init"); + ServiceContext.ContentService.SaveAndPublish(document); //all values are published which means the document is not 'edited' + + document = ServiceContext.ContentService.GetById(document.Id); + Assert.IsFalse(document.IsCultureEdited("en")); + Assert.IsFalse(document.IsCultureEdited("fr")); + Assert.IsFalse(document.Edited); + + document.SetValue("value1", "v1en"); //change the property value, so now the invariant (default) culture will be edited + ServiceContext.ContentService.Save(document); + + document = ServiceContext.ContentService.GetById(document.Id); + Assert.AreEqual("doc1en", document.Name); + Assert.AreEqual("doc1en", document.GetCultureName("en")); + Assert.AreEqual("doc1fr", document.GetCultureName("fr")); + Assert.AreEqual("v1en", document.GetValue("value1")); + Assert.AreEqual("v1en-init", document.GetValue("value1", published: true)); + Assert.IsTrue(document.IsCultureEdited("en")); //This is true because the invariant property reflects changes on the default lang + Assert.IsFalse(document.IsCultureEdited("fr")); + Assert.IsTrue(document.Edited); + + // switch property type to Culture + contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture; + ServiceContext.ContentTypeService.Save(contentType); //This is going to have to re-normalize the "Edited" flag + + document = ServiceContext.ContentService.GetById(document.Id); + Assert.IsTrue(document.IsCultureEdited("en")); //Remains true + Assert.IsFalse(document.IsCultureEdited("fr")); //False because no french property has ever been edited + Assert.IsTrue(document.Edited); + + //update the culture value and publish + document.SetValue("value1", "v1en2", "en"); + ServiceContext.ContentService.SaveAndPublish(document); + + document = ServiceContext.ContentService.GetById(document.Id); + Assert.AreEqual("doc1en", document.Name); + Assert.AreEqual("doc1en", document.GetCultureName("en")); + Assert.AreEqual("doc1fr", document.GetCultureName("fr")); + Assert.IsNull(document.GetValue("value1")); //The values are there but the business logic returns null + Assert.IsNull(document.GetValue("value1", published: true)); //The values are there but the business logic returns null + Assert.AreEqual("v1en2", document.GetValue("value1", "en")); + Assert.AreEqual("v1en2", document.GetValue("value1", "en", published: true)); + Assert.IsFalse(document.IsCultureEdited("en")); //This returns false, everything is published + Assert.IsFalse(document.IsCultureEdited("fr")); //False because no french property has ever been edited + Assert.IsFalse(document.Edited); + + // switch property back to Invariant + contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing; + ServiceContext.ContentTypeService.Save(contentType); + + document = ServiceContext.ContentService.GetById(document.Id); + Assert.AreEqual("v1en2", document.GetValue("value1")); //The variant property value gets copied over to the invariant + Assert.AreEqual("v1en2", document.GetValue("value1", published: true)); + Assert.IsNull(document.GetValue("value1", "fr")); //The values are there but the business logic returns null + Assert.IsNull(document.GetValue("value1", "fr", published: true)); //The values are there but the business logic returns null + Assert.IsFalse(document.IsCultureEdited("en")); //The variant published AND edited values are copied over to the invariant + Assert.IsFalse(document.IsCultureEdited("fr")); + Assert.IsFalse(document.Edited); + + } + [Test] public void Change_Variations_ComposedContentType_1() { From 9f6a7dec2e474854adc52151078ac4f8de770835 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 2 Aug 2019 00:28:48 +1000 Subject: [PATCH 226/776] Fixes how examine starts up to make it easier for custom index developers --- src/Umbraco.Web/Search/ExamineComponent.cs | 134 ++-------------- src/Umbraco.Web/Search/ExamineComposer.cs | 1 + .../Search/ExamineFinalComponent.cs | 151 ++++++++++++++++++ .../Search/ExamineFinalComposer.cs | 11 ++ .../Search/ExamineUserComponent.cs | 29 ++++ src/Umbraco.Web/Umbraco.Web.csproj | 3 + 6 files changed, 210 insertions(+), 119 deletions(-) create mode 100644 src/Umbraco.Web/Search/ExamineFinalComponent.cs create mode 100644 src/Umbraco.Web/Search/ExamineFinalComposer.cs create mode 100644 src/Umbraco.Web/Search/ExamineUserComponent.cs diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index 3583e5b7f9..b468963380 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Threading; using Examine; using Umbraco.Core; using Umbraco.Core.Cache; @@ -15,13 +14,12 @@ using Umbraco.Core.Sync; using Umbraco.Web.Cache; using Umbraco.Examine; using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Web.Scheduling; -using System.Threading.Tasks; using Examine.LuceneEngine.Directories; using Umbraco.Core.Composing; namespace Umbraco.Web.Search { + public sealed class ExamineComponent : IComponent { private readonly IExamineManager _examineManager; @@ -30,26 +28,31 @@ namespace Umbraco.Web.Search private readonly IValueSetBuilder _mediaValueSetBuilder; private readonly IValueSetBuilder _memberValueSetBuilder; private static bool _disableExamineIndexing = false; - private static volatile bool _isConfigured = false; - private static readonly object IsConfiguredLocker = new object(); private readonly IScopeProvider _scopeProvider; - private readonly ServiceContext _services; - private static BackgroundTaskRunner _rebuildOnStartupRunner; - private static readonly object RebuildLocker = new object(); + private readonly ServiceContext _services; private readonly IMainDom _mainDom; private readonly IProfilingLogger _logger; private readonly IUmbracoIndexesCreator _indexCreator; - private readonly IndexRebuilder _indexRebuilder; + // the default enlist priority is 100 // enlist with a lower priority to ensure that anything "default" runs after us // but greater that SafeXmlReaderWriter priority which is 60 private const int EnlistPriority = 80; + /// + /// Returns true if Examine is enabled for this AppDomain + /// + /// + /// This flag should be used by custom Examine index implementors to determine if they should + /// execute. This value is true if this component successfully registered with + /// + public static bool ExamineEnabled => !_disableExamineIndexing; + public ExamineComponent(IMainDom mainDom, IExamineManager examineManager, IProfilingLogger profilingLogger, IScopeProvider scopeProvider, IUmbracoIndexesCreator indexCreator, - IndexRebuilder indexRebuilder, ServiceContext services, + ServiceContext services, IContentValueSetBuilder contentValueSetBuilder, IPublishedContentValueSetBuilder publishedContentValueSetBuilder, IValueSetBuilder mediaValueSetBuilder, @@ -66,7 +69,6 @@ namespace Umbraco.Web.Search _mainDom = mainDom; _logger = profilingLogger; _indexCreator = indexCreator; - _indexRebuilder = indexRebuilder; } public void Initialize() @@ -119,11 +121,6 @@ namespace Umbraco.Web.Search ContentTypeCacheRefresher.CacheUpdated += ContentTypeCacheRefresherUpdated; ; MediaCacheRefresher.CacheUpdated += MediaCacheRefresherUpdated; MemberCacheRefresher.CacheUpdated += MemberCacheRefresherUpdated; - - EnsureUnlocked(_logger, _examineManager); - - // TODO: Instead of waiting 5000 ms, we could add an event handler on to fulfilling the first request, then start? - RebuildIndexes(_indexRebuilder, _logger, true, 5000); } public void Terminate() @@ -136,51 +133,7 @@ namespace Umbraco.Web.Search /// /// /// - public static void RebuildIndexes(IndexRebuilder indexRebuilder, ILogger logger, bool onlyEmptyIndexes, int waitMilliseconds = 0) - { - // TODO: need a way to disable rebuilding on startup - - lock(RebuildLocker) - { - if (_rebuildOnStartupRunner != null && _rebuildOnStartupRunner.IsRunning) - { - logger.Warn("Call was made to RebuildIndexes but the task runner for rebuilding is already running"); - return; - } - - logger.Info("Starting initialize async background thread."); - //do the rebuild on a managed background thread - var task = new RebuildOnStartupTask(indexRebuilder, logger, onlyEmptyIndexes, waitMilliseconds); - - _rebuildOnStartupRunner = new BackgroundTaskRunner( - "RebuildIndexesOnStartup", - logger); - - _rebuildOnStartupRunner.TryAdd(task); - } - } - - /// - /// Must be called to each index is unlocked before any indexing occurs - /// - /// - /// Indexing rebuilding can occur on a normal boot if the indexes are empty or on a cold boot by the database server messenger. Before - /// either of these happens, we need to configure the indexes. - /// - private static void EnsureUnlocked(ILogger logger, IExamineManager examineManager) - { - if (_disableExamineIndexing) return; - if (_isConfigured) return; - - lock (IsConfiguredLocker) - { - //double check - if (_isConfigured) return; - - _isConfigured = true; - examineManager.UnlockLuceneIndexes(logger); - } - } + public static void RebuildIndexes(IndexRebuilder indexRebuilder, ILogger logger, bool onlyEmptyIndexes, int waitMilliseconds = 0) => ExamineFinalComponent.RebuildIndexes(indexRebuilder, logger, onlyEmptyIndexes, waitMilliseconds); #region Cache refresher updated event handlers @@ -746,63 +699,6 @@ namespace Umbraco.Web.Search } #endregion - /// - /// Background task used to rebuild empty indexes on startup - /// - private class RebuildOnStartupTask : IBackgroundTask - { - private readonly IndexRebuilder _indexRebuilder; - private readonly ILogger _logger; - private readonly bool _onlyEmptyIndexes; - private readonly int _waitMilliseconds; - - public RebuildOnStartupTask(IndexRebuilder indexRebuilder, ILogger logger, bool onlyEmptyIndexes, int waitMilliseconds = 0) - { - _indexRebuilder = indexRebuilder ?? throw new ArgumentNullException(nameof(indexRebuilder)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _onlyEmptyIndexes = onlyEmptyIndexes; - _waitMilliseconds = waitMilliseconds; - } - - public bool IsAsync => false; - - public void Dispose() - { - } - - public void Run() - { - try - { - // rebuilds indexes - RebuildIndexes(); - } - catch (Exception ex) - { - _logger.Error(ex, "Failed to rebuild empty indexes."); - } - } - - public Task RunAsync(CancellationToken token) - { - throw new NotImplementedException(); - } - - /// - /// Used to rebuild indexes on startup or cold boot - /// - private void RebuildIndexes() - { - //do not attempt to do this if this has been disabled since we are not the main dom. - //this can be called during a cold boot - if (_disableExamineIndexing) return; - - if (_waitMilliseconds > 0) - Thread.Sleep(_waitMilliseconds); - - EnsureUnlocked(_logger, _indexRebuilder.ExamineManager); - _indexRebuilder.RebuildIndexes(_onlyEmptyIndexes); - } - } + } } diff --git a/src/Umbraco.Web/Search/ExamineComposer.cs b/src/Umbraco.Web/Search/ExamineComposer.cs index 6e74f0e89d..de33afe1cb 100644 --- a/src/Umbraco.Web/Search/ExamineComposer.cs +++ b/src/Umbraco.Web/Search/ExamineComposer.cs @@ -10,6 +10,7 @@ using Umbraco.Examine; namespace Umbraco.Web.Search { + /// /// Configures and installs Examine. /// diff --git a/src/Umbraco.Web/Search/ExamineFinalComponent.cs b/src/Umbraco.Web/Search/ExamineFinalComponent.cs new file mode 100644 index 0000000000..5a13185cdc --- /dev/null +++ b/src/Umbraco.Web/Search/ExamineFinalComponent.cs @@ -0,0 +1,151 @@ +using System; +using System.Threading; +using Examine; +using Umbraco.Core.Logging; +using Umbraco.Examine; +using Umbraco.Web.Scheduling; +using System.Threading.Tasks; +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Search +{ + /// + /// Executes after all other examine components have executed + /// + public sealed class ExamineFinalComponent : IComponent + { + private static volatile bool _isConfigured = false; + private static readonly object IsConfiguredLocker = new object(); + private readonly IProfilingLogger _logger; + private readonly IExamineManager _examineManager; + private static readonly object RebuildLocker = new object(); + private readonly IndexRebuilder _indexRebuilder; + private static BackgroundTaskRunner _rebuildOnStartupRunner; + + public void Initialize() + { + if (!ExamineComponent.ExamineEnabled) return; + + EnsureUnlocked(_logger, _examineManager); + + // TODO: Instead of waiting 5000 ms, we could add an event handler on to fulfilling the first request, then start? + ExamineComponent.RebuildIndexes(_indexRebuilder, _logger, true, 5000); + } + + public void Terminate() + { + } + + /// + /// Called to rebuild empty indexes on startup + /// + /// + /// + /// + /// + internal static void RebuildIndexes(IndexRebuilder indexRebuilder, ILogger logger, bool onlyEmptyIndexes, int waitMilliseconds = 0) + { + // TODO: need a way to disable rebuilding on startup + + lock (RebuildLocker) + { + if (_rebuildOnStartupRunner != null && _rebuildOnStartupRunner.IsRunning) + { + logger.Warn("Call was made to RebuildIndexes but the task runner for rebuilding is already running"); + return; + } + + logger.Info("Starting initialize async background thread."); + //do the rebuild on a managed background thread + var task = new RebuildOnStartupTask(indexRebuilder, logger, onlyEmptyIndexes, waitMilliseconds); + + _rebuildOnStartupRunner = new BackgroundTaskRunner( + "RebuildIndexesOnStartup", + logger); + + _rebuildOnStartupRunner.TryAdd(task); + } + } + + /// + /// Must be called to each index is unlocked before any indexing occurs + /// + /// + /// Indexing rebuilding can occur on a normal boot if the indexes are empty or on a cold boot by the database server messenger. Before + /// either of these happens, we need to configure the indexes. + /// + internal static void EnsureUnlocked(ILogger logger, IExamineManager examineManager) + { + if (!ExamineComponent.ExamineEnabled) return; + if (_isConfigured) return; + + lock (IsConfiguredLocker) + { + //double check + if (_isConfigured) return; + + _isConfigured = true; + examineManager.UnlockLuceneIndexes(logger); + } + } + + /// + /// Background task used to rebuild empty indexes on startup + /// + private class RebuildOnStartupTask : IBackgroundTask + { + private readonly IndexRebuilder _indexRebuilder; + private readonly ILogger _logger; + private readonly bool _onlyEmptyIndexes; + private readonly int _waitMilliseconds; + + public RebuildOnStartupTask(IndexRebuilder indexRebuilder, ILogger logger, bool onlyEmptyIndexes, int waitMilliseconds = 0) + { + _indexRebuilder = indexRebuilder ?? throw new ArgumentNullException(nameof(indexRebuilder)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _onlyEmptyIndexes = onlyEmptyIndexes; + _waitMilliseconds = waitMilliseconds; + } + + public bool IsAsync => false; + + public void Dispose() + { + } + + public void Run() + { + try + { + // rebuilds indexes + RebuildIndexes(); + } + catch (Exception ex) + { + _logger.Error(ex, "Failed to rebuild empty indexes."); + } + } + + public Task RunAsync(CancellationToken token) + { + throw new NotImplementedException(); + } + + /// + /// Used to rebuild indexes on startup or cold boot + /// + private void RebuildIndexes() + { + //do not attempt to do this if this has been disabled since we are not the main dom. + //this can be called during a cold boot + if (!ExamineComponent.ExamineEnabled) return; + + if (_waitMilliseconds > 0) + Thread.Sleep(_waitMilliseconds); + + EnsureUnlocked(_logger, _indexRebuilder.ExamineManager); + _indexRebuilder.RebuildIndexes(_onlyEmptyIndexes); + } + } + } +} diff --git a/src/Umbraco.Web/Search/ExamineFinalComposer.cs b/src/Umbraco.Web/Search/ExamineFinalComposer.cs new file mode 100644 index 0000000000..6b33459159 --- /dev/null +++ b/src/Umbraco.Web/Search/ExamineFinalComposer.cs @@ -0,0 +1,11 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Search +{ + // examine's final composer composes after all user composers + // and *also* after ICoreComposer (in case IUserComposer is disabled) + [ComposeAfter(typeof(IUserComposer))] + [ComposeAfter(typeof(ICoreComposer))] + public class ExamineFinalComposer : ComponentComposer + { } +} diff --git a/src/Umbraco.Web/Search/ExamineUserComponent.cs b/src/Umbraco.Web/Search/ExamineUserComponent.cs new file mode 100644 index 0000000000..ddee07543c --- /dev/null +++ b/src/Umbraco.Web/Search/ExamineUserComponent.cs @@ -0,0 +1,29 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Search +{ + /// + /// An abstract class for custom index authors to inherit from + /// + public abstract class ExamineUserComponent : IComponent + { + /// + /// Initialize the component, eagerly exits if ExamineComponent.ExamineEnabled == false + /// + public void Initialize() + { + if (!ExamineComponent.ExamineEnabled) return; + + InitializeComponent(); + } + + /// + /// Abstract method which executes to initialize this component if ExamineComponent.ExamineEnabled == true + /// + protected abstract void InitializeComponent(); + + public virtual void Terminate() + { + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index dcaa3494fd..c9c9a4412a 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -233,6 +233,9 @@ + + + From 366058581a8fc11697f92636a3961fa7d4cbb2d5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 2 Aug 2019 01:15:08 +1000 Subject: [PATCH 227/776] Adds LuceneIndexDiagnostics to make it easier for devs and custom indexes --- src/Umbraco.Examine/LuceneIndexDiagnostics.cs | 80 +++++++++++++++++++ src/Umbraco.Examine/Umbraco.Examine.csproj | 1 + .../UmbracoExamineIndexDiagnostics.cs | 62 ++------------ src/Umbraco.Web/Search/ExamineComponent.cs | 2 +- .../Search/GenericIndexDiagnostics.cs | 1 + src/Umbraco.Web/Suspendable.cs | 2 + 6 files changed, 91 insertions(+), 57 deletions(-) create mode 100644 src/Umbraco.Examine/LuceneIndexDiagnostics.cs diff --git a/src/Umbraco.Examine/LuceneIndexDiagnostics.cs b/src/Umbraco.Examine/LuceneIndexDiagnostics.cs new file mode 100644 index 0000000000..03e5018a79 --- /dev/null +++ b/src/Umbraco.Examine/LuceneIndexDiagnostics.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using Examine.LuceneEngine.Providers; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Lucene.Net.Store; +using Umbraco.Core.IO; + +namespace Umbraco.Examine +{ + public class LuceneIndexDiagnostics : IIndexDiagnostics + { + public LuceneIndexDiagnostics(LuceneIndex index, ILogger logger) + { + Index = index; + Logger = logger; + } + + public LuceneIndex Index { get; } + public ILogger Logger { get; } + + public int DocumentCount + { + get + { + try + { + return Index.GetIndexDocumentCount(); + } + catch (AlreadyClosedException) + { + Logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexDocumentCount, the writer is already closed"); + return 0; + } + } + } + + public int FieldCount + { + get + { + try + { + return Index.GetIndexFieldCount(); + } + catch (AlreadyClosedException) + { + Logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexFieldCount, the writer is already closed"); + return 0; + } + } + } + + public Attempt IsHealthy() + { + var isHealthy = Index.IsHealthy(out var indexError); + return isHealthy ? Attempt.Succeed() : Attempt.Fail(indexError.Message); + } + + public virtual IReadOnlyDictionary Metadata + { + get + { + var d = new Dictionary + { + [nameof(UmbracoExamineIndex.CommitCount)] = Index.CommitCount, + [nameof(UmbracoExamineIndex.DefaultAnalyzer)] = Index.DefaultAnalyzer.GetType().Name, + ["LuceneDirectory"] = Index.GetLuceneDirectory().GetType().Name, + [nameof(UmbracoExamineIndex.LuceneIndexFolder)] = + Index.LuceneIndexFolder == null + ? string.Empty + : Index.LuceneIndexFolder.ToString().ToLowerInvariant().TrimStart(IOHelper.MapPath(SystemDirectories.Root).ToLowerInvariant()).Replace("\\", "/").EnsureStartsWith('/'), + }; + + return d; + } + } + + + } +} diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index 95690c17e4..e28a8e674e 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -72,6 +72,7 @@ + diff --git a/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs b/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs index fed5b9bae7..8be46869ac 100644 --- a/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs +++ b/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs @@ -7,73 +7,23 @@ using Umbraco.Core.Logging; namespace Umbraco.Examine { - public class UmbracoExamineIndexDiagnostics : IIndexDiagnostics + public class UmbracoExamineIndexDiagnostics : LuceneIndexDiagnostics { private readonly UmbracoExamineIndex _index; - private readonly ILogger _logger; public UmbracoExamineIndexDiagnostics(UmbracoExamineIndex index, ILogger logger) + : base(index, logger) { - _index = index; - _logger = logger; } - public int DocumentCount + public override IReadOnlyDictionary Metadata { get { - try - { - return _index.GetIndexDocumentCount(); - } - catch (AlreadyClosedException) - { - _logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexDocumentCount, the writer is already closed"); - return 0; - } - } - } + var d = base.Metadata.ToDictionary(x => x.Key, x => x.Value); - public int FieldCount - { - get - { - try - { - return _index.GetIndexFieldCount(); - } - catch (AlreadyClosedException) - { - _logger.Warn(typeof(UmbracoContentIndex), "Cannot get GetIndexFieldCount, the writer is already closed"); - return 0; - } - } - } - - public Attempt IsHealthy() - { - var isHealthy = _index.IsHealthy(out var indexError); - return isHealthy ? Attempt.Succeed() : Attempt.Fail(indexError.Message); - } - - public virtual IReadOnlyDictionary Metadata - { - get - { - var d = new Dictionary - { - [nameof(UmbracoExamineIndex.CommitCount)] = _index.CommitCount, - [nameof(UmbracoExamineIndex.DefaultAnalyzer)] = _index.DefaultAnalyzer.GetType().Name, - ["LuceneDirectory"] = _index.GetLuceneDirectory().GetType().Name, - [nameof(UmbracoExamineIndex.EnableDefaultEventHandler)] = _index.EnableDefaultEventHandler, - [nameof(UmbracoExamineIndex.LuceneIndexFolder)] = - _index.LuceneIndexFolder == null - ? string.Empty - : _index.LuceneIndexFolder.ToString().ToLowerInvariant().TrimStart(IOHelper.MapPath(SystemDirectories.Root).ToLowerInvariant()).Replace("\\", "/").EnsureStartsWith('/'), - [nameof(UmbracoExamineIndex.PublishedValuesOnly)] = _index.PublishedValuesOnly, - //There's too much info here - //[nameof(UmbracoExamineIndexer.FieldDefinitionCollection)] = _index.FieldDefinitionCollection, - }; + d[nameof(UmbracoExamineIndex.EnableDefaultEventHandler)] = _index.EnableDefaultEventHandler; + d[nameof(UmbracoExamineIndex.PublishedValuesOnly)] = _index.PublishedValuesOnly; if (_index.ValueSetValidator is ValueSetValidator vsv) { diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index b468963380..5d33bfb169 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -118,7 +118,7 @@ namespace Umbraco.Web.Search // bind to distributed cache events - this ensures that this logic occurs on ALL servers // that are taking part in a load balanced environment. ContentCacheRefresher.CacheUpdated += ContentCacheRefresherUpdated; - ContentTypeCacheRefresher.CacheUpdated += ContentTypeCacheRefresherUpdated; ; + ContentTypeCacheRefresher.CacheUpdated += ContentTypeCacheRefresherUpdated; MediaCacheRefresher.CacheUpdated += MediaCacheRefresherUpdated; MemberCacheRefresher.CacheUpdated += MemberCacheRefresherUpdated; } diff --git a/src/Umbraco.Web/Search/GenericIndexDiagnostics.cs b/src/Umbraco.Web/Search/GenericIndexDiagnostics.cs index 560fb19f09..cb25e1242a 100644 --- a/src/Umbraco.Web/Search/GenericIndexDiagnostics.cs +++ b/src/Umbraco.Web/Search/GenericIndexDiagnostics.cs @@ -8,6 +8,7 @@ using Umbraco.Examine; namespace Umbraco.Web.Search { + /// /// Used to return diagnostic data for any index /// diff --git a/src/Umbraco.Web/Suspendable.cs b/src/Umbraco.Web/Suspendable.cs index 86c83120ea..cfe4d28e8b 100644 --- a/src/Umbraco.Web/Suspendable.cs +++ b/src/Umbraco.Web/Suspendable.cs @@ -50,6 +50,8 @@ namespace Umbraco.Web } } + //This is really needed at all since the only place this is used is in ExamineComponent and that already maintains a flag of whether it suspsended or not + // AHH... but Deploy probably uses this? public static class ExamineEvents { private static bool _tried, _suspended; From 14915e9da788570a1626a0fe8c1a2de7b29c49de Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 2 Aug 2019 01:16:37 +1000 Subject: [PATCH 228/776] oops --- src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs b/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs index 8be46869ac..4a926deebe 100644 --- a/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs +++ b/src/Umbraco.Examine/UmbracoExamineIndexDiagnostics.cs @@ -14,6 +14,7 @@ namespace Umbraco.Examine public UmbracoExamineIndexDiagnostics(UmbracoExamineIndex index, ILogger logger) : base(index, logger) { + _index = index; } public override IReadOnlyDictionary Metadata From 86a97c30d193d92c67c9fe917c17e2a783449d51 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 2 Aug 2019 12:40:45 +1000 Subject: [PATCH 229/776] fixes up LuceneIndexDiagnostics --- src/Umbraco.Examine/LuceneIndexDiagnostics.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Examine/LuceneIndexDiagnostics.cs b/src/Umbraco.Examine/LuceneIndexDiagnostics.cs index 03e5018a79..96363904b4 100644 --- a/src/Umbraco.Examine/LuceneIndexDiagnostics.cs +++ b/src/Umbraco.Examine/LuceneIndexDiagnostics.cs @@ -4,6 +4,7 @@ using Umbraco.Core; using Umbraco.Core.Logging; using Lucene.Net.Store; using Umbraco.Core.IO; +using System.Linq; namespace Umbraco.Examine { @@ -60,17 +61,19 @@ namespace Umbraco.Examine { get { + var luceneDir = Index.GetLuceneDirectory(); var d = new Dictionary { [nameof(UmbracoExamineIndex.CommitCount)] = Index.CommitCount, [nameof(UmbracoExamineIndex.DefaultAnalyzer)] = Index.DefaultAnalyzer.GetType().Name, - ["LuceneDirectory"] = Index.GetLuceneDirectory().GetType().Name, - [nameof(UmbracoExamineIndex.LuceneIndexFolder)] = - Index.LuceneIndexFolder == null - ? string.Empty - : Index.LuceneIndexFolder.ToString().ToLowerInvariant().TrimStart(IOHelper.MapPath(SystemDirectories.Root).ToLowerInvariant()).Replace("\\", "/").EnsureStartsWith('/'), + ["LuceneDirectory"] = luceneDir.GetType().Name }; + if (luceneDir is FSDirectory fsDir) + { + d[nameof(UmbracoExamineIndex.LuceneIndexFolder)] = fsDir.Directory.ToString().ToLowerInvariant().TrimStart(IOHelper.MapPath(SystemDirectories.Root).ToLowerInvariant()).Replace("\\", "/").EnsureStartsWith('/'); + } + return d; } } From 345e6091d2d09f3c83d9d26044e0ace9f22cd5d4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 2 Aug 2019 13:01:02 +1000 Subject: [PATCH 230/776] fixes null check, was on the wrong element --- src/Umbraco.Examine/ContentValueSetBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Examine/ContentValueSetBuilder.cs b/src/Umbraco.Examine/ContentValueSetBuilder.cs index 44cef08813..9cbc311639 100644 --- a/src/Umbraco.Examine/ContentValueSetBuilder.cs +++ b/src/Umbraco.Examine/ContentValueSetBuilder.cs @@ -54,7 +54,7 @@ namespace Umbraco.Examine {"updateDate", new object[] {c.UpdateDate}}, //Always add invariant updateDate {"nodeName", (PublishedValuesOnly //Always add invariant nodeName ? c.PublishName?.Yield() - : c?.Name.Yield()) ?? Enumerable.Empty()}, + : c.Name?.Yield()) ?? Enumerable.Empty()}, {"urlName", urlValue?.Yield() ?? Enumerable.Empty()}, //Always add invariant urlName {"path", c.Path?.Yield() ?? Enumerable.Empty()}, {"nodeType", c.ContentType.Id.ToString().Yield() ?? Enumerable.Empty()}, From 6d3faf91d8bc06f0378c816f92b2b2fc517ee168 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 5 Aug 2019 13:50:50 +1000 Subject: [PATCH 231/776] fixes spacing with ext login providers --- .../src/views/common/overlays/user/user.html | 16 ++++++++-------- .../views/components/application/umb-login.html | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html index 4af8c83983..60477ae9b8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/user/user.html @@ -53,14 +53,14 @@
- + + Link your {{login.caption}} account +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html index e7513617b7..bc27196466 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html @@ -138,7 +138,7 @@ id="{{login.authType}}" name="provider" value="{{login.authType}}" title="Log in using your {{login.caption}} account"> - Sign in with {{login.caption}} + Sign in with {{login.caption}} From 89429bd65a3377b8b86ffd284b44ac2483693a64 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 5 Aug 2019 16:28:23 +1000 Subject: [PATCH 232/776] fixes ctor --- src/Umbraco.Web/Search/ExamineFinalComponent.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Umbraco.Web/Search/ExamineFinalComponent.cs b/src/Umbraco.Web/Search/ExamineFinalComponent.cs index 5a13185cdc..4213efac34 100644 --- a/src/Umbraco.Web/Search/ExamineFinalComponent.cs +++ b/src/Umbraco.Web/Search/ExamineFinalComponent.cs @@ -14,6 +14,7 @@ namespace Umbraco.Web.Search ///
public sealed class ExamineFinalComponent : IComponent { + private static volatile bool _isConfigured = false; private static readonly object IsConfiguredLocker = new object(); private readonly IProfilingLogger _logger; @@ -22,6 +23,13 @@ namespace Umbraco.Web.Search private readonly IndexRebuilder _indexRebuilder; private static BackgroundTaskRunner _rebuildOnStartupRunner; + public ExamineFinalComponent(IProfilingLogger logger, IExamineManager examineManager, IndexRebuilder indexRebuilder) + { + _logger = logger; + _examineManager = examineManager; + _indexRebuilder = indexRebuilder; + } + public void Initialize() { if (!ExamineComponent.ExamineEnabled) return; From be4536cf164dd5a27c2046603319409b8a7b9eb3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 5 Aug 2019 17:14:29 +1000 Subject: [PATCH 233/776] Refactors ugly statics and nested classes --- src/Umbraco.Examine/ExamineExtensions.cs | 25 ++++ src/Umbraco.Examine/IndexRebuilder.cs | 3 +- .../Search/BackgroundIndexRebuilder.cs | 123 +++++++++++++++ src/Umbraco.Web/Search/ExamineComponent.cs | 28 +--- src/Umbraco.Web/Search/ExamineComposer.cs | 1 + .../Search/ExamineFinalComponent.cs | 141 ++---------------- .../Search/ExamineUserComponent.cs | 12 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 + 8 files changed, 180 insertions(+), 154 deletions(-) create mode 100644 src/Umbraco.Web/Search/BackgroundIndexRebuilder.cs diff --git a/src/Umbraco.Examine/ExamineExtensions.cs b/src/Umbraco.Examine/ExamineExtensions.cs index 376950c95e..ae6913f4ee 100644 --- a/src/Umbraco.Examine/ExamineExtensions.cs +++ b/src/Umbraco.Examine/ExamineExtensions.cs @@ -28,6 +28,31 @@ namespace Umbraco.Examine /// internal static readonly Regex CultureIsoCodeFieldNameMatchExpression = new Regex("^([_\\w]+)_([a-z]{2}-[a-z0-9]{2,4})$", RegexOptions.Compiled); + private static volatile bool _isUnlocked = false; + private static readonly object IsUnlockedLocker = new object(); + + /// + /// Unlocks all Lucene based indexes registered with the + /// + /// + /// Indexing rebuilding can occur on a normal boot if the indexes are empty or on a cold boot by the database server messenger. Before + /// either of these happens, we need to configure the indexes. + /// + internal static void EnsureUnlocked(this IExamineManager examineManager, IMainDom mainDom, ILogger logger) + { + if (!mainDom.IsMainDom) return; + if (_isUnlocked) return; + + lock (IsUnlockedLocker) + { + //double check + if (_isUnlocked) return; + + _isUnlocked = true; + examineManager.UnlockLuceneIndexes(logger); + } + } + //TODO: We need a public method here to just match a field name against CultureIsoCodeFieldNameMatchExpression /// diff --git a/src/Umbraco.Examine/IndexRebuilder.cs b/src/Umbraco.Examine/IndexRebuilder.cs index 43c309b9c5..786aecac71 100644 --- a/src/Umbraco.Examine/IndexRebuilder.cs +++ b/src/Umbraco.Examine/IndexRebuilder.cs @@ -5,7 +5,8 @@ using System.Threading.Tasks; using Examine; namespace Umbraco.Examine -{ +{ + /// /// Utility to rebuild all indexes ensuring minimal data queries /// diff --git a/src/Umbraco.Web/Search/BackgroundIndexRebuilder.cs b/src/Umbraco.Web/Search/BackgroundIndexRebuilder.cs new file mode 100644 index 0000000000..d747d8d9e4 --- /dev/null +++ b/src/Umbraco.Web/Search/BackgroundIndexRebuilder.cs @@ -0,0 +1,123 @@ +using System; +using System.Threading; +using Umbraco.Core.Logging; +using Umbraco.Examine; +using System.Threading.Tasks; +using Umbraco.Core; +using Umbraco.Web.Scheduling; + +namespace Umbraco.Web.Search +{ + /// + /// Utility to rebuild all indexes on a background thread + /// + public sealed class BackgroundIndexRebuilder + { + private static readonly object RebuildLocker = new object(); + private readonly IndexRebuilder _indexRebuilder; + private readonly IMainDom _mainDom; + private readonly IProfilingLogger _logger; + private static BackgroundTaskRunner _rebuildOnStartupRunner; + + public BackgroundIndexRebuilder(IMainDom mainDom, IProfilingLogger logger, IndexRebuilder indexRebuilder) + { + _mainDom = mainDom; + _logger = logger; + _indexRebuilder = indexRebuilder; + } + + /// + /// Called to rebuild empty indexes on startup + /// + /// + /// + /// + /// + public void RebuildIndexes(bool onlyEmptyIndexes, int waitMilliseconds = 0) + { + // TODO: need a way to disable rebuilding on startup + + lock (RebuildLocker) + { + if (_rebuildOnStartupRunner != null && _rebuildOnStartupRunner.IsRunning) + { + _logger.Warn("Call was made to RebuildIndexes but the task runner for rebuilding is already running"); + return; + } + + _logger.Info("Starting initialize async background thread."); + //do the rebuild on a managed background thread + var task = new RebuildOnStartupTask(_mainDom, _indexRebuilder, _logger, onlyEmptyIndexes, waitMilliseconds); + + _rebuildOnStartupRunner = new BackgroundTaskRunner( + "RebuildIndexesOnStartup", + _logger); + + _rebuildOnStartupRunner.TryAdd(task); + } + } + + /// + /// Background task used to rebuild empty indexes on startup + /// + private class RebuildOnStartupTask : IBackgroundTask + { + private readonly IMainDom _mainDom; + + private readonly IndexRebuilder _indexRebuilder; + private readonly ILogger _logger; + private readonly bool _onlyEmptyIndexes; + private readonly int _waitMilliseconds; + + public RebuildOnStartupTask(IMainDom mainDom, + IndexRebuilder indexRebuilder, ILogger logger, bool onlyEmptyIndexes, int waitMilliseconds = 0) + { + _mainDom = mainDom; + _indexRebuilder = indexRebuilder ?? throw new ArgumentNullException(nameof(indexRebuilder)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _onlyEmptyIndexes = onlyEmptyIndexes; + _waitMilliseconds = waitMilliseconds; + } + + public bool IsAsync => false; + + public void Dispose() + { + } + + public void Run() + { + try + { + // rebuilds indexes + RebuildIndexes(); + } + catch (Exception ex) + { + _logger.Error(ex, "Failed to rebuild empty indexes."); + } + } + + public Task RunAsync(CancellationToken token) + { + throw new NotImplementedException(); + } + + /// + /// Used to rebuild indexes on startup or cold boot + /// + private void RebuildIndexes() + { + //do not attempt to do this if this has been disabled since we are not the main dom. + //this can be called during a cold boot + if (!_mainDom.IsMainDom) return; + + if (_waitMilliseconds > 0) + Thread.Sleep(_waitMilliseconds); + + _indexRebuilder.ExamineManager.EnsureUnlocked(_mainDom, _logger); + _indexRebuilder.RebuildIndexes(_onlyEmptyIndexes); + } + } + } +} diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index 5d33bfb169..fd1eee8f96 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -16,18 +16,17 @@ using Umbraco.Examine; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Examine.LuceneEngine.Directories; using Umbraco.Core.Composing; +using System.ComponentModel; namespace Umbraco.Web.Search { - - public sealed class ExamineComponent : IComponent + public sealed class ExamineComponent : Umbraco.Core.Composing.IComponent { private readonly IExamineManager _examineManager; private readonly IContentValueSetBuilder _contentValueSetBuilder; private readonly IPublishedContentValueSetBuilder _publishedContentValueSetBuilder; private readonly IValueSetBuilder _mediaValueSetBuilder; private readonly IValueSetBuilder _memberValueSetBuilder; - private static bool _disableExamineIndexing = false; private readonly IScopeProvider _scopeProvider; private readonly ServiceContext _services; private readonly IMainDom _mainDom; @@ -39,16 +38,7 @@ namespace Umbraco.Web.Search // enlist with a lower priority to ensure that anything "default" runs after us // but greater that SafeXmlReaderWriter priority which is 60 private const int EnlistPriority = 80; - - /// - /// Returns true if Examine is enabled for this AppDomain - /// - /// - /// This flag should be used by custom Examine index implementors to determine if they should - /// execute. This value is true if this component successfully registered with - /// - public static bool ExamineEnabled => !_disableExamineIndexing; - + public ExamineComponent(IMainDom mainDom, IExamineManager examineManager, IProfilingLogger profilingLogger, IScopeProvider scopeProvider, IUmbracoIndexesCreator indexCreator, @@ -97,7 +87,6 @@ namespace Umbraco.Web.Search //if we could not register the shutdown examine ourselves, it means we are not maindom! in this case all of examine should be disabled! Suspendable.ExamineEvents.SuspendIndexers(_logger); - _disableExamineIndexing = true; return; //exit, do not continue } @@ -126,14 +115,9 @@ namespace Umbraco.Web.Search public void Terminate() { } - /// - /// Called to rebuild empty indexes on startup - /// - /// - /// - /// - /// - public static void RebuildIndexes(IndexRebuilder indexRebuilder, ILogger logger, bool onlyEmptyIndexes, int waitMilliseconds = 0) => ExamineFinalComponent.RebuildIndexes(indexRebuilder, logger, onlyEmptyIndexes, waitMilliseconds); + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("This method should not be used and will be removed in future versions, rebuilding indexes can be done with the IndexRebuilder or the BackgroundIndexRebuilder")] + public static void RebuildIndexes(IndexRebuilder indexRebuilder, ILogger logger, bool onlyEmptyIndexes, int waitMilliseconds = 0) => Current.Factory.GetInstance().RebuildIndexes(onlyEmptyIndexes, waitMilliseconds); #region Cache refresher updated event handlers diff --git a/src/Umbraco.Web/Search/ExamineComposer.cs b/src/Umbraco.Web/Search/ExamineComposer.cs index de33afe1cb..0ade432d70 100644 --- a/src/Umbraco.Web/Search/ExamineComposer.cs +++ b/src/Umbraco.Web/Search/ExamineComposer.cs @@ -44,6 +44,7 @@ namespace Umbraco.Web.Search false)); composition.RegisterUnique, MediaValueSetBuilder>(); composition.RegisterUnique, MemberValueSetBuilder>(); + composition.RegisterUnique(); //We want to manage Examine's AppDomain shutdown sequence ourselves so first we'll disable Examine's default behavior //and then we'll use MainDom to control Examine's shutdown - this MUST be done in Compose ie before ExamineManager diff --git a/src/Umbraco.Web/Search/ExamineFinalComponent.cs b/src/Umbraco.Web/Search/ExamineFinalComponent.cs index 4213efac34..4514a78faa 100644 --- a/src/Umbraco.Web/Search/ExamineFinalComponent.cs +++ b/src/Umbraco.Web/Search/ExamineFinalComponent.cs @@ -1,159 +1,42 @@ -using System; -using System.Threading; -using Examine; +using Examine; using Umbraco.Core.Logging; using Umbraco.Examine; -using Umbraco.Web.Scheduling; -using System.Threading.Tasks; using Umbraco.Core.Composing; +using Umbraco.Core; namespace Umbraco.Web.Search { + /// /// Executes after all other examine components have executed /// public sealed class ExamineFinalComponent : IComponent - { - - private static volatile bool _isConfigured = false; - private static readonly object IsConfiguredLocker = new object(); + { private readonly IProfilingLogger _logger; private readonly IExamineManager _examineManager; - private static readonly object RebuildLocker = new object(); - private readonly IndexRebuilder _indexRebuilder; - private static BackgroundTaskRunner _rebuildOnStartupRunner; - - public ExamineFinalComponent(IProfilingLogger logger, IExamineManager examineManager, IndexRebuilder indexRebuilder) + BackgroundIndexRebuilder _indexRebuilder; + private readonly IMainDom _mainDom; + + public ExamineFinalComponent(IProfilingLogger logger, IExamineManager examineManager, BackgroundIndexRebuilder indexRebuilder, IMainDom mainDom) { _logger = logger; _examineManager = examineManager; _indexRebuilder = indexRebuilder; + _mainDom = mainDom; } public void Initialize() { - if (!ExamineComponent.ExamineEnabled) return; + if (!_mainDom.IsMainDom) return; - EnsureUnlocked(_logger, _examineManager); + _examineManager.EnsureUnlocked(_mainDom, _logger); // TODO: Instead of waiting 5000 ms, we could add an event handler on to fulfilling the first request, then start? - ExamineComponent.RebuildIndexes(_indexRebuilder, _logger, true, 5000); + _indexRebuilder.RebuildIndexes(true, 5000); } public void Terminate() { } - - /// - /// Called to rebuild empty indexes on startup - /// - /// - /// - /// - /// - internal static void RebuildIndexes(IndexRebuilder indexRebuilder, ILogger logger, bool onlyEmptyIndexes, int waitMilliseconds = 0) - { - // TODO: need a way to disable rebuilding on startup - - lock (RebuildLocker) - { - if (_rebuildOnStartupRunner != null && _rebuildOnStartupRunner.IsRunning) - { - logger.Warn("Call was made to RebuildIndexes but the task runner for rebuilding is already running"); - return; - } - - logger.Info("Starting initialize async background thread."); - //do the rebuild on a managed background thread - var task = new RebuildOnStartupTask(indexRebuilder, logger, onlyEmptyIndexes, waitMilliseconds); - - _rebuildOnStartupRunner = new BackgroundTaskRunner( - "RebuildIndexesOnStartup", - logger); - - _rebuildOnStartupRunner.TryAdd(task); - } - } - - /// - /// Must be called to each index is unlocked before any indexing occurs - /// - /// - /// Indexing rebuilding can occur on a normal boot if the indexes are empty or on a cold boot by the database server messenger. Before - /// either of these happens, we need to configure the indexes. - /// - internal static void EnsureUnlocked(ILogger logger, IExamineManager examineManager) - { - if (!ExamineComponent.ExamineEnabled) return; - if (_isConfigured) return; - - lock (IsConfiguredLocker) - { - //double check - if (_isConfigured) return; - - _isConfigured = true; - examineManager.UnlockLuceneIndexes(logger); - } - } - - /// - /// Background task used to rebuild empty indexes on startup - /// - private class RebuildOnStartupTask : IBackgroundTask - { - private readonly IndexRebuilder _indexRebuilder; - private readonly ILogger _logger; - private readonly bool _onlyEmptyIndexes; - private readonly int _waitMilliseconds; - - public RebuildOnStartupTask(IndexRebuilder indexRebuilder, ILogger logger, bool onlyEmptyIndexes, int waitMilliseconds = 0) - { - _indexRebuilder = indexRebuilder ?? throw new ArgumentNullException(nameof(indexRebuilder)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _onlyEmptyIndexes = onlyEmptyIndexes; - _waitMilliseconds = waitMilliseconds; - } - - public bool IsAsync => false; - - public void Dispose() - { - } - - public void Run() - { - try - { - // rebuilds indexes - RebuildIndexes(); - } - catch (Exception ex) - { - _logger.Error(ex, "Failed to rebuild empty indexes."); - } - } - - public Task RunAsync(CancellationToken token) - { - throw new NotImplementedException(); - } - - /// - /// Used to rebuild indexes on startup or cold boot - /// - private void RebuildIndexes() - { - //do not attempt to do this if this has been disabled since we are not the main dom. - //this can be called during a cold boot - if (!ExamineComponent.ExamineEnabled) return; - - if (_waitMilliseconds > 0) - Thread.Sleep(_waitMilliseconds); - - EnsureUnlocked(_logger, _indexRebuilder.ExamineManager); - _indexRebuilder.RebuildIndexes(_onlyEmptyIndexes); - } - } } } diff --git a/src/Umbraco.Web/Search/ExamineUserComponent.cs b/src/Umbraco.Web/Search/ExamineUserComponent.cs index ddee07543c..35bc3f59ea 100644 --- a/src/Umbraco.Web/Search/ExamineUserComponent.cs +++ b/src/Umbraco.Web/Search/ExamineUserComponent.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Composing; +using Umbraco.Core; +using Umbraco.Core.Composing; namespace Umbraco.Web.Search { @@ -7,12 +8,19 @@ namespace Umbraco.Web.Search /// public abstract class ExamineUserComponent : IComponent { + private readonly IMainDom _mainDom; + + public ExamineUserComponent(IMainDom mainDom) + { + _mainDom = mainDom; + } + /// /// Initialize the component, eagerly exits if ExamineComponent.ExamineEnabled == false /// public void Initialize() { - if (!ExamineComponent.ExamineEnabled) return; + if (!_mainDom.IsMainDom) return; InitializeComponent(); } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c9c9a4412a..2365017504 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -233,6 +233,7 @@ + From e4f346620ee051a17357da7ece6ba65500620505 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 5 Aug 2019 10:38:35 +0200 Subject: [PATCH 234/776] Added runtime level to ExamineFinalComposer --- src/Umbraco.Web/Search/ExamineFinalComposer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Search/ExamineFinalComposer.cs b/src/Umbraco.Web/Search/ExamineFinalComposer.cs index 6b33459159..5b6334f1f6 100644 --- a/src/Umbraco.Web/Search/ExamineFinalComposer.cs +++ b/src/Umbraco.Web/Search/ExamineFinalComposer.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Composing; +using Umbraco.Core; +using Umbraco.Core.Composing; namespace Umbraco.Web.Search { @@ -6,6 +7,7 @@ namespace Umbraco.Web.Search // and *also* after ICoreComposer (in case IUserComposer is disabled) [ComposeAfter(typeof(IUserComposer))] [ComposeAfter(typeof(ICoreComposer))] + [RuntimeLevel(MinLevel = RuntimeLevel.Run)] public class ExamineFinalComposer : ComponentComposer { } } From 467cf8d76235fb421876db66086a33fd7c3cf4e6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 7 Aug 2019 15:40:27 +1000 Subject: [PATCH 235/776] Adds comments/notes --- src/Umbraco.Core/Composing/Lifetime.cs | 67 +++++++++++++++++++------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Core/Composing/Lifetime.cs b/src/Umbraco.Core/Composing/Lifetime.cs index 1a2cc3119a..012555be5e 100644 --- a/src/Umbraco.Core/Composing/Lifetime.cs +++ b/src/Umbraco.Core/Composing/Lifetime.cs @@ -12,31 +12,62 @@ /// or MS.DI, PerDependency in Autofac. Transient, + // TODO: We need to fix this up, currently LightInject is the only one that behaves differently from all other containers. + // ... the simple fix would be to map this to PerScopeLifetime in LI but need to wait on a response here https://github.com/seesharper/LightInject/issues/494#issuecomment-518942625 + // + // we use it for controllers, httpContextBase and other request scoped objects: MembershpHelper, TagQuery, UmbracoTreeSearcher and ISearchableTree + // - so that they are automatically disposed at the end of the scope (ie request) + // - not sure they should not be simply 'scoped'? + /// /// One unique instance per request. /// - // TODO: review lifetimes for LightInject vs other containers - // currently, corresponds to 'Request' in LightInject which is 'Transient + disposed by Scope' - // but NOT (in LightInject) a per-web-request lifetime, more a TransientScoped - // - // we use it for controllers, httpContextBase and umbracoContext - // - so that they are automatically disposed at the end of the scope (ie request) - // - not sure they should not be simply 'scoped'? - // - // Castle has an extra PerWebRequest something, and others use scope - // what about Request before first request ie during application startup? - // see http://blog.ploeh.dk/2009/11/17/UsingCastleWindsor'sPerWebRequestlifestylewithASP.NETMVConIIS7/ - // Castle ends up requiring a special scope manager too - // see https://groups.google.com/forum/#!topic/castle-project-users/1E2W9LVIYR4 - // - // but maybe also - why are we requiring scoped services at startup? + /// + /// + /// Any instance created with this lifetime will be disposed at the end of a request. + /// + /// Corresponds to + /// + /// PerRequestLifeTime in LightInject - means transient but disposed at the end of the current web request. + /// see: https://github.com/seesharper/LightInject/issues/494#issuecomment-518493262 + /// + /// + /// Scoped in MS.DI - means one per web request. + /// see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#service-lifetimes + /// + /// InstancePerRequest in Autofac - means one per web request. + /// see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-request + /// But "Behind the scenes, though, it’s still just instance per matching lifetime scope." + /// + /// + /// LifestylePerWebRequest in Castle Windsor - means one per web request. + /// see https://github.com/castleproject/Windsor/blob/master/docs/mvc-tutorial-part-7-lifestyles.md#the-perwebrequest-lifestyle + /// + /// Request, /// - /// One unique instance per container scope. + /// One unique instance per scope. /// - /// Corresponds to Scope in LightInject, Scoped in MS.DI - /// or Castle Windsor, PerLifetimeScope in Autofac. + /// + /// + /// Any instance created with this lifetime will be disposed at the end of the current scope. + /// + /// Corresponds to + /// PerScopeLifetime in LightInject (when in a request, means one per web request) + /// + /// Scoped in MS.DI (when in a request, means one per web request) + /// see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#service-lifetimes + /// + /// InstancePerLifetimeScope in Autofac (when in a request, means one per web request) + /// see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-lifetime-scope + /// Also note that Autofac's InstancePerRequest is the same as this, see https://autofaccn.readthedocs.io/en/latest/lifetime/instance-scope.html#instance-per-request + /// it says "Behind the scenes, though, it’s still just instance per matching lifetime scope." + /// + /// + /// LifestyleScoped in Castle Windsor + /// + /// Scope, /// From 0517f2913438638ace044e063facc74d12fb670f Mon Sep 17 00:00:00 2001 From: Linus Elander Date: Wed, 19 Jun 2019 21:41:47 +0200 Subject: [PATCH 236/776] Adjusts conditions for which indexes should be affected when a node is unpublished (cherry picked from commit 7557c584d8331482ffb7157dcf2c4d083c5e226d) --- src/Umbraco.Web/Search/ExamineComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index fd1eee8f96..d2eaa7aa0c 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -674,7 +674,7 @@ namespace Umbraco.Web.Search { var strId = id.ToString(CultureInfo.InvariantCulture); foreach (var index in examineComponent._examineManager.Indexes.OfType() - .Where(x => (keepIfUnpublished && !x.PublishedValuesOnly) || !keepIfUnpublished) + .Where(x => x.PublishedValuesOnly || !keepIfUnpublished) .Where(x => x.EnableDefaultEventHandler)) { index.DeleteFromIndex(strId); From e495faf83c2e927578092f64560d0f6179474bae Mon Sep 17 00:00:00 2001 From: Benjamin Carleski Date: Wed, 7 Aug 2019 10:31:08 -0700 Subject: [PATCH 237/776] Fix FIPS compliance, add FIPS tests (#5978) --- .../ControllerTesting/TestRunner.cs | 10 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../AuthenticationControllerTests.cs | 139 ++++++++++++++++++ .../Web/Controllers/UsersControllerTests.cs | 97 +++++++++++- .../Models/Mapping/UserMapDefinition.cs | 2 +- 5 files changed, 243 insertions(+), 6 deletions(-) create mode 100644 src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs index 8c598281dd..9f9f933d72 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs @@ -25,19 +25,23 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting public async Task> Execute(string controllerName, string actionName, HttpMethod method, HttpContent content = null, MediaTypeWithQualityHeaderValue mediaTypeHeader = null, - bool assertOkResponse = true) + bool assertOkResponse = true, object routeDefaults = null, string url = null) { if (mediaTypeHeader == null) { mediaTypeHeader = new MediaTypeWithQualityHeaderValue("application/json"); } + if (routeDefaults == null) + { + routeDefaults = new { controller = controllerName, action = actionName, id = RouteParameter.Optional }; + } var startup = new TestStartup( configuration => { configuration.Routes.MapHttpRoute("Default", routeTemplate: "{controller}/{action}/{id}", - defaults: new { controller = controllerName, action = actionName, id = RouteParameter.Optional }); + defaults: routeDefaults); }, _controllerFactory); @@ -45,7 +49,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting { var request = new HttpRequestMessage { - RequestUri = new Uri("https://testserver/"), + RequestUri = new Uri("https://testserver/" + (url ?? "")), Method = method }; diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 717006b702..f788168ddc 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -243,6 +243,7 @@ + diff --git a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs new file mode 100644 index 0000000000..3d264663b5 --- /dev/null +++ b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Reflection; +using System.Security.Cryptography; +using System.Threading; +using System.Web; +using System.Web.Hosting; +using System.Web.Http; +using Moq; +using Newtonsoft.Json; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.ControllerTesting; +using Umbraco.Tests.Testing; +using Umbraco.Web; +using Umbraco.Web.Editors; +using Umbraco.Web.Features; +using Umbraco.Web.Models.ContentEditing; +using IUser = Umbraco.Core.Models.Membership.IUser; + +namespace Umbraco.Tests.Web.Controllers +{ + [TestFixture] + [UmbracoTest(Database = UmbracoTestOptions.Database.None)] + public class AuthenticationControllerTests : TestWithDatabaseBase + { + protected override void ComposeApplication(bool withApplication) + { + base.ComposeApplication(withApplication); + //if (!withApplication) return; + + // replace the true IUserService implementation with a mock + // so that each test can configure the service to their liking + Composition.RegisterUnique(f => Mock.Of()); + + // kill the true IEntityService too + Composition.RegisterUnique(f => Mock.Of()); + + Composition.RegisterUnique(); + } + + + [Test] + public async System.Threading.Tasks.Task GetCurrentUser_Fips() + { + ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper) + { + //setup some mocks + var userServiceMock = Mock.Get(Current.Services.UserService); + userServiceMock.Setup(service => service.GetUserById(It.IsAny())) + .Returns(() => null); + + if (Thread.GetDomain().GetData(".appPath") != null) + { + HttpContext.Current = new HttpContext(new SimpleWorkerRequest("", "", new StringWriter())); + } + else + { + var baseDir = IOHelper.MapPath("", false).TrimEnd(IOHelper.DirSepChar); + HttpContext.Current = new HttpContext(new SimpleWorkerRequest("/", baseDir, "", "", new StringWriter())); + } + IOHelper.ForceNotHosted = true; + var usersController = new AuthenticationController( + Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + helper); + return usersController; + } + + Mock.Get(Current.SqlContext) + .Setup(x => x.Query()) + .Returns(new Query(Current.SqlContext)); + + var syntax = new SqlCeSyntaxProvider(); + + Mock.Get(Current.SqlContext) + .Setup(x => x.SqlSyntax) + .Returns(syntax); + + var mappers = new MapperCollection(new[] + { + new UserMapper(new Lazy(() => Current.SqlContext), new ConcurrentDictionary>()) + }); + + Mock.Get(Current.SqlContext) + .Setup(x => x.Mappers) + .Returns(mappers); + + // Testing what happens if the system were configured to only use FIPS-compliant algorithms + var typ = typeof(CryptoConfig); + var flds = typ.GetFields(BindingFlags.Static | BindingFlags.NonPublic); + var haveFld = flds.FirstOrDefault(f => f.Name == "s_haveFipsAlgorithmPolicy"); + var isFld = flds.FirstOrDefault(f => f.Name == "s_fipsAlgorithmPolicy"); + var originalFipsValue = CryptoConfig.AllowOnlyFipsAlgorithms; + + try + { + if (!originalFipsValue) + { + haveFld.SetValue(null, true); + isFld.SetValue(null, true); + } + + var runner = new TestRunner(CtrlFactory); + var response = await runner.Execute("Authentication", "GetCurrentUser", HttpMethod.Get); + + var obj = JsonConvert.DeserializeObject(response.Item2); + Assert.AreEqual(-1, obj.UserId); + } + finally + { + if (!originalFipsValue) + { + haveFld.SetValue(null, false); + isFld.SetValue(null, false); + } + } + } + } +} diff --git a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs index a4c3078b8f..85dd303432 100644 --- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Net.Http.Formatting; +using System.Reflection; +using System.Security.Cryptography; using System.Web.Http; using Moq; using Newtonsoft.Json; @@ -155,7 +157,7 @@ namespace Umbraco.Tests.Web.Controllers var runner = new TestRunner(CtrlFactory); var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get); - var obj = JsonConvert.DeserializeObject>(response.Item2); + var obj = JsonConvert.DeserializeObject>(response.Item2); Assert.AreEqual(0, obj.TotalItems); } @@ -190,9 +192,100 @@ namespace Umbraco.Tests.Web.Controllers var runner = new TestRunner(CtrlFactory); var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get); - var obj = JsonConvert.DeserializeObject>(response.Item2); + var obj = JsonConvert.DeserializeObject>(response.Item2); Assert.AreEqual(10, obj.TotalItems); Assert.AreEqual(10, obj.Items.Count()); } + + [Test] + public async System.Threading.Tasks.Task GetPagedUsers_Fips() + { + await RunFipsTest("GetPagedUsers", mock => + { + var users = MockedUser.CreateMulipleUsers(10); + long outVal = 10; + mock.Setup(service => service.GetAll( + It.IsAny(), It.IsAny(), out outVal, It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + .Returns(() => users); + }, response => + { + var obj = JsonConvert.DeserializeObject>(response.Item2); + Assert.AreEqual(10, obj.TotalItems); + Assert.AreEqual(10, obj.Items.Count()); + }); + } + + [Test] + public async System.Threading.Tasks.Task GetById_Fips() + { + const int mockUserId = 1234; + var user = MockedUser.CreateUser(); + + await RunFipsTest("GetById", mock => + { + mock.Setup(service => service.GetUserById(1234)) + .Returns((int i) => i == mockUserId ? user : null); + }, response => + { + var obj = JsonConvert.DeserializeObject(response.Item2); + Assert.AreEqual(user.Username, obj.Username); + Assert.AreEqual(user.Email, obj.Email); + }, new { controller = "Users", action = "GetById" }, $"Users/GetById/{mockUserId}"); + } + + + private async System.Threading.Tasks.Task RunFipsTest(string action, Action> userServiceSetup, + Action> verification, + object routeDefaults = null, string url = null) + { + ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper) + { + //setup some mocks + var userServiceMock = Mock.Get(Current.Services.UserService); + userServiceSetup(userServiceMock); + + var usersController = new UsersController( + Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + helper); + return usersController; + } + + // Testing what happens if the system were configured to only use FIPS-compliant algorithms + var typ = typeof(CryptoConfig); + var flds = typ.GetFields(BindingFlags.Static | BindingFlags.NonPublic); + var haveFld = flds.FirstOrDefault(f => f.Name == "s_haveFipsAlgorithmPolicy"); + var isFld = flds.FirstOrDefault(f => f.Name == "s_fipsAlgorithmPolicy"); + var originalFipsValue = CryptoConfig.AllowOnlyFipsAlgorithms; + + try + { + if (!originalFipsValue) + { + haveFld.SetValue(null, true); + isFld.SetValue(null, true); + } + + MockForGetPagedUsers(); + + var runner = new TestRunner(CtrlFactory); + var response = await runner.Execute("Users", action, HttpMethod.Get, routeDefaults: routeDefaults, url: url); + verification(response); + } + finally + { + if (!originalFipsValue) + { + haveFld.SetValue(null, false); + isFld.SetValue(null, false); + } + } + } } } diff --git a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs index 3860d5d525..4acda1e552 100644 --- a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs @@ -301,7 +301,7 @@ namespace Umbraco.Web.Models.Mapping target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache); target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString(); target.Email = source.Email; - target.EmailHash = source.Email.ToLowerInvariant().Trim().ToMd5(); + target.EmailHash = source.Email.ToLowerInvariant().Trim().GenerateHash(); target.Id = source.Id; target.Key = source.Key; target.LastLoginDate = source.LastLoginDate == default ? null : (DateTime?) source.LastLoginDate; From 289d50d170e90e7c2ba7193bae12e08790e86f3f Mon Sep 17 00:00:00 2001 From: Benjamin Carleski Date: Wed, 7 Aug 2019 10:31:08 -0700 Subject: [PATCH 238/776] Fix FIPS compliance, add FIPS tests (#5978) (cherry picked from commit e495faf83c2e927578092f64560d0f6179474bae) --- .../ControllerTesting/TestRunner.cs | 10 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../AuthenticationControllerTests.cs | 139 ++++++++++++++++++ .../Web/Controllers/UsersControllerTests.cs | 97 +++++++++++- .../Models/Mapping/UserMapDefinition.cs | 2 +- 5 files changed, 243 insertions(+), 6 deletions(-) create mode 100644 src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs index 8c598281dd..9f9f933d72 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs @@ -25,19 +25,23 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting public async Task> Execute(string controllerName, string actionName, HttpMethod method, HttpContent content = null, MediaTypeWithQualityHeaderValue mediaTypeHeader = null, - bool assertOkResponse = true) + bool assertOkResponse = true, object routeDefaults = null, string url = null) { if (mediaTypeHeader == null) { mediaTypeHeader = new MediaTypeWithQualityHeaderValue("application/json"); } + if (routeDefaults == null) + { + routeDefaults = new { controller = controllerName, action = actionName, id = RouteParameter.Optional }; + } var startup = new TestStartup( configuration => { configuration.Routes.MapHttpRoute("Default", routeTemplate: "{controller}/{action}/{id}", - defaults: new { controller = controllerName, action = actionName, id = RouteParameter.Optional }); + defaults: routeDefaults); }, _controllerFactory); @@ -45,7 +49,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting { var request = new HttpRequestMessage { - RequestUri = new Uri("https://testserver/"), + RequestUri = new Uri("https://testserver/" + (url ?? "")), Method = method }; diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 717006b702..f788168ddc 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -243,6 +243,7 @@ + diff --git a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs new file mode 100644 index 0000000000..3d264663b5 --- /dev/null +++ b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Reflection; +using System.Security.Cryptography; +using System.Threading; +using System.Web; +using System.Web.Hosting; +using System.Web.Http; +using Moq; +using Newtonsoft.Json; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.ControllerTesting; +using Umbraco.Tests.Testing; +using Umbraco.Web; +using Umbraco.Web.Editors; +using Umbraco.Web.Features; +using Umbraco.Web.Models.ContentEditing; +using IUser = Umbraco.Core.Models.Membership.IUser; + +namespace Umbraco.Tests.Web.Controllers +{ + [TestFixture] + [UmbracoTest(Database = UmbracoTestOptions.Database.None)] + public class AuthenticationControllerTests : TestWithDatabaseBase + { + protected override void ComposeApplication(bool withApplication) + { + base.ComposeApplication(withApplication); + //if (!withApplication) return; + + // replace the true IUserService implementation with a mock + // so that each test can configure the service to their liking + Composition.RegisterUnique(f => Mock.Of()); + + // kill the true IEntityService too + Composition.RegisterUnique(f => Mock.Of()); + + Composition.RegisterUnique(); + } + + + [Test] + public async System.Threading.Tasks.Task GetCurrentUser_Fips() + { + ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper) + { + //setup some mocks + var userServiceMock = Mock.Get(Current.Services.UserService); + userServiceMock.Setup(service => service.GetUserById(It.IsAny())) + .Returns(() => null); + + if (Thread.GetDomain().GetData(".appPath") != null) + { + HttpContext.Current = new HttpContext(new SimpleWorkerRequest("", "", new StringWriter())); + } + else + { + var baseDir = IOHelper.MapPath("", false).TrimEnd(IOHelper.DirSepChar); + HttpContext.Current = new HttpContext(new SimpleWorkerRequest("/", baseDir, "", "", new StringWriter())); + } + IOHelper.ForceNotHosted = true; + var usersController = new AuthenticationController( + Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + helper); + return usersController; + } + + Mock.Get(Current.SqlContext) + .Setup(x => x.Query()) + .Returns(new Query(Current.SqlContext)); + + var syntax = new SqlCeSyntaxProvider(); + + Mock.Get(Current.SqlContext) + .Setup(x => x.SqlSyntax) + .Returns(syntax); + + var mappers = new MapperCollection(new[] + { + new UserMapper(new Lazy(() => Current.SqlContext), new ConcurrentDictionary>()) + }); + + Mock.Get(Current.SqlContext) + .Setup(x => x.Mappers) + .Returns(mappers); + + // Testing what happens if the system were configured to only use FIPS-compliant algorithms + var typ = typeof(CryptoConfig); + var flds = typ.GetFields(BindingFlags.Static | BindingFlags.NonPublic); + var haveFld = flds.FirstOrDefault(f => f.Name == "s_haveFipsAlgorithmPolicy"); + var isFld = flds.FirstOrDefault(f => f.Name == "s_fipsAlgorithmPolicy"); + var originalFipsValue = CryptoConfig.AllowOnlyFipsAlgorithms; + + try + { + if (!originalFipsValue) + { + haveFld.SetValue(null, true); + isFld.SetValue(null, true); + } + + var runner = new TestRunner(CtrlFactory); + var response = await runner.Execute("Authentication", "GetCurrentUser", HttpMethod.Get); + + var obj = JsonConvert.DeserializeObject(response.Item2); + Assert.AreEqual(-1, obj.UserId); + } + finally + { + if (!originalFipsValue) + { + haveFld.SetValue(null, false); + isFld.SetValue(null, false); + } + } + } + } +} diff --git a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs index a4c3078b8f..85dd303432 100644 --- a/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/UsersControllerTests.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Net.Http.Formatting; +using System.Reflection; +using System.Security.Cryptography; using System.Web.Http; using Moq; using Newtonsoft.Json; @@ -155,7 +157,7 @@ namespace Umbraco.Tests.Web.Controllers var runner = new TestRunner(CtrlFactory); var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get); - var obj = JsonConvert.DeserializeObject>(response.Item2); + var obj = JsonConvert.DeserializeObject>(response.Item2); Assert.AreEqual(0, obj.TotalItems); } @@ -190,9 +192,100 @@ namespace Umbraco.Tests.Web.Controllers var runner = new TestRunner(CtrlFactory); var response = await runner.Execute("Users", "GetPagedUsers", HttpMethod.Get); - var obj = JsonConvert.DeserializeObject>(response.Item2); + var obj = JsonConvert.DeserializeObject>(response.Item2); Assert.AreEqual(10, obj.TotalItems); Assert.AreEqual(10, obj.Items.Count()); } + + [Test] + public async System.Threading.Tasks.Task GetPagedUsers_Fips() + { + await RunFipsTest("GetPagedUsers", mock => + { + var users = MockedUser.CreateMulipleUsers(10); + long outVal = 10; + mock.Setup(service => service.GetAll( + It.IsAny(), It.IsAny(), out outVal, It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) + .Returns(() => users); + }, response => + { + var obj = JsonConvert.DeserializeObject>(response.Item2); + Assert.AreEqual(10, obj.TotalItems); + Assert.AreEqual(10, obj.Items.Count()); + }); + } + + [Test] + public async System.Threading.Tasks.Task GetById_Fips() + { + const int mockUserId = 1234; + var user = MockedUser.CreateUser(); + + await RunFipsTest("GetById", mock => + { + mock.Setup(service => service.GetUserById(1234)) + .Returns((int i) => i == mockUserId ? user : null); + }, response => + { + var obj = JsonConvert.DeserializeObject(response.Item2); + Assert.AreEqual(user.Username, obj.Username); + Assert.AreEqual(user.Email, obj.Email); + }, new { controller = "Users", action = "GetById" }, $"Users/GetById/{mockUserId}"); + } + + + private async System.Threading.Tasks.Task RunFipsTest(string action, Action> userServiceSetup, + Action> verification, + object routeDefaults = null, string url = null) + { + ApiController CtrlFactory(HttpRequestMessage message, IUmbracoContextAccessor umbracoContextAccessor, UmbracoHelper helper) + { + //setup some mocks + var userServiceMock = Mock.Get(Current.Services.UserService); + userServiceSetup(userServiceMock); + + var usersController = new UsersController( + Factory.GetInstance(), + umbracoContextAccessor, + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + Factory.GetInstance(), + helper); + return usersController; + } + + // Testing what happens if the system were configured to only use FIPS-compliant algorithms + var typ = typeof(CryptoConfig); + var flds = typ.GetFields(BindingFlags.Static | BindingFlags.NonPublic); + var haveFld = flds.FirstOrDefault(f => f.Name == "s_haveFipsAlgorithmPolicy"); + var isFld = flds.FirstOrDefault(f => f.Name == "s_fipsAlgorithmPolicy"); + var originalFipsValue = CryptoConfig.AllowOnlyFipsAlgorithms; + + try + { + if (!originalFipsValue) + { + haveFld.SetValue(null, true); + isFld.SetValue(null, true); + } + + MockForGetPagedUsers(); + + var runner = new TestRunner(CtrlFactory); + var response = await runner.Execute("Users", action, HttpMethod.Get, routeDefaults: routeDefaults, url: url); + verification(response); + } + finally + { + if (!originalFipsValue) + { + haveFld.SetValue(null, false); + isFld.SetValue(null, false); + } + } + } } } diff --git a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs index 3860d5d525..4acda1e552 100644 --- a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs @@ -301,7 +301,7 @@ namespace Umbraco.Web.Models.Mapping target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache); target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString(); target.Email = source.Email; - target.EmailHash = source.Email.ToLowerInvariant().Trim().ToMd5(); + target.EmailHash = source.Email.ToLowerInvariant().Trim().GenerateHash(); target.Id = source.Id; target.Key = source.Key; target.LastLoginDate = source.LastLoginDate == default ? null : (DateTime?) source.LastLoginDate; From 6c0fb2f849fa9287c4bc1ff218a6aeb9172e8a62 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 7 Aug 2019 19:56:30 +0200 Subject: [PATCH 239/776] Fix the create context menu overflows introduced with 405e9a79 (#5754) --- .../src/views/contentblueprints/create.html | 4 ++-- .../src/views/datatypes/create.html | 8 ++++---- .../src/views/documenttypes/create.html | 16 +++++++-------- .../src/views/media/create.html | 4 ++-- .../src/views/mediatypes/create.html | 8 ++++---- .../src/views/member/create.html | 4 ++-- .../src/views/membertypes/create.html | 8 ++++---- .../src/views/partialviewmacros/create.html | 20 +++++++++---------- .../src/views/partialviews/create.html | 16 +++++++-------- .../src/views/scripts/create.html | 8 ++++---- .../src/views/stylesheets/create.html | 12 +++++------ 11 files changed, 54 insertions(+), 54 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/contentblueprints/create.html b/src/Umbraco.Web.UI.Client/src/views/contentblueprints/create.html index 24cfe5d0d1..6146c007b1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/contentblueprints/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/contentblueprints/create.html @@ -11,7 +11,7 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/create.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/create.html index c359e7bfd1..55039831ac 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/create.html @@ -6,20 +6,20 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html index 4085e84ccd..2241f852b5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html @@ -7,36 +7,36 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/media/create.html b/src/Umbraco.Web.UI.Client/src/views/media/create.html index d93d4f0e30..57c8f48eb4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/create.html @@ -17,13 +17,13 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/media/create.html b/src/Umbraco.Web.UI.Client/src/views/media/create.html index d93d4f0e30..57c8f48eb4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/create.html @@ -17,13 +17,13 @@ /// The text. - /// The text with text line breaks replaced with HTML line breaks (
)
+ /// The text with text line breaks replaced with HTML line breaks (<br />). + [Obsolete("This method doesn't HTML encode the text. Use ReplaceLineBreaks instead.")] public HtmlString ReplaceLineBreaksForHtml(string text) { - return new HtmlString(text.Replace("\r\n", @"
").Replace("\n", @"
").Replace("\r", @"
")); + return new HtmlString(text.Replace("\r\n", @"
").Replace("\n", @"
").Replace("\r", @"
")); + } + + /// + /// HTML encodes the text and replaces text line breaks with HTML line breaks. + /// + /// The text. + /// The HTML encoded text with text line breaks replaced with HTML line breaks (<br />). + public IHtmlString ReplaceLineBreaks(string text) + { + var value = HttpUtility.HtmlEncode(text)? + .Replace("\r\n", "
") + .Replace("\r", "
") + .Replace("\n", "
"); + + return new HtmlString(value); } public HtmlString StripHtmlTags(string html, params string[] tags) From 24efa54481f98078740e20a5e84f70f587df2ab6 Mon Sep 17 00:00:00 2001 From: Kasper Christensen Date: Mon, 2 Sep 2019 10:00:44 +0200 Subject: [PATCH 455/776] =?UTF-8?q?Loginpage:=20Added=20an=20id=20to=20inp?= =?UTF-8?q?ut=20fields=20and=20a=20for=20attr=20on=20the=20la=E2=80=A6=20(?= =?UTF-8?q?#6247)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/application/umb-login.html | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html index bc27196466..676b2efc38 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html @@ -18,11 +18,11 @@

-
- - + + Required The confirmed password doesn't match the new password! @@ -152,13 +152,13 @@
- - + +
- - + +
Show password @@ -189,8 +189,8 @@
- - + +
@@ -220,13 +220,13 @@
- - + +
- - + +
From da0ace18e69879471a55472e391aa7b98c96cb4d Mon Sep 17 00:00:00 2001 From: Kasper Christensen Date: Mon, 2 Sep 2019 10:00:44 +0200 Subject: [PATCH 456/776] =?UTF-8?q?Loginpage:=20Added=20an=20id=20to=20inp?= =?UTF-8?q?ut=20fields=20and=20a=20for=20attr=20on=20the=20la=E2=80=A6=20(?= =?UTF-8?q?#6247)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 24efa54481f98078740e20a5e84f70f587df2ab6) --- .../components/application/umb-login.html | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html index bc27196466..676b2efc38 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html @@ -18,11 +18,11 @@

-
- - + + Required The confirmed password doesn't match the new password! @@ -152,13 +152,13 @@
- - + +
- - + +
Show password @@ -189,8 +189,8 @@
- - + +
@@ -220,13 +220,13 @@
- - + +
- - + +
From 3d3a836c5281de142d36febe49ced5b845b6b251 Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Fri, 30 Aug 2019 23:19:54 +0100 Subject: [PATCH 457/776] fix to the documentation link in create packages opening in same window --- src/Umbraco.Web.UI.Client/src/views/packages/edit.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/packages/edit.html b/src/Umbraco.Web.UI.Client/src/views/packages/edit.html index 2df95cec0d..7a95fd9214 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packages/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/packages/edit.html @@ -298,7 +298,7 @@ description="Here you can add custom installer / uninstaller events to perform certain tasks during installation and uninstallation. All actions are formed as a xml node, containing data for the action to be performed.">
- Documentation + Documentation
From 1963dbe6394f3f56a329f3b0288936642fec8897 Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Fri, 30 Aug 2019 23:19:54 +0100 Subject: [PATCH 458/776] fix to the documentation link in create packages opening in same window (cherry picked from commit 3d3a836c5281de142d36febe49ced5b845b6b251) --- src/Umbraco.Web.UI.Client/src/views/packages/edit.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/packages/edit.html b/src/Umbraco.Web.UI.Client/src/views/packages/edit.html index 2df95cec0d..7a95fd9214 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packages/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/packages/edit.html @@ -298,7 +298,7 @@ description="Here you can add custom installer / uninstaller events to perform certain tasks during installation and uninstallation. All actions are formed as a xml node, containing data for the action to be performed.">
- Documentation + Documentation
From 157c0a2479b4566901b9bfa2d80f92dab8354fa1 Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Sat, 31 Aug 2019 21:22:12 +0100 Subject: [PATCH 459/776] Added Review process file --- .github/REVIEW_PROCESS.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/REVIEW_PROCESS.md diff --git a/.github/REVIEW_PROCESS.md b/.github/REVIEW_PROCESS.md new file mode 100644 index 0000000000..917d25b090 --- /dev/null +++ b/.github/REVIEW_PROCESS.md @@ -0,0 +1,25 @@ +# Review process + +You're an awesome person and have sent us your contribution in the form of a pull request! It's now time to relax for a bit and wait for our response. + +In order to set some expectations, here's what happens next. + +## Review process + +You will get an initial reply within 48 hours (workdays) to acknowledge that we’ve seen your PR and we’ll pick it up as soon as we can. + +You will get feedback within at most 14 days after opening the PR. You’ll most likely get feedback sooner though. Then there are a few possible outcomes: + +- Your proposed change is awesome! We merge it in and it will be included in the next minor release of Umbraco +- If the change is a high priority bug fix, we will cherry-pick it into the next patch release as well so that we can release it as soon as possible +- Your proposed change is awesome but needs a bit more work, we’ll give you feedback on the changes we’d like to see +- Your proposed change is awesome but.. not something we’re looking to include at this point. We’ll close your PR and the related issue (we’ll be nice about it!) + +## Are you still available? + +We understand you have other things to do and can't just drop everything to help us out. +So if we’re asking for your help to improve the PR we’ll wait for two weeks to give you a fair chance to make changes. We’ll ask for an update if we don’t hear back from you after that time. + +If we don’t hear back from you for 4 weeks, we’ll close the PR so that it doesn’t just hang around forever. You’re very welcome to re-open it once you have some more time to spend on it. + +There will be times that we really like your proposed changes and we’ll finish the final improvements we’d like to see ourselves. You still get the credits and your commits will live on in the git repository. \ No newline at end of file From 087122868557f28d1f5d0b6f2a372eaf52e3ab22 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Fri, 16 Aug 2019 11:52:34 +0200 Subject: [PATCH 460/776] v8: Add umbLoader directive (#4987) (cherry picked from commit e4f19f71926ff1c0db427d6604798795fbe88f8c) --- .../components/umbloader.directive.js | 75 +++++++++++++++++++ .../components/umbloadindicator.directive.js | 2 +- src/Umbraco.Web.UI.Client/src/less/belle.less | 1 + .../less/components/application/umb-tour.less | 12 ++- .../src/less/components/umb-loader.less | 42 +++++++++++ src/Umbraco.Web.UI.Client/src/less/main.less | 36 --------- .../components/application/umb-tour.html | 3 +- .../src/views/components/umb-loader.html | 3 + .../src/views/content/copy.html | 4 +- .../src/views/content/emptyrecyclebin.html | 5 +- .../src/views/content/move.html | 4 +- .../src/views/datatypes/move.html | 4 +- .../src/views/documenttypes/copy.html | 4 +- .../src/views/documenttypes/move.html | 4 +- .../src/views/media/emptyrecyclebin.html | 5 +- .../src/views/mediatypes/copy.html | 4 +- .../src/views/mediatypes/move.html | 4 +- .../propertyeditors/listview/listview.html | 5 +- 18 files changed, 145 insertions(+), 72 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/directives/components/umbloader.directive.js create mode 100644 src/Umbraco.Web.UI.Client/src/less/components/umb-loader.less create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/umb-loader.html diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbloader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbloader.directive.js new file mode 100644 index 0000000000..e70f7b3cac --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbloader.directive.js @@ -0,0 +1,75 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbLoader +@restrict E + +@description +Use this directive to generate a loading indicator. + +

Markup example

+
+    
+ + + + +
+

{{content}}

+
+ +
+
+ +

Controller example

+
+    (function () {
+        "use strict";
+
+        function Controller(myService) {
+
+            var vm = this;
+
+            vm.content = "";
+            vm.loading = true;
+
+            myService.getContent().then(function(content){
+                vm.content = content;
+                vm.loading = false;
+            });
+
+        }
+
+        angular.module("umbraco").controller("My.Controller", Controller);
+    })();
+
+ +@param {string=} position The loader position ("top", "bottom"). + +**/ + +(function() { + 'use strict'; + + function UmbLoaderDirective() { + + function link(scope, el, attr, ctrl) { + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-loader.html', + scope: { + position: "@?" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbLoader', UmbLoaderDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbloadindicator.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbloadindicator.directive.js index 0671770796..c45b8f3f47 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbloadindicator.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbloadindicator.directive.js @@ -39,7 +39,7 @@ Use this directive to generate a loading indicator. }); } -½ + angular.module("umbraco").controller("My.Controller", Controller); })(); diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index bd1cdd5b4f..cf5ccb3d00 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -124,6 +124,7 @@ @import "components/umb-form-check.less"; @import "components/umb-locked-field.less"; @import "components/umb-tabs.less"; +@import "components/umb-loader.less"; @import "components/umb-load-indicator.less"; @import "components/umb-breadcrumbs.less"; @import "components/umb-media-grid.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less index 315cd91dbd..33a723a3f7 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-tour.less @@ -1,8 +1,12 @@ -.umb-tour__loader { - background: @white; - z-index: @zindexTourModal; +.umb-loader-wrapper.umb-tour__loader { + margin: 0; position: fixed; - height: 5px; + z-index: @zindexTourModal; + + .umb-loader { + background-color: @white; + height: 5px; + } } .umb-tour__pulse { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-loader.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-loader.less new file mode 100644 index 0000000000..260710ce72 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-loader.less @@ -0,0 +1,42 @@ +// Loading Animation +// ------------------------ + +.umb-loader { + background-color: @blue; + margin-top: 0; + margin-left: -100%; + animation-name: bounce_loadingProgressG; + animation-duration: 1s; + animation-iteration-count: infinite; + animation-timing-function: linear; + width: 100%; + height: 2px; +} + +@keyframes bounce_loadingProgressG { + 0% { + margin-left: -100%; + } + + 100% { + margin-left: 100%; + } +} + +.umb-loader-wrapper { + position: absolute; + right: 0; + left: 0; + margin: 10px 0; + overflow: hidden; +} + +.umb-loader-wrapper.-top { + top: 0; + bottom: auto; +} + +.umb-loader-wrapper.-bottom { + top: auto; + bottom: 0; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 9f9bbce310..2134514b4d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -486,42 +486,6 @@ table thead a { color:@green; } -// Loading Animation -// ------------------------ - -.umb-loader{ - background-color: @blue; - margin-top:0; - margin-left:-100%; - animation-name:bounce_loadingProgressG; - animation-duration:1s; - animation-iteration-count:infinite; - animation-timing-function:linear; - width:100%; - height:2px; -} - -@keyframes bounce_loadingProgressG{ - 0%{ - margin-left:-100%; - } - 100%{ - margin-left:100%; - } -} - -.umb-loader-wrapper { - position: absolute; - right: 0; - left: 0; - margin: 10px 0; - overflow: hidden; -} - -.umb-loader-wrapper.-bottom { - bottom: 0; -} - // Helpers .strong { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html index b756d23b50..2910986048 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html @@ -1,6 +1,7 @@
-
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-loader.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-loader.html new file mode 100644 index 0000000000..07aeb7fdfa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-loader.html @@ -0,0 +1,3 @@ +
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/copy.html b/src/Umbraco.Web.UI.Client/src/views/content/copy.html index 0ebe577ed8..111dacd7cb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/copy.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/copy.html @@ -24,9 +24,7 @@ to in the tree structure below

-
-
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/emptyrecyclebin.html b/src/Umbraco.Web.UI.Client/src/views/content/emptyrecyclebin.html index 524bb06354..9ac7ef10fc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/emptyrecyclebin.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/emptyrecyclebin.html @@ -1,9 +1,8 @@
-
-
-
+ +

When items are deleted from the recycle bin, they will be gone forever. diff --git a/src/Umbraco.Web.UI.Client/src/views/content/move.html b/src/Umbraco.Web.UI.Client/src/views/content/move.html index fb72c38974..0bc2012ad3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/move.html @@ -24,9 +24,7 @@ to in the tree structure below

-
-
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html b/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html index 2723dd305d..dd943bf832 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/move.html @@ -7,9 +7,7 @@ Select the folder to move {{source.name}} to in the tree structure below

-
-
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/copy.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/copy.html index 4b90c244e8..7ea990e0bf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/copy.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/copy.html @@ -7,9 +7,7 @@ Select the folder to copy {{source.name}} to in the tree structure below

-
-
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html index 6b81c15bc5..49fdae9c92 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/move.html @@ -7,9 +7,7 @@ Select the folder to move {{source.name}} to in the tree structure below

-
-
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/media/emptyrecyclebin.html b/src/Umbraco.Web.UI.Client/src/views/media/emptyrecyclebin.html index 9d1b28edc9..33681f9269 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/emptyrecyclebin.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/emptyrecyclebin.html @@ -2,9 +2,8 @@
-
-
-
+ +

When items are deleted from the recycle bin, they will be gone forever. diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html index 05075ecbec..bd54dfe4a1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/copy.html @@ -7,9 +7,7 @@ Select the folder to copy {{source.name}} to in the tree structure below

-
-
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html b/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html index 01d5ade7b1..4d4a840850 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/move.html @@ -7,9 +7,7 @@ Select the folder to move {{source.name}} to in the tree structure below

-
-
-
+
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 e971897f2a..0770081aba 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 @@ -95,9 +95,8 @@ {{ selectedItemsCount() }} of {{ listViewResultSet.items.length }} selected -
-
-
+ + From 6588fa2e379bf7be38c63595540729cc084cb9d3 Mon Sep 17 00:00:00 2001 From: Liam Laverty Date: Tue, 3 Sep 2019 15:28:08 +0100 Subject: [PATCH 461/776] updated to 700 people --- src/Umbraco.Web.UI.Client/src/installer/installer.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js index 50b9f2f3c0..f6f162f04f 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js @@ -31,7 +31,7 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco "At least 4 people have the Umbraco logo tattooed on them", "'Umbraco' is the Danish name for an allen key", "Umbraco has been around since 2005, that's a looong time in IT", - "More than 600 people from all over the world meet each year in Denmark in May for our annual conference CodeGarden", + "More than 700 people from all over the world meet each year in Denmark in May for our annual conference CodeGarden", "While you are installing Umbraco someone else on the other side of the planet is probably doing it too", "You can extend Umbraco without modifying the source code using either JavaScript or C#", "Umbraco has been installed in more than 198 countries" From 8b0a64f8cd37d4268de3aef098a38e0127cda1e7 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 4 Sep 2019 14:22:57 +0100 Subject: [PATCH 462/776] Use the angular 3rd party lib angular-local-storage (which falls back to cookies) instead of native localStorage --- .../src/common/services/tinymce.service.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index b8a1191175..23104b99f5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -6,7 +6,8 @@ * @description * A service containing all logic for all of the Umbraco TinyMCE plugins */ -function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, stylesheetResource, macroResource, macroService, $routeParams, umbRequestHelper, angularHelper, userService, editorService, entityResource) { +function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, stylesheetResource, macroResource, macroService, + $routeParams, umbRequestHelper, angularHelper, userService, editorService, entityResource, localStorageService) { //These are absolutely required in order for the macros to render inline //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce @@ -190,7 +191,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s } // Put UDI into localstorage (used to update the img with data-udi later on) - localStorage.setItem(`tinymce__${json.location}`, json.udi); + localStorageService.set(`tinymce__${json.location}`, json.udi); success(json.location); }; @@ -222,13 +223,13 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s var imgSrc = img.getAttribute("src"); //Try & find in localstorage - var udi = localStorage.getItem(`tinymce__${imgSrc}`); + var udi = localStorageService.get(`tinymce__${imgSrc}`); //Select the img & update is attr tinymce.activeEditor.$(img).attr({ "data-udi": udi }); //Remove key - localStorage.removeItem(`tinymce__${imgSrc}`); + localStorageService.remove(`tinymce__${imgSrc}`); }); }); } From 5bbe1faddabec021d599742744adf3c6ad4c0ebf Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 5 Sep 2019 10:31:08 +0200 Subject: [PATCH 463/776] Fixes #6021 - Error on save and publish for existing node after content creation in eventhandler --- src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 8e2cf7bc3c..1bd58c3878 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -712,9 +712,9 @@ namespace Umbraco.Web.PublishedCache.NuCache var id = content.FirstChildContentId; while (id > 0) { - var link = GetLinkedNode(id, "child"); - ClearBranchLocked(link.Value); - id = link.Value.NextSiblingContentId; + var child = GetLinkedNode(id, "child").Value; + ClearBranchLocked(child); + id = child.NextSiblingContentId; } } From d3f3c74e10770d839c4c025e329219bf1d3a89e3 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 5 Sep 2019 11:47:13 +0200 Subject: [PATCH 464/776] Fix #6283 Save Member resets password --- .../src/views/member/member.edit.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js index eb99e46a1f..8d54597791 100644 --- a/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js @@ -133,7 +133,7 @@ function MemberEditController($scope, $routeParams, $location, $q, $window, appS //anytime a user is changing a member's password without the oldPassword, we are in effect resetting it so we need to set that flag here var passwordProp = _.find(contentEditingHelper.getAllProps($scope.content), function (e) { return e.alias === '_umb_password' }); - if (passwordProp && passwordProp.value && !passwordProp.value.reset) { + if (passwordProp && passwordProp.value && (typeof passwordProp.value.reset !== 'undefined') && !passwordProp.value.reset) { //so if the admin is not explicitly resetting the password, flag it for resetting if a new password is being entered passwordProp.value.reset = !passwordProp.value.oldPassword && passwordProp.config.allowManuallyChangingPassword; } From 4b344ac3fdde5801074a1539f83b5e6adfcc51cf Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Thu, 5 Sep 2019 12:29:25 +0200 Subject: [PATCH 465/776] Accessibility: Grid filter drop down can't be accessed via keyboard (#5963) --- .../components/events/events.directive.js | 37 +++++++++++++++++++ .../components/umblayoutselector.directive.js | 5 +++ .../less/components/umb-layout-selector.less | 2 + .../views/components/umb-layout-selector.html | 18 ++++++--- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 3 +- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 3 +- .../Umbraco/config/lang/en_us.xml | 3 +- 7 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js index 15e74bbd90..53aa7475c4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js @@ -233,4 +233,41 @@ angular.module('umbraco.directives') }); } }; + }) + + // A slightly modified version of https://github.com/myplanet/angular-deep-blur/blob/master/angular-deep-blur.js - Kudos to Ufuk Kayserilioglu (paracycle) + .directive('deepBlur', function ($timeout) { + return { + + restrict: 'A', + + controller: function ($scope, $element, $attrs) { + var leaveExpr = $attrs.deepBlur, + dom = $element[0]; + + function containsDom(parent, dom) { + while (dom) { + if (dom === parent) { + return true; + } + dom = dom.parentNode; + } + return false; + } + + function onBlur(e) { + var targetElement = e.relatedTarget; + + if (!containsDom(dom, targetElement)) { + $timeout(function () { + $scope.$apply(leaveExpr); + }, 10); + } + } + + dom.addEventListener('blur', onBlur, true); + } + }; }); + + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js index 58a5e1be0e..7453353018 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js @@ -24,6 +24,7 @@ vm.showLayoutSelector = true; vm.pickLayout = pickLayout; vm.toggleLayoutDropdown = toggleLayoutDropdown; + vm.leaveLayoutDropdown = leaveLayoutDropdown; vm.closeLayoutDropdown = closeLayoutDropdown; function onInit() { @@ -38,6 +39,10 @@ vm.layoutDropDownIsOpen = !vm.layoutDropDownIsOpen; } + function leaveLayoutDropdown() { + vm.layoutDropDownIsOpen = false; + } + function pickLayout(selectedLayout) { if (vm.onLayoutSelect) { vm.onLayoutSelect({ layout: selectedLayout }); 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 cf407b667f..cdc6cfcb63 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 @@ -4,6 +4,7 @@ } .umb-layout-selector__active-layout { + background: transparent; box-sizing: border-box; border: 1px solid @inputBorder; cursor: pointer; @@ -33,6 +34,7 @@ } .umb-layout-selector__dropdown-item { + background: transparent; padding: 5px; margin: 3px 5px; display: flex; 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 c84e63a359..c6c841f8b1 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 @@ -1,20 +1,26 @@
-
- -
+
+ on-outside-click="vm.closeLayoutDropdown()" + deep-blur="vm.leaveLayoutDropdown()"> -
- + + {{layout.name}}
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 8d4226fe36..0e43d4217e 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -1613,6 +1613,7 @@ Mange hilsner fra Umbraco robotten Installer Umbraco Forms - Gå tilbage + Gå tilbage + Aktivt layout: diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index a8114faf93..41c77d0c39 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -2131,6 +2131,7 @@ To manage your website, simply open the Umbraco back office and start adding con Install Umbraco Forms - Go back + Go back + Active layout: 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 a933b8507d..d9cea4f14c 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -2145,6 +2145,7 @@ To manage your website, simply open the Umbraco back office and start adding con Install Umbraco Forms - Go back + Go back + Active layout: From e1a438dfc9183737d502d9d53a9c3979220807ad Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Thu, 5 Sep 2019 12:29:25 +0200 Subject: [PATCH 466/776] Accessibility: Grid filter drop down can't be accessed via keyboard (#5963) (cherry picked from commit 4b344ac3fdde5801074a1539f83b5e6adfcc51cf) # Conflicts: # src/Umbraco.Web.UI/Umbraco/config/lang/da.xml # src/Umbraco.Web.UI/Umbraco/config/lang/en.xml # src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml --- .../components/events/events.directive.js | 37 +++++++++++++++++++ .../components/umblayoutselector.directive.js | 5 +++ .../less/components/umb-layout-selector.less | 2 + .../views/components/umb-layout-selector.html | 18 ++++++--- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 6 ++- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 6 ++- .../Umbraco/config/lang/en_us.xml | 6 ++- 7 files changed, 71 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js index 15e74bbd90..53aa7475c4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js @@ -233,4 +233,41 @@ angular.module('umbraco.directives') }); } }; + }) + + // A slightly modified version of https://github.com/myplanet/angular-deep-blur/blob/master/angular-deep-blur.js - Kudos to Ufuk Kayserilioglu (paracycle) + .directive('deepBlur', function ($timeout) { + return { + + restrict: 'A', + + controller: function ($scope, $element, $attrs) { + var leaveExpr = $attrs.deepBlur, + dom = $element[0]; + + function containsDom(parent, dom) { + while (dom) { + if (dom === parent) { + return true; + } + dom = dom.parentNode; + } + return false; + } + + function onBlur(e) { + var targetElement = e.relatedTarget; + + if (!containsDom(dom, targetElement)) { + $timeout(function () { + $scope.$apply(leaveExpr); + }, 10); + } + } + + dom.addEventListener('blur', onBlur, true); + } + }; }); + + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js index 58a5e1be0e..7453353018 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js @@ -24,6 +24,7 @@ vm.showLayoutSelector = true; vm.pickLayout = pickLayout; vm.toggleLayoutDropdown = toggleLayoutDropdown; + vm.leaveLayoutDropdown = leaveLayoutDropdown; vm.closeLayoutDropdown = closeLayoutDropdown; function onInit() { @@ -38,6 +39,10 @@ vm.layoutDropDownIsOpen = !vm.layoutDropDownIsOpen; } + function leaveLayoutDropdown() { + vm.layoutDropDownIsOpen = false; + } + function pickLayout(selectedLayout) { if (vm.onLayoutSelect) { vm.onLayoutSelect({ layout: selectedLayout }); 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 cf407b667f..cdc6cfcb63 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 @@ -4,6 +4,7 @@ } .umb-layout-selector__active-layout { + background: transparent; box-sizing: border-box; border: 1px solid @inputBorder; cursor: pointer; @@ -33,6 +34,7 @@ } .umb-layout-selector__dropdown-item { + background: transparent; padding: 5px; margin: 3px 5px; display: flex; 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 c84e63a359..c6c841f8b1 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 @@ -1,20 +1,26 @@
-
- -
+
+ on-outside-click="vm.closeLayoutDropdown()" + deep-blur="vm.leaveLayoutDropdown()"> -
- + + {{layout.name}}
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 5ae084bc06..b16fea3301 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -1610,5 +1610,9 @@ Mange hilsner fra Umbraco robotten Profiling Kom godt i gang Installer Umbraco Forms - + + + Gå tilbage + Aktivt layout: + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index fc8a6cf213..df23ce9204 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -2128,5 +2128,9 @@ To manage your website, simply open the Umbraco back office and start adding con Profiling Getting Started Install Umbraco Forms - > + + + Go back + Active layout: + 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 13ec6f8135..c4e07f18ca 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -2142,5 +2142,9 @@ To manage your website, simply open the Umbraco back office and start adding con Profiling Getting Started Install Umbraco Forms - + + + Go back + Active layout: + From b94d78e01a88435a1f5b0ecb49f9bf50c05e0d85 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 5 Sep 2019 13:01:05 +0200 Subject: [PATCH 467/776] Revert "Accessibility: Grid filter drop down can't be accessed via keyboard (#5963)" This reverts commit e1a438dfc9183737d502d9d53a9c3979220807ad. Should not have been picked into a patch release :) --- .../components/events/events.directive.js | 37 ------------------- .../components/umblayoutselector.directive.js | 5 --- .../less/components/umb-layout-selector.less | 2 - .../views/components/umb-layout-selector.html | 18 +++------ src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 6 +-- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 6 +-- .../Umbraco/config/lang/en_us.xml | 6 +-- 7 files changed, 9 insertions(+), 71 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js index 53aa7475c4..15e74bbd90 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js @@ -233,41 +233,4 @@ angular.module('umbraco.directives') }); } }; - }) - - // A slightly modified version of https://github.com/myplanet/angular-deep-blur/blob/master/angular-deep-blur.js - Kudos to Ufuk Kayserilioglu (paracycle) - .directive('deepBlur', function ($timeout) { - return { - - restrict: 'A', - - controller: function ($scope, $element, $attrs) { - var leaveExpr = $attrs.deepBlur, - dom = $element[0]; - - function containsDom(parent, dom) { - while (dom) { - if (dom === parent) { - return true; - } - dom = dom.parentNode; - } - return false; - } - - function onBlur(e) { - var targetElement = e.relatedTarget; - - if (!containsDom(dom, targetElement)) { - $timeout(function () { - $scope.$apply(leaveExpr); - }, 10); - } - } - - dom.addEventListener('blur', onBlur, true); - } - }; }); - - diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js index 7453353018..58a5e1be0e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblayoutselector.directive.js @@ -24,7 +24,6 @@ vm.showLayoutSelector = true; vm.pickLayout = pickLayout; vm.toggleLayoutDropdown = toggleLayoutDropdown; - vm.leaveLayoutDropdown = leaveLayoutDropdown; vm.closeLayoutDropdown = closeLayoutDropdown; function onInit() { @@ -39,10 +38,6 @@ vm.layoutDropDownIsOpen = !vm.layoutDropDownIsOpen; } - function leaveLayoutDropdown() { - vm.layoutDropDownIsOpen = false; - } - function pickLayout(selectedLayout) { if (vm.onLayoutSelect) { vm.onLayoutSelect({ layout: selectedLayout }); 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 cdc6cfcb63..cf407b667f 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 @@ -4,7 +4,6 @@ } .umb-layout-selector__active-layout { - background: transparent; box-sizing: border-box; border: 1px solid @inputBorder; cursor: pointer; @@ -34,7 +33,6 @@ } .umb-layout-selector__dropdown-item { - background: transparent; padding: 5px; margin: 3px 5px; display: flex; 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 c6c841f8b1..c84e63a359 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 @@ -1,26 +1,20 @@
- +
+ +
+ on-outside-click="vm.closeLayoutDropdown()"> -
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index b16fea3301..5ae084bc06 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -1610,9 +1610,5 @@ Mange hilsner fra Umbraco robotten Profiling Kom godt i gang Installer Umbraco Forms - - - Gå tilbage - Aktivt layout: - + diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index df23ce9204..fc8a6cf213 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -2128,9 +2128,5 @@ To manage your website, simply open the Umbraco back office and start adding con Profiling Getting Started Install Umbraco Forms - - - Go back - Active layout: - + > 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 c4e07f18ca..13ec6f8135 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -2142,9 +2142,5 @@ To manage your website, simply open the Umbraco back office and start adding con Profiling Getting Started Install Umbraco Forms - - - Go back - Active layout: - + From 428da6ce1331af62353613366a956335d32222a2 Mon Sep 17 00:00:00 2001 From: Jan Skovgaard Date: Thu, 5 Sep 2019 13:41:21 +0200 Subject: [PATCH 468/776] Accessibility: Create drop down can't be accessed via keyboard (#5960) --- .../lib/bootstrap/less/dropdowns.less | 2 + .../lib/bootstrap/less/sprites.less | 4 ++ .../src/less/canvas-designer.less | 10 ++- .../components/buttons/umb-button-group.less | 3 +- src/Umbraco.Web.UI.Client/src/less/navs.less | 22 +++++- .../listview/listview.controller.js | 14 +++- .../propertyeditors/listview/listview.html | 72 +++++++++++-------- 7 files changed, 91 insertions(+), 36 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/dropdowns.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/dropdowns.less index 7707e04feb..94f229a191 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/dropdowns.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/dropdowns.less @@ -80,6 +80,8 @@ // ----------- .dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus, +.dropdown-menu > li > button:hover, +.dropdown-menu > li > button:focus, .dropdown-submenu:hover > a, .dropdown-submenu:focus > a { text-decoration: none; diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/sprites.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/sprites.less index d73e23f5ea..dd60f10cee 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/sprites.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/sprites.less @@ -39,6 +39,10 @@ .dropdown-menu > li > a:focus > [class^="icon-"], .dropdown-menu > li > a:hover > [class*=" icon-"], .dropdown-menu > li > a:focus > [class*=" icon-"], +.dropdown-menu > li > button:hover > [class^="icon-"], +.dropdown-menu > li > button:focus > [class^="icon-"], +.dropdown-menu > li > button:hover > [class*=" icon-"], +.dropdown-menu > li > button:focus > [class*=" icon-"], .dropdown-menu > .active > a > [class^="icon-"], .dropdown-menu > .active > a > [class*=" icon-"], .dropdown-submenu:hover > a > [class^="icon-"], diff --git a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less index 7440a5723a..4db2e434d2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less +++ b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less @@ -163,7 +163,8 @@ a, a:hover{ background-clip: padding-box; } -.dropdown-menu > li > a { +.dropdown-menu > li > a, +.dropdown-menu > li > button { display: block; padding: 3px 20px; clear: both; @@ -174,7 +175,12 @@ a, a:hover{ cursor:pointer; } -.dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus, .dropdown-submenu:hover > a, .dropdown-submenu:focus > a { +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus, +.dropdown-menu > li > button:hover, +.dropdown-menu > li > button:focus, +.dropdown-submenu:hover > a, +.dropdown-submenu:focus > a { color: #000000; background: #e4e0dd; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less index e40282cb58..0465881387 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less @@ -9,6 +9,7 @@ left: auto; } +.umb-button-group__sub-buttons>li>button, .umb-button-group__sub-buttons>li>a { display: flex; } @@ -20,7 +21,7 @@ } .umb-button-group__toggle { - border-radius: 0px @baseBorderRadius @baseBorderRadius 0; + border-radius: 0 @baseBorderRadius @baseBorderRadius 0; border-left: 1px solid rgba(0,0,0,0.09); margin-left: -2px; padding-left: 10px; diff --git a/src/Umbraco.Web.UI.Client/src/less/navs.less b/src/Umbraco.Web.UI.Client/src/less/navs.less index a2710fab6c..5b97464e31 100644 --- a/src/Umbraco.Web.UI.Client/src/less/navs.less +++ b/src/Umbraco.Web.UI.Client/src/less/navs.less @@ -237,7 +237,27 @@ color: @ui-option-type; } -.dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus, .dropdown-submenu:hover > a, .dropdown-submenu:focus > a { +.dropdown-menu > li > button { + background: transparent; + border: 0; + padding: 8px 20px; + color: @ui-option-type; + display: block; + clear: both; + font-weight: normal; + line-height: 20px; + white-space: nowrap; + cursor:pointer; + width: 100%; + text-align: left; +} + +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus, +.dropdown-menu > li > button:hover, +.dropdown-menu > li > button:focus, +.dropdown-submenu:hover > a, +.dropdown-submenu:focus > a { color: @ui-option-type-hover; background: @ui-option-hover; } 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 2fbd2b0a32..ce0815c640 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 @@ -53,7 +53,9 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs $scope.actionInProgress = false; $scope.selection = []; $scope.folders = []; - $scope.page = {}; + $scope.page = { + createDropdownOpen: false + }; $scope.listViewResultSet = { totalPages: 0, items: [] @@ -795,8 +797,18 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs .search("blueprintId", blueprintId); } + function toggleDropdown () { + $scope.page.createDropdownOpen = !$scope.page.createDropdownOpen; + } + + function leaveDropdown () { + $scope.page.createDropdownOpen = false; + } + $scope.createBlank = createBlank; $scope.createFromBlueprint = createFromBlueprint; + $scope.toggleDropdown = toggleDropdown; + $scope.leaveDropdown = leaveDropdown; //GO! initView(); 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 0770081aba..304f3f3fbc 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 @@ -12,57 +12,68 @@ + -
- - + + - - + + +