From 7db0440b5c08927a5c753fc3e49a0446ee7fc2ea Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 16 Apr 2019 10:11:16 +0200 Subject: [PATCH] Merge branch 'v7/dev' into v8/dev # Conflicts: # src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs # src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs # src/Umbraco.Core/Services/ContentService.cs # src/Umbraco.Core/Services/IContentService.cs # src/Umbraco.Core/UriExtensions.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/search.service.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/mediaPicker/mediapicker.controller.js # 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/components/content/umb-content-node-info.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/Cache/UnpublishedPageCacheRefresher.cs # src/Umbraco.Web/Editors/ContentController.cs # src/Umbraco.Web/Editors/EntityController.cs # src/Umbraco.Web/Editors/MediaController.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/PublishedCache/ContextualPublishedCache.cs # src/Umbraco.Web/PublishedCache/ContextualPublishedContentCache.cs # src/Umbraco.Web/PublishedCache/ContextualPublishedMediaCache.cs # src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs # src/Umbraco.Web/Search/UmbracoTreeSearcher.cs # src/Umbraco.Web/Trees/ContentTreeControllerBase.cs # src/Umbraco.Web/Trees/TreeControllerBase.cs # src/Umbraco.Web/Trees/TreeQueryStringParameters.cs # src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs # src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs # src/Umbraco.Web/umbraco.presentation/content.cs # src/Umbraco.Web/umbraco.presentation/umbraco/preview/PreviewContent.cs --- .../tree/umbtreesearchbox.directive.js | 7 ++ .../src/common/resources/content.resource.js | 17 ++- .../src/common/resources/entity.resource.js | 41 +++++-- .../src/common/resources/media.resource.js | 4 +- .../src/common/services/search.service.js | 20 +++- .../src/common/services/tinymce.service.js | 30 ++++- .../linkpicker/linkpicker.controller.js | 27 ++++- .../linkpicker/linkpicker.html | 2 + .../mediapicker/mediapicker.controller.js | 37 ++++-- .../treepicker/treepicker.controller.js | 4 + .../treepicker/treepicker.html | 3 +- .../contentpicker/contentpicker.controller.js | 5 +- .../contentpicker/contentpicker.html | 1 + .../grid/editors/media.controller.js | 17 ++- .../mediapicker/mediapicker.controller.js | 43 ++++--- .../multiurlpicker.controller.js | 3 +- .../relatedlinks/relatedlinks.controller.js | 2 + .../propertyeditors/rte/rte.prevalues.html | 8 ++ src/Umbraco.Web.UI/favicon.ico | Bin 0 -> 15406 bytes src/Umbraco.Web/Editors/EntityController.cs | 108 ++++++++++++------ src/Umbraco.Web/Editors/MediaController.cs | 61 +++++++++- .../ContentPickerConfiguration.cs | 3 + .../PropertyEditors/GridConfiguration.cs | 3 + .../MediaPickerConfiguration.cs | 3 + .../MultiNodePickerConfiguration.cs | 3 + .../MultiUrlPickerConfiguration.cs | 3 + .../PropertyEditors/RichTextConfiguration.cs | 3 + src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 39 ++++++- .../Trees/ContentTreeControllerBase.cs | 11 +- src/Umbraco.Web/Trees/TreeControllerBase.cs | 10 ++ .../Trees/TreeQueryStringParameters.cs | 1 + ...EnsureUserPermissionForContentAttribute.cs | 42 ++++--- .../FilterAllowedOutgoingMediaAttribute.cs | 9 +- 33 files changed, 449 insertions(+), 121 deletions(-) create mode 100644 src/Umbraco.Web.UI/favicon.ico 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 b807a4dc31..d571de0e2d 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 @@ -365,17 +365,28 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { * * * @param {Int} id id of content item to return - * @param {Int} culture optional culture to retrieve the item in + * @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) { + 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 })), + [{ id: id }, { ignoreUserStartNodes: options.ignoreUserStartNodes }])), 'Failed to retrieve data for content id ' + id) .then(function (result) { return $q.when(umbDataFormatter.formatContentGetData(result)); 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..d5145727ac 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 @@ -284,15 +284,31 @@ function entityResource($q, $http, umbRequestHelper) { * @returns {Promise} resourcePromise object containing the entity. * */ - getAncestors: function (id, type, culture) { + getAncestors: function (id, type, culture, 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; if (culture === undefined) culture = ""; return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetAncestors", - [{ id: id }, { type: type }, { culture: culture }])), - 'Failed to retrieve ancestor data for id ' + id); + [ + { id: id }, + { type: type }, + { culture: culture }, + { ignoreUserStartNodes: options.ignoreUserStartNodes } + ])), + + 'Failed to retrieve ancestor data for id ' + id); }, /** @@ -424,7 +440,8 @@ function entityResource($q, $http, umbRequestHelper) { pageNumber: 1, filter: '', orderDirection: "Ascending", - orderBy: "SortOrder" + orderBy: "SortOrder", + ignoreUserStartNodes: false }; if (options === undefined) { options = {}; @@ -453,7 +470,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); @@ -481,12 +499,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 }); + + if(options !== undefined) { + if (options.searchFrom) { + args.push({ searchFrom: options.searchFrom }); + } + if (options.ignoreUserStartNodes) { + args.push({ ignoreUserStartNodes: options.ignoreUserStartNodes }); + } } + var httpConfig = {}; if (canceler) { 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..462184c9f2 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 = {}; @@ -367,6 +368,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) { "GetChildren", [ { id: parentId }, + { ignoreUserStartNodes: options.ignoreUserStartNodes }, { pageNumber: options.pageNumber }, { pageSize: options.pageSize }, { orderBy: options.orderBy }, 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..a2010d20f2 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); }); 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 ce4bf6077c..3c4f5a7d73 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 @@ -1143,11 +1143,25 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s let self = this; + function getIgnoreUserStartNodes(args) { + var ignoreUserStartNodes = false; + // Most property editors have a "config" property with ignoreUserStartNodes on then + if (args.model.config) { + ignoreUserStartNodes = Object.toBoolean(args.model.config.ignoreUserStartNodes); + } + // EXCEPT for the grid's TinyMCE editor, that one wants to be special and the config is called "configuration" instead + else if (args.model.configuration) { + ignoreUserStartNodes = Object.toBoolean(args.model.configuration.ignoreUserStartNodes); + } + return ignoreUserStartNodes; + } + //create link picker self.createLinkPicker(args.editor, function (currentTarget, anchorElement) { var linkPicker = { currentTarget: currentTarget, anchors: editorState.current ? self.getAnchorNames(JSON.stringify(editorState.current.properties)) : [], + ignoreUserStartNodes: getIgnoreUserStartNodes(args), submit: function (model) { self.insertLinkInEditor(args.editor, model.target, anchorElement); editorService.close(); @@ -1161,13 +1175,25 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s //Create the insert media plugin self.createMediaPicker(args.editor, function (currentTarget, userData) { + + var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + var startNodeIsVirtual = userData.startMediaIds.length !== 1; + + var ignoreUserStartNodes = getIgnoreUserStartNodes(args); + if (ignoreUserStartNodes) { + ignoreUserStartNodes = true; + startNodeId = -1; + startNodeIsVirtual = true; + } + 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: startNodeId, + startNodeIsVirtual: startNodeIsVirtual, + ignoreUserStartNodes: ignoreUserStartNodes, 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 f4725fa82d..1e3cf54450 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 @@ -28,9 +28,11 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", searchFromName: null, showSearch: false, results: [], - selectedSearchResults: [] + selectedSearchResults: [], + ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes }; + $scope.customTreeParams = dialogOptions.ignoreUserStartNodes ? "ignoreUserStartNodes=" + dialogOptions.ignoreUserStartNodes : ""; $scope.showTarget = $scope.model.hideTarget !== true; // this ensures that we only sync the tree once and only when it's ready @@ -73,7 +75,11 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", }); // get the content properties to build the anchor name list - contentResource.getById(id).then(function (resp) { + + var options = {}; + options.ignoreUserStartNodes = dialogOptions.ignoreUserStartNodes; + + contentResource.getById(id, options).then(function (resp) { $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); $scope.model.target.url = resp.urls[0].text; }); @@ -119,7 +125,10 @@ 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) { + var options = {}; + options.ignoreUserStartNodes = dialogOptions.ignoreUserStartNodes; + + contentResource.getById(args.node.id, options).then(function (resp) { $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); $scope.model.target.url = resp.urls[0].text; }); @@ -139,9 +148,17 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", $scope.switchToMediaPicker = function () { userService.getCurrentUser().then(function (userData) { + var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0]; + var startNodeIsVirtual = userData.startMediaIds.length !== 1; + if (dialogOptions.ignoreUserStartNodes) { + startNodeId = -1; + startNodeIsVirtual = true; + } + var mediaPicker = { - startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0], - startNodeIsVirtual: userData.startMediaIds.length !== 1, + startNodeId: startNodeId, + startNodeIsVirtual: startNodeIsVirtual, + ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes, 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 71fcf2f493..414dbecfc2 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 @@ -68,6 +68,7 @@ search-from-id="{{searchInfo.searchFromId}}" search-from-name="{{searchInfo.searchFromName}}" show-search="{{searchInfo.showSearch}}" + ignore-user-start-nodes="{{searchInfo.ignoreUserStartNodes}}" section="{{section}}"> @@ -84,6 +85,7 @@ section="content" hideheader="true" hideoptions="true" + customtreeparams="{{customTreeParams}}" api="dialogTreeApi" on-init="onTreeInit()" enablelistviewexpand="true" 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 2d6a2be471..2ba2300730 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 @@ -20,11 +20,13 @@ angular.module("umbraco") $scope.showDetails = dialogOptions.showDetails; $scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false; $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1; + $scope.ignoreUserStartNodes = Object.toBoolean(dialogOptions.ignoreUserStartNodes); $scope.cropSize = dialogOptions.cropSize; $scope.lastOpenedNode = localStorageService.get("umbLastOpenedMediaNodeId"); $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: "", + ignoreUserStartNodes: $scope.model.ignoreUserStartNodes }; //preload selected item @@ -66,7 +69,7 @@ angular.module("umbraco") function onInit() { if ($scope.startNodeId !== -1) { entityResource.getById($scope.startNodeId, "media") - .then(function (ent) { + .then(function(ent) { $scope.startNodeId = ent.id; run(); }); @@ -143,7 +146,7 @@ angular.module("umbraco") } }; - $scope.gotoFolder = function(folder) { + $scope.gotoFolder = function (folder) { if (!$scope.multiPicker) { deselectAllImages($scope.model.selection); } @@ -152,8 +155,10 @@ angular.module("umbraco") folder = { id: -1, name: "Media", icon: "icon-folder" }; } + var options = {}; if (folder.id > 0) { - entityResource.getAncestors(folder.id, "media") + options.ignoreUserStartNodes = $scope.model.ignoreUserStartNodes; + entityResource.getAncestors(folder.id, "media", options) .then(function(anc) { $scope.path = _.filter(anc, function(f) { @@ -169,13 +174,26 @@ 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); - return getChildren(folder.id); + options.ignoreUserStartNodes = $scope.ignoreUserStartNodes; + return getChildren(folder.id, options); }; + 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; + } + $scope.clickHandler = function(image, event, index) { if (image.isFolder) { if ($scope.disableFolderSelect) { @@ -299,7 +317,8 @@ angular.module("umbraco") pageSize: 100, totalItems: 0, totalPages: 0, - filter: '' + filter: "", + ignoreUserStartNodes: $scope.model.ignoreUserStartNodes }; getChildren($scope.currentFolder.id); } @@ -367,9 +386,9 @@ angular.module("umbraco") } } - function getChildren(id) { + function getChildren(id, options) { $scope.loading = true; - return mediaResource.getChildren(id) + return mediaResource.getChildren(id, options) .then(function(data) { $scope.searchOptions.filter = ""; $scope.images = data.items ? data.items : []; 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 5883313753..a6e2838b56 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 @@ -36,6 +36,7 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", selectedSearchResults: [] } vm.startNodeId = $scope.model.startNodeId; + vm.ignoreUserStartNodes = $scope.model.ignoreUserStartNodes; //Used for toggling an empty-state message //Some trees can have no items (dictionary & forms email templates) vm.hasItems = true; @@ -171,6 +172,9 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", if (vm.startNodeId) { queryParams["startNodeId"] = $scope.model.startNodeId; } + if (vm.ignoreUserStartNodes) { + queryParams["ignoreUserStartNodes"] = $scope.model.ignoreUserStartNodes; + } if (vm.selectedLanguage && vm.selectedLanguage.id) { queryParams["culture"] = vm.selectedLanguage.culture; } 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 c592b4ec3b..acd838f7bf 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}} - +
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 866cfb54ab..894ad2eedf 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 @@ -81,6 +81,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper showOpenButton: false, showEditButton: false, showPathOnHover: false, + ignoreUserStartNodes: false, maxNumber: 1, minNumber: 0, startNode: { @@ -118,7 +119,8 @@ function contentPickerController($scope, entityResource, editorState, iconHelper $scope.model.config.showOpenButton = Object.toBoolean($scope.model.config.showOpenButton); $scope.model.config.showEditButton = Object.toBoolean($scope.model.config.showEditButton); $scope.model.config.showPathOnHover = Object.toBoolean($scope.model.config.showPathOnHover); - + $scope.model.config.ignoreUserStartNodes = Object.toBoolean($scope.model.config.ignoreUserStartNodes); + var entityType = $scope.model.config.startNode.type === "member" ? "Member" : $scope.model.config.startNode.type === "media" @@ -134,6 +136,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper entityType: entityType, filterCssClass: "not-allowed not-published", startNodeId: null, + ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes, currentNode: editorState ? editorState.current : null, callback: function (data) { if (angular.isArray(data)) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html index a589cf8947..be4dbb9b12 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html @@ -14,6 +14,7 @@ sortable="!sortableOptions.disabled" allow-remove="allowRemoveButton" allow-open="model.config.showOpenButton && allowOpenButton && !dialogEditor" + ignore-user-startnodes="model.config.ignoreUserStartNodes" on-remove="remove($index)" on-open="openContentEditor(node)"> 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..71bb51f686 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,25 +1,32 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.Grid.MediaController", function ($scope, $timeout, userService, editorService) { + var ignoreUserStartNodes = Object.toBoolean($scope.model.config.ignoreUserStartNodes); $scope.thumbnailUrl = getThumbnailUrl(); 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(){ var startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined; var startNodeIsVirtual = startNodeId ? $scope.model.config.startNodeIsVirtual : undefined; - var mediaPicker = { startNodeId: startNodeId, startNodeIsVirtual: startNodeIsVirtual, + ignoreUserStartNodes: ignoreUserStartNodes, cropSize: $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined, showDetails: true, disableFolderSelect: 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 bac8eb903a..c937360693 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,9 +7,12 @@ 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 = Object.toBoolean($scope.model.config.ignoreUserStartNodes); $scope.allowEditMedia = false; $scope.allowAddMedia = false; + + function setupViewModel() { $scope.mediaItems = []; $scope.ids = []; @@ -90,26 +93,31 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl // reload. We only reload the images that is already picked but has been updated. // We have to get the entities from the server because the media // can be edited without being selected - _.each($scope.images, function (image, i) { - if (updatedMediaNodes.indexOf(image.udi) !== -1) { - image.loading = true; - entityResource.getById(image.udi, "media") - .then(function (mediaEntity) { - angular.extend(image, mediaEntity); - image.thumbnail = mediaHelper.resolveFileFromEntity(image, true); - image.loading = false; - }); - } - }) + _.each($scope.images, + function (image, i) { + if (updatedMediaNodes.indexOf(image.udi) !== -1) { + image.loading = true; + entityResource.getById(image.udi, "media") + .then(function (mediaEntity) { + angular.extend(image, mediaEntity); + image.thumbnail = mediaHelper.resolveFileFromEntity(image, true); + image.loading = false; + }); + } + }); } 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 (ignoreUserStartNodes === true) { + $scope.model.config.startNodeId = -1; + $scope.model.config.startNodeIsVirtual = true; + } + // 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; @@ -167,12 +175,13 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl var mediaPicker = { startNodeId: $scope.model.config.startNodeId, startNodeIsVirtual: $scope.model.config.startNodeIsVirtual, + ignoreUserStartNodes: ignoreUserStartNodes, multiPicker: multiPicker, onlyImages: onlyImages, disableFolderSelect: disableFolderSelect, allowMediaEdit: true, - submit: function(model) { + submit: function (model) { editorService.close(); @@ -182,7 +191,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); } - $scope.mediaItems.push(media); + $scope.mediaItems.push(media); if ($scope.model.config.idType === "udi") { $scope.ids.push(media.udi); @@ -205,7 +214,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl }; - + $scope.sortableOptions = { disabled: !$scope.isMultiPicker, @@ -217,7 +226,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl // 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) { + angular.forEach($scope.mediaItems, function (value, key) { r.push($scope.model.config.idType === "udi" ? value.udi : value.id); }); $scope.ids = r; @@ -236,5 +245,5 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl }; init(); - + }); 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 063a726f44..5b02479813 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 @@ -69,9 +69,10 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en url: link.url, target: link.target } : null; - + var linkPicker = { currentTarget: target, + ignoreUserStartNodes: Object.toBoolean($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/relatedlinks/relatedlinks.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js index d54a17e15a..979baef0f7 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 @@ section: "content", treeAlias: "content", multiPicker: false, + ignoreUserStartNodes: Object.toBoolean($scope.model.config.ignoreUserStartNodes), idType: $scope.model.config.idType ? $scope.model.config.idType : "int", submit: function (model) { select(model.selection[0]); @@ -47,6 +48,7 @@ section: "content", treeAlias: "content", multiPicker: false, + ignoreUserStartNodes: Object.toBoolean($scope.model.config.ignoreUserStartNodes), idType: $scope.model.config.idType ? $scope.model.config.idType : "udi", submit: function (model) { select(model.selection[0]); 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 6314e0b31e..13515ca7a9 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 @@ -28,6 +28,14 @@ + +
+ +
+
+
× diff --git a/src/Umbraco.Web.UI/favicon.ico b/src/Umbraco.Web.UI/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c0749ddf7f1b68606b0672aa8d709779d6b0817a GIT binary patch literal 15406 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 literal 0 HcmV?d00001 diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index e648a4d140..05f7b6525c 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -99,8 +99,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, ignoreUserStartNodes: 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? @@ -110,7 +127,7 @@ namespace Umbraco.Web.Editors //TODO: This uses the internal UmbracoTreeSearcher, this instead should delgate to the ISearchableTree implementation for the type - return ExamineSearch(query, type, searchFrom); + return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes != null && ignoreUserStartNodes.Value); } /// @@ -534,6 +551,7 @@ namespace Umbraco.Web.Editors } } + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] public PagedResult GetPagedDescendants( int id, UmbracoEntityTypes type, @@ -542,6 +560,20 @@ namespace Umbraco.Web.Editors string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = "") + { + return GetPagedDescendants(id, type, pageNumber, pageSize, + ignoreUserStartNodes: 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); @@ -569,7 +601,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.GetPagedDescendants(objectType.Value, pageNumber - 1, pageSize, out totalRecords, SqlContext.Query().Where(x => x.Name.Contains(filter)), Ordering.By(orderBy, orderDirection), includeTrashed: false) @@ -611,9 +643,15 @@ namespace Umbraco.Web.Editors } } - public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] + public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormDataCollection queryStrings) { - return GetResultForAncestors(id, type, queryStrings); + return GetResultForAncestors(id, type, queryStrings, ignoreUserStartNodes: false); + } + + public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings, bool ignoreUserStartNodes) + { + return GetResultForAncestors(id, type, queryStrings, ignoreUserStartNodes); } /// @@ -622,10 +660,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 _, ignoreUserStartNodes, searchFrom); } private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes entityType) @@ -651,7 +690,7 @@ namespace Umbraco.Web.Editors } } - private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, FormDataCollection queryStrings = null) + private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, FormDataCollection queryStrings = null, bool ignoreUserStartNodes = false) { var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) @@ -660,35 +699,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(); } var culture = queryStrings?.GetValue("culture"); diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 3b7142397b..6710cf59f6 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -246,6 +246,7 @@ namespace Umbraco.Web.Editors /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(int id, + bool ignoreUserStartNodes, int pageNumber = 0, int pageSize = 0, string orderBy = "SortOrder", @@ -255,7 +256,7 @@ namespace Umbraco.Web.Editors { //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); @@ -311,6 +312,7 @@ namespace Umbraco.Web.Editors } /// + /// This method is obsolete, use the overload with ignoreUserStartNodes instead /// Returns the child media objects - using the entity GUID id /// /// @@ -321,8 +323,34 @@ namespace Umbraco.Web.Editors /// /// /// + [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, ignoreUserStartNodes: 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", @@ -333,12 +361,13 @@ namespace Umbraco.Web.Editors var entity = Services.EntityService.Get(id); if (entity != null) { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + return GetChildren(entity.Id, ignoreUserStartNodes, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); } 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 /// /// @@ -349,6 +378,7 @@ namespace Umbraco.Web.Editors /// /// /// + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] public PagedResult> GetChildren(Udi id, int pageNumber = 0, @@ -357,6 +387,31 @@ namespace Umbraco.Web.Editors Direction orderDirection = Direction.Ascending, bool orderBySystemField = true, string filter = "") + { + return GetChildren(id, ignoreUserStartNodes: 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) @@ -364,7 +419,7 @@ namespace Umbraco.Web.Editors var entity = Services.EntityService.Get(guidUdi.Guid); if (entity != null) { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); + return GetChildren(entity.Id, ignoreUserStartNodes, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); } } diff --git a/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs index 7879e2b42b..5653e3fe03 100644 --- a/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs @@ -10,5 +10,8 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("startNodeId", "Start node", "treepicker")] // + config in configuration editor ctor public Udi StartNodeId { get; set; } + + [ConfigurationField("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 e2b46b360d..136ab88204 100644 --- a/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/GridConfiguration.cs @@ -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("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.
Note: this applies to all editors in this grid editor except for the rich text editor, which has it's own option for that.")] + public bool IgnoreUserStartNodes { get; set; } } } diff --git a/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs b/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs index 4844e2f822..fa430e103b 100644 --- a/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/MediaPickerConfiguration.cs @@ -19,5 +19,8 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("startNodeId", "Start node", "mediapicker")] public Udi StartNodeId { get; set; } + + [ConfigurationField("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 b6333c3140..a0a2467b1c 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiNodePickerConfiguration.cs @@ -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("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 515512eff8..ec9439ceea 100644 --- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerConfiguration.cs @@ -9,5 +9,8 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("maxNumber", "Maximum number of items", "number")] public int MaxNumber { get; set; } + + [ConfigurationField("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 13bf269bcd..d99c2b17e0 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextConfiguration.cs @@ -14,5 +14,8 @@ namespace Umbraco.Web.PropertyEditors [ConfigurationField("hideLabel", "Hide Label", "boolean")] public bool HideLabel { get; set; } + + [ConfigurationField("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/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index 43db9ff0ba..7fb51a61eb 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -39,6 +39,7 @@ namespace Umbraco.Web.Search } /// + /// This method is obsolete, use the overload with ignoreUserStartNodes instead /// Searches for results based on the entity type /// /// @@ -50,11 +51,39 @@ namespace Umbraco.Web.Search /// /// /// + [Obsolete("This method is obsolete, use the overload with ignoreUserStartNodes instead", false)] public IEnumerable ExamineSearch( string query, UmbracoEntityTypes entityType, int pageSize, - long pageIndex, out long totalFound, string searchFrom = null) + long pageIndex, + out long totalFound, + string searchFrom = null) + { + return ExamineSearch(query, entityType, pageSize, pageIndex, out totalFound, ignoreUserStartNodes: 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( + string query, + UmbracoEntityTypes entityType, + int pageSize, + long pageIndex, + out long totalFound, + bool ignoreUserStartNodes, + string searchFrom = null) { var sb = new StringBuilder(); @@ -85,12 +114,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 +317,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 +340,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 0c02dd6e46..d01e9fffb4 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; } @@ -90,11 +90,11 @@ namespace Umbraco.Web.Trees internal TreeNode GetSingleTreeNodeWithAccessCheck(IEntitySlim e, string parentId, FormDataCollection queryStrings) { var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out var hasPathAccess); - if (entityIsAncestorOfStartNodes == false) + if (IgnoreUserStartNodes(queryStrings) == false && entityIsAncestorOfStartNodes == false) return null; var treeNode = GetSingleTreeNode(e, parentId, queryStrings); - if (hasPathAccess == false) + if (IgnoreUserStartNodes(queryStrings) == false && hasPathAccess == false) { treeNode.AdditionalData["noAccess"] = true; } @@ -134,7 +134,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) { Logger.Warn("User {Username} does not have access to node with id {Id}", Security.CurrentUser.Username, id); return nodes; @@ -255,8 +255,9 @@ namespace Umbraco.Web.Trees /// protected sealed override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) { + var ignoreUserStartNodes = queryStrings.GetValue(TreeQueryStringParameters.IgnoreUserStartNodes); //check if we're rendering the root - if (id == Constants.System.RootString && UserStartNodes.Contains(Constants.System.Root)) + if (id == Constants.System.RootString && UserStartNodes.Contains(Constants.System.Root) || ignoreUserStartNodes) { var altStartId = string.Empty; diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index 4acf807b77..2e409c2820 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -369,6 +369,16 @@ namespace Umbraco.Web.Trees return queryStrings.GetValue(TreeQueryStringParameters.Use) == "dialog"; } + /// + /// 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 466aff5a1f..0fcf5321e4 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 IgnoreUserStartNodes = "ignoreUserStartNodes"; //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 efee045890..67099dc58a 100644 --- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Models; using Umbraco.Web.Actions; using Umbraco.Core.Security; using System.Net; +using System.Web; namespace Umbraco.Web.WebApi.Filters { @@ -66,7 +67,7 @@ namespace Umbraco.Web.WebApi.Filters //not logged in throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized); } - + int nodeId; if (_nodeId.HasValue == false) { @@ -116,24 +117,29 @@ namespace Umbraco.Web.WebApi.Filters nodeId = _nodeId.Value; } - var permissionResult = ContentPermissionsHelper.CheckPermissions(nodeId, - Current.UmbracoContext.Security.CurrentUser, - Current.Services.UserService, - Current.Services.ContentService, - Current.Services.EntityService, - out var contentItem, - _permissionToCheck.HasValue ? new[] { _permissionToCheck.Value } : null); - - if (permissionResult == ContentPermissionsHelper.ContentAccess.NotFound) - throw new HttpResponseException(HttpStatusCode.NotFound); - - if (permissionResult == ContentPermissionsHelper.ContentAccess.Denied) - throw new HttpResponseException(actionContext.Request.CreateUserNoAccessResponse()); - - if (contentItem != null) + var queryStringCollection = HttpUtility.ParseQueryString(actionContext.Request.RequestUri.Query); + var ignoreUserStartNodes = bool.Parse(queryStringCollection["ignoreUserStartNodes"]); + if (ignoreUserStartNodes == false) { - //store the content item in request cache so it can be resolved in the controller without re-looking it up - actionContext.Request.Properties[typeof(IContent).ToString()] = contentItem; + var permissionResult = ContentPermissionsHelper.CheckPermissions(nodeId, + Current.UmbracoContext.Security.CurrentUser, + Current.Services.UserService, + Current.Services.ContentService, + Current.Services.EntityService, + out var contentItem, + _permissionToCheck.HasValue ? new[] {_permissionToCheck.Value} : null); + + if (permissionResult == ContentPermissionsHelper.ContentAccess.NotFound) + throw new HttpResponseException(HttpStatusCode.NotFound); + + if (permissionResult == ContentPermissionsHelper.ContentAccess.Denied) + throw new HttpResponseException(actionContext.Request.CreateUserNoAccessResponse()); + + if (contentItem != null) + { + //store the content item in request cache so it can be resolved in the controller without re-looking it up + actionContext.Request.Properties[typeof(IContent).ToString()] = contentItem; + } } base.OnActionExecuting(actionContext); diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs index 21dc60e6cc..f8b02f08ca 100644 --- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs @@ -3,12 +3,14 @@ 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.Core.Composing; using Umbraco.Core.Security; +using Umbraco.Web.Trees; namespace Umbraco.Web.WebApi.Filters { @@ -72,7 +74,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)