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 0000000000..c0749ddf7f
Binary files /dev/null and b/src/Umbraco.Web.UI/favicon.ico differ
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)