diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js
index 4ba4cf96bb..b81e62a66b 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreesearchbox.directive.js
@@ -12,6 +12,7 @@ function treeSearchBox(localizationService, searchService, $q) {
searchFromName: "@",
showSearch: "@",
section: "@",
+ ignoreUserStartNodes: "@",
hideSearchCallback: "=",
searchCallback: "="
},
@@ -34,6 +35,7 @@ function treeSearchBox(localizationService, searchService, $q) {
scope.showSearch = "false";
}
+
//used to cancel any request in progress if another one needs to take it's place
var canceler = null;
@@ -60,6 +62,11 @@ function treeSearchBox(localizationService, searchService, $q) {
searchArgs["searchFrom"] = scope.searchFromId;
}
+ //append ignoreUserStartNodes value if there is one
+ if (scope.ignoreUserStartNodes) {
+ searchArgs["ignoreUserStartNodes"] = scope.ignoreUserStartNodes;
+ }
+
searcher(searchArgs).then(function (data) {
scope.searchCallback(data);
//set back to null so it can be re-created
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js
index b695809eaa..77e587bcc6 100644
--- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js
@@ -1,782 +1,795 @@
-/**
- * @ngdoc service
- * @name umbraco.resources.contentResource
- * @description Handles all transactions of content data
- * from the angular application to the Umbraco database, using the Content WebApi controller
- *
- * all methods returns a resource promise async, so all operations won't complete untill .then() is completed.
- *
- * @requires $q
- * @requires $http
- * @requires umbDataFormatter
- * @requires umbRequestHelper
- *
- * ##usage
- * To use, simply inject the contentResource into any controller or service that needs it, and make
- * sure the umbraco.resources module is accesible - which it should be by default.
- *
- *
- * contentResource.getById(1234)
- * .then(function(data) {
- * $scope.content = data;
- * });
- *
- **/
-
-function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
-
- /** internal method process the saving of data and post processing the result */
- function saveContentItem(content, action, files, restApiUrl) {
- return umbRequestHelper.postSaveContent({
- restApiUrl: restApiUrl,
- content: content,
- action: action,
- files: files,
- dataFormatter: function (c, a) {
- return umbDataFormatter.formatContentPostData(c, a);
- }
- });
- }
-
- return {
-
-
- savePermissions: function (saveModel) {
- if (!saveModel) {
- throw "saveModel cannot be null";
- }
- if (!saveModel.contentId) {
- throw "saveModel.contentId cannot be null";
- }
- if (!saveModel.permissions) {
- throw "saveModel.permissions cannot be null";
- }
-
- return umbRequestHelper.resourcePromise(
- $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSaveUserGroupPermissions"),
- saveModel),
- 'Failed to save permissions');
- },
-
-
- getRecycleBin: function () {
- return umbRequestHelper.resourcePromise(
- $http.get(
- umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "GetRecycleBin")),
- 'Failed to retrieve data for content recycle bin');
- },
-
- /**
- * @ngdoc method
- * @name umbraco.resources.contentResource#sort
- * @methodOf umbraco.resources.contentResource
- *
- * @description
- * Sorts all children below a given parent node id, based on a collection of node-ids
- *
- * ##usage
- *
- * var ids = [123,34533,2334,23434];
- * contentResource.sort({ parentId: 1244, sortedIds: ids })
- * .then(function() {
- * $scope.complete = true;
- * });
- *
- * @param {Object} args arguments object
- * @param {Int} args.parentId the ID of the parent node
- * @param {Array} options.sortedIds array of node IDs as they should be sorted
- * @returns {Promise} resourcePromise object.
- *
- */
- sort: function (args) {
- if (!args) {
- throw "args cannot be null";
- }
- if (!args.parentId) {
- throw "args.parentId cannot be null";
- }
- if (!args.sortedIds) {
- throw "args.sortedIds cannot be null";
- }
-
- return umbRequestHelper.resourcePromise(
- $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"),
- {
- parentId: args.parentId,
- idSortOrder: args.sortedIds
- }),
- 'Failed to sort content');
- },
-
- /**
- * @ngdoc method
- * @name umbraco.resources.contentResource#move
- * @methodOf umbraco.resources.contentResource
- *
- * @description
- * Moves a node underneath a new parentId
- *
- * ##usage
- *
- * contentResource.move({ parentId: 1244, id: 123 })
- * .then(function() {
- * alert("node was moved");
- * }, function(err){
- * alert("node didnt move:" + err.data.Message);
- * });
- *
- * @param {Object} args arguments object
- * @param {Int} args.idd the ID of the node to move
- * @param {Int} args.parentId the ID of the parent node to move to
- * @returns {Promise} resourcePromise object.
- *
- */
- move: function (args) {
- if (!args) {
- throw "args cannot be null";
- }
- if (!args.parentId) {
- throw "args.parentId cannot be null";
- }
- if (!args.id) {
- throw "args.id cannot be null";
- }
-
- return umbRequestHelper.resourcePromise(
- $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"),
- {
- parentId: args.parentId,
- id: args.id
- }),
- 'Failed to move content');
- },
-
- /**
- * @ngdoc method
- * @name umbraco.resources.contentResource#copy
- * @methodOf umbraco.resources.contentResource
- *
- * @description
- * Copies a node underneath a new parentId
- *
- * ##usage
- *
- * contentResource.copy({ parentId: 1244, id: 123 })
- * .then(function() {
- * alert("node was copied");
- * }, function(err){
- * alert("node wasnt copy:" + err.data.Message);
- * });
- *
- * @param {Object} args arguments object
- * @param {Int} args.id the ID of the node to copy
- * @param {Int} args.parentId the ID of the parent node to copy to
- * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api
- * @returns {Promise} resourcePromise object.
- *
- */
- copy: function (args) {
- if (!args) {
- throw "args cannot be null";
- }
- if (!args.parentId) {
- throw "args.parentId cannot be null";
- }
- if (!args.id) {
- throw "args.id cannot be null";
- }
-
- return umbRequestHelper.resourcePromise(
- $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"),
- args),
- 'Failed to copy content');
- },
-
- /**
- * @ngdoc method
- * @name umbraco.resources.contentResource#unPublish
- * @methodOf umbraco.resources.contentResource
- *
- * @description
- * Unpublishes a content item with a given Id
- *
- * ##usage
- *
- * contentResource.unPublish(1234)
- * .then(function() {
- * alert("node was unpulished");
- * }, function(err){
- * alert("node wasnt unpublished:" + err.data.Message);
- * });
- *
- * @param {Int} id the ID of the node to unpublish
- * @returns {Promise} resourcePromise object.
- *
- */
- unPublish: function (id) {
- if (!id) {
- throw "id cannot be null";
- }
-
- return umbRequestHelper.resourcePromise(
- $http.post(
- umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "PostUnPublish",
- [{ id: id }])),
- 'Failed to publish content with id ' + id);
- },
- /**
- * @ngdoc method
- * @name umbraco.resources.contentResource#emptyRecycleBin
- * @methodOf umbraco.resources.contentResource
- *
- * @description
- * Empties the content recycle bin
- *
- * ##usage
- *
- * contentResource.emptyRecycleBin()
- * .then(function() {
- * alert('its empty!');
- * });
- *
- *
- * @returns {Promise} resourcePromise object.
- *
- */
- emptyRecycleBin: function () {
- return umbRequestHelper.resourcePromise(
- $http.post(
- umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "EmptyRecycleBin")),
- 'Failed to empty the recycle bin');
- },
-
- /**
- * @ngdoc method
- * @name umbraco.resources.contentResource#deleteById
- * @methodOf umbraco.resources.contentResource
- *
- * @description
- * Deletes a content item with a given id
- *
- * ##usage
- *
- * contentResource.deleteById(1234)
- * .then(function() {
- * alert('its gone!');
- * });
- *
- *
- * @param {Int} id id of content item to delete
- * @returns {Promise} resourcePromise object.
- *
- */
- deleteById: function (id) {
- return umbRequestHelper.resourcePromise(
- $http.post(
- umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "DeleteById",
- [{ id: id }])),
- 'Failed to delete item ' + id);
- },
-
- deleteBlueprint: function (id) {
- return umbRequestHelper.resourcePromise(
- $http.post(
- umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "DeleteBlueprint",
- [{ id: id }])),
- 'Failed to delete blueprint ' + id);
- },
-
- /**
- * @ngdoc method
- * @name umbraco.resources.contentResource#getById
- * @methodOf umbraco.resources.contentResource
- *
- * @description
- * Gets a content item with a given id
- *
- * ##usage
- *
- * contentResource.getById(1234)
- * .then(function(content) {
- * var myDoc = content;
- * alert('its here!');
- * });
- *
- *
- * @param {Int} id id of content item to return
- * @returns {Promise} resourcePromise object containing the content item.
- *
- */
- getById: function (id) {
- return umbRequestHelper.resourcePromise(
- $http.get(
- umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "GetById",
- [{ id: id }])),
- 'Failed to retrieve data for content id ' + id);
- },
-
- getBlueprintById: function (id) {
- return umbRequestHelper.resourcePromise(
- $http.get(
- umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "GetBlueprintById",
- [{ id: id }])),
- 'Failed to retrieve data for content id ' + id);
- },
-
- getNotifySettingsById: function (id) {
- return umbRequestHelper.resourcePromise(
- $http.get(
- umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "GetNotificationOptions",
- [{ contentId: id }])),
- 'Failed to retrieve data for content id ' + id);
- },
-
- setNotifySettingsById: function (id, options) {
- if (!id) {
- throw "contentId cannot be null";
- }
- return umbRequestHelper.resourcePromise(
- $http.post(
- umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "PostNotificationOptions",
- { contentId: id, notifyOptions: options })),
- 'Failed to set notify settings for content id ' + id);
- },
-
- /**
- * @ngdoc method
- * @name umbraco.resources.contentResource#getByIds
- * @methodOf umbraco.resources.contentResource
- *
- * @description
- * Gets an array of content items, given a collection of ids
- *
- * ##usage
- *
- * contentResource.getByIds( [1234,2526,28262])
- * .then(function(contentArray) {
- * var myDoc = contentArray;
- * alert('they are here!');
- * });
- *
- *
- * @param {Array} ids ids of content items to return as an array
- * @returns {Promise} resourcePromise object containing the content items array.
- *
- */
- getByIds: function (ids) {
-
- var idQuery = "";
- _.each(ids, function (item) {
- idQuery += "ids=" + item + "&";
- });
-
- return umbRequestHelper.resourcePromise(
- $http.get(
- umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "GetByIds",
- idQuery)),
- 'Failed to retrieve data for content with multiple ids');
- },
-
-
- /**
- * @ngdoc method
- * @name umbraco.resources.contentResource#getScaffold
- * @methodOf umbraco.resources.contentResource
- *
- * @description
- * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias.
- *
- * - Parent Id must be provided so umbraco knows where to store the content
- * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold
- *
- * The scaffold is used to build editors for content that has not yet been populated with data.
- *
- * ##usage
- *
- * contentResource.getScaffold(1234, 'homepage')
- * .then(function(scaffold) {
- * var myDoc = scaffold;
- * myDoc.name = "My new document";
- *
- * contentResource.publish(myDoc, true)
- * .then(function(content){
- * alert("Retrieved, updated and published again");
- * });
- * });
- *
- *
- * @param {Int} parentId id of content item to return
- * @param {String} alias contenttype alias to base the scaffold on
- * @returns {Promise} resourcePromise object containing the content scaffold.
- *
- */
- getScaffold: function (parentId, alias) {
-
- return umbRequestHelper.resourcePromise(
- $http.get(
- umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "GetEmpty",
- [{ contentTypeAlias: alias }, { parentId: parentId }])),
- 'Failed to retrieve data for empty content item type ' + alias);
- },
-
- getBlueprintScaffold: function (parentId, blueprintId) {
-
- return umbRequestHelper.resourcePromise(
- $http.get(
- umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "GetEmpty",
- [{ blueprintId: blueprintId }, { parentId: parentId}])),
- 'Failed to retrieve blueprint for id ' + blueprintId);
- },
-
- /**
- * @ngdoc method
- * @name umbraco.resources.contentResource#getNiceUrl
- * @methodOf umbraco.resources.contentResource
- *
- * @description
- * Returns a url, given a node ID
- *
- * ##usage
- *
- * contentResource.getNiceUrl(id)
- * .then(function(url) {
- * alert('its here!');
- * });
- *
- *
- * @param {Int} id Id of node to return the public url to
- * @returns {Promise} resourcePromise object containing the url.
- *
- */
- getNiceUrl: function (id) {
- return umbRequestHelper.resourcePromise(
- $http.get(
- umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "GetNiceUrl", [{ id: id }])),
- 'Failed to retrieve url for id:' + id);
- },
-
- /**
- * @ngdoc method
- * @name umbraco.resources.contentResource#getChildren
- * @methodOf umbraco.resources.contentResource
- *
- * @description
- * Gets children of a content item with a given id
- *
- * ##usage
- *
- * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
- * .then(function(contentArray) {
- * var children = contentArray;
- * alert('they are here!');
- * });
- *
- *
- * @param {Int} parentid id of content item to return children of
- * @param {Object} options optional options object
- * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0
- * @param {Int} options.pageNumber if paging data, current page index, default = 0
- * @param {String} options.filter if provided, query will only return those with names matching the filter
- * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending`
- * @param {String} options.orderBy property to order items by, default: `SortOrder`
- * @returns {Promise} resourcePromise object containing an array of content items.
- *
- */
- getChildren: function (parentId, options) {
-
- var defaults = {
- includeProperties: [],
- pageSize: 0,
- pageNumber: 0,
- filter: '',
- orderDirection: "Ascending",
- orderBy: "SortOrder",
- orderBySystemField: true
- };
- if (options === undefined) {
- options = {};
- }
- //overwrite the defaults if there are any specified
- angular.extend(defaults, options);
- //now copy back to the options we will use
- options = defaults;
- //change asc/desct
- if (options.orderDirection === "asc") {
- options.orderDirection = "Ascending";
- }
- else if (options.orderDirection === "desc") {
- options.orderDirection = "Descending";
- }
-
- //converts the value to a js bool
- function toBool(v) {
- if (angular.isNumber(v)) {
- return v > 0;
- }
- if (angular.isString(v)) {
- return v === "true";
- }
- if (typeof v === "boolean") {
- return v;
- }
- return false;
- }
-
- return umbRequestHelper.resourcePromise(
- $http.get(
- umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "GetChildren",
- {
- id: parentId,
- includeProperties: _.pluck(options.includeProperties, 'alias').join(","),
- pageNumber: options.pageNumber,
- pageSize: options.pageSize,
- orderBy: options.orderBy,
- orderDirection: options.orderDirection,
- orderBySystemField: toBool(options.orderBySystemField),
- filter: options.filter
- })),
- 'Failed to retrieve children for content item ' + parentId);
- },
-
- /**
- * @ngdoc method
- * @name umbraco.resources.contentResource#hasPermission
- * @methodOf umbraco.resources.contentResource
- *
- * @description
- * Returns true/false given a permission char to check against a nodeID
- * for the current user
- *
- * ##usage
- *
- * contentResource.hasPermission('p',1234)
- * .then(function() {
- * alert('You are allowed to publish this item');
- * });
- *
- *
- * @param {String} permission char representing the permission to check
- * @param {Int} id id of content item to delete
- * @returns {Promise} resourcePromise object.
- *
- */
- checkPermission: function (permission, id) {
- return umbRequestHelper.resourcePromise(
- $http.get(
- umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "HasPermission",
- [{ permissionToCheck: permission }, { nodeId: id }])),
- 'Failed to check permission for item ' + id);
- },
-
- getDetailedPermissions: function (contentId) {
- return umbRequestHelper.resourcePromise(
- $http.get(
- umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "GetDetailedPermissions", { contentId: contentId })),
- 'Failed to retrieve permissions for content item ' + contentId);
- },
-
- getPermissions: function (nodeIds) {
- return umbRequestHelper.resourcePromise(
- $http.post(
- umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "GetPermissions"),
- nodeIds),
- 'Failed to get permissions');
- },
-
- /**
- * @ngdoc method
- * @name umbraco.resources.contentResource#save
- * @methodOf umbraco.resources.contentResource
- *
- * @description
- * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation
- * if the content item needs to have files attached, they must be provided as the files param and passed separately
- *
- *
- * ##usage
- *
- * contentResource.getById(1234)
- * .then(function(content) {
- * content.name = "I want a new name!";
- * contentResource.save(content, false)
- * .then(function(content){
- * alert("Retrieved, updated and saved again");
- * });
- * });
- *
- *
- * @param {Object} content The content item object with changes applied
- * @param {Bool} isNew set to true to create a new item or to update an existing
- * @param {Array} files collection of files for the document
- * @returns {Promise} resourcePromise object containing the saved content item.
- *
- */
- save: function (content, isNew, files) {
- var endpoint = umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "PostSave");
- return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint);
- },
-
- saveBlueprint: function (content, isNew, files) {
- var endpoint = umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "PostSaveBlueprint");
- return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint);
- },
-
- /**
- * @ngdoc method
- * @name umbraco.resources.contentResource#publish
- * @methodOf umbraco.resources.contentResource
- *
- * @description
- * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation
- * if the content item needs to have files attached, they must be provided as the files param and passed separately
- *
- *
- * ##usage
- *
- * contentResource.getById(1234)
- * .then(function(content) {
- * content.name = "I want a new name, and be published!";
- * contentResource.publish(content, false)
- * .then(function(content){
- * alert("Retrieved, updated and published again");
- * });
- * });
- *
- *
- * @param {Object} content The content item object with changes applied
- * @param {Bool} isNew set to true to create a new item or to update an existing
- * @param {Array} files collection of files for the document
- * @returns {Promise} resourcePromise object containing the saved content item.
- *
- */
- publish: function (content, isNew, files) {
- var endpoint = umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "PostSave");
- return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint);
- },
-
-
- /**
- * @ngdoc method
- * @name umbraco.resources.contentResource#sendToPublish
- * @methodOf umbraco.resources.contentResource
- *
- * @description
- * Saves changes made to a content item, and notifies any subscribers about a pending publication
- *
- * ##usage
- *
- * contentResource.getById(1234)
- * .then(function(content) {
- * content.name = "I want a new name, and be published!";
- * contentResource.sendToPublish(content, false)
- * .then(function(content){
- * alert("Retrieved, updated and notication send off");
- * });
- * });
- *
- *
- * @param {Object} content The content item object with changes applied
- * @param {Bool} isNew set to true to create a new item or to update an existing
- * @param {Array} files collection of files for the document
- * @returns {Promise} resourcePromise object containing the saved content item.
- *
- */
- sendToPublish: function (content, isNew, files) {
- var endpoint = umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "PostSave");
- return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint);
- },
-
- /**
- * @ngdoc method
- * @name umbraco.resources.contentResource#publishByid
- * @methodOf umbraco.resources.contentResource
- *
- * @description
- * Publishes a content item with a given ID
- *
- * ##usage
- *
- * contentResource.publishById(1234)
- * .then(function(content) {
- * alert("published");
- * });
- *
- *
- * @param {Int} id The ID of the conten to publish
- * @returns {Promise} resourcePromise object containing the published content item.
- *
- */
- publishById: function (id) {
-
- if (!id) {
- throw "id cannot be null";
- }
-
- return umbRequestHelper.resourcePromise(
- $http.post(
- umbRequestHelper.getApiUrl(
- "contentApiBaseUrl",
- "PostPublishById",
- [{ id: id }])),
- 'Failed to publish content with id ' + id);
-
- },
-
- createBlueprintFromContent: function (contentId, name) {
- return umbRequestHelper.resourcePromise(
- $http.post(
- umbRequestHelper.getApiUrl("contentApiBaseUrl", "CreateBlueprintFromContent", {
- contentId: contentId, name: name
- })
- ),
- "Failed to create blueprint from content with id " + contentId
- );
- }
-
-
- };
-}
-
-angular.module('umbraco.resources').factory('contentResource', contentResource);
+/**
+ * @ngdoc service
+ * @name umbraco.resources.contentResource
+ * @description Handles all transactions of content data
+ * from the angular application to the Umbraco database, using the Content WebApi controller
+ *
+ * all methods returns a resource promise async, so all operations won't complete untill .then() is completed.
+ *
+ * @requires $q
+ * @requires $http
+ * @requires umbDataFormatter
+ * @requires umbRequestHelper
+ *
+ * ##usage
+ * To use, simply inject the contentResource into any controller or service that needs it, and make
+ * sure the umbraco.resources module is accesible - which it should be by default.
+ *
+ *
+ * contentResource.getById(1234)
+ * .then(function(data) {
+ * $scope.content = data;
+ * });
+ *
+ **/
+
+function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
+
+ /** internal method process the saving of data and post processing the result */
+ function saveContentItem(content, action, files, restApiUrl) {
+ return umbRequestHelper.postSaveContent({
+ restApiUrl: restApiUrl,
+ content: content,
+ action: action,
+ files: files,
+ dataFormatter: function (c, a) {
+ return umbDataFormatter.formatContentPostData(c, a);
+ }
+ });
+ }
+
+ return {
+
+
+ savePermissions: function (saveModel) {
+ if (!saveModel) {
+ throw "saveModel cannot be null";
+ }
+ if (!saveModel.contentId) {
+ throw "saveModel.contentId cannot be null";
+ }
+ if (!saveModel.permissions) {
+ throw "saveModel.permissions cannot be null";
+ }
+
+ return umbRequestHelper.resourcePromise(
+ $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSaveUserGroupPermissions"),
+ saveModel),
+ 'Failed to save permissions');
+ },
+
+
+ getRecycleBin: function () {
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "GetRecycleBin")),
+ 'Failed to retrieve data for content recycle bin');
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#sort
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Sorts all children below a given parent node id, based on a collection of node-ids
+ *
+ * ##usage
+ *
+ * var ids = [123,34533,2334,23434];
+ * contentResource.sort({ parentId: 1244, sortedIds: ids })
+ * .then(function() {
+ * $scope.complete = true;
+ * });
+ *
+ * @param {Object} args arguments object
+ * @param {Int} args.parentId the ID of the parent node
+ * @param {Array} options.sortedIds array of node IDs as they should be sorted
+ * @returns {Promise} resourcePromise object.
+ *
+ */
+ sort: function (args) {
+ if (!args) {
+ throw "args cannot be null";
+ }
+ if (!args.parentId) {
+ throw "args.parentId cannot be null";
+ }
+ if (!args.sortedIds) {
+ throw "args.sortedIds cannot be null";
+ }
+
+ return umbRequestHelper.resourcePromise(
+ $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSort"),
+ {
+ parentId: args.parentId,
+ idSortOrder: args.sortedIds
+ }),
+ 'Failed to sort content');
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#move
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Moves a node underneath a new parentId
+ *
+ * ##usage
+ *
+ * contentResource.move({ parentId: 1244, id: 123 })
+ * .then(function() {
+ * alert("node was moved");
+ * }, function(err){
+ * alert("node didnt move:" + err.data.Message);
+ * });
+ *
+ * @param {Object} args arguments object
+ * @param {Int} args.idd the ID of the node to move
+ * @param {Int} args.parentId the ID of the parent node to move to
+ * @returns {Promise} resourcePromise object.
+ *
+ */
+ move: function (args) {
+ if (!args) {
+ throw "args cannot be null";
+ }
+ if (!args.parentId) {
+ throw "args.parentId cannot be null";
+ }
+ if (!args.id) {
+ throw "args.id cannot be null";
+ }
+
+ return umbRequestHelper.resourcePromise(
+ $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostMove"),
+ {
+ parentId: args.parentId,
+ id: args.id
+ }),
+ 'Failed to move content');
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#copy
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Copies a node underneath a new parentId
+ *
+ * ##usage
+ *
+ * contentResource.copy({ parentId: 1244, id: 123 })
+ * .then(function() {
+ * alert("node was copied");
+ * }, function(err){
+ * alert("node wasnt copy:" + err.data.Message);
+ * });
+ *
+ * @param {Object} args arguments object
+ * @param {Int} args.id the ID of the node to copy
+ * @param {Int} args.parentId the ID of the parent node to copy to
+ * @param {Boolean} args.relateToOriginal if true, relates the copy to the original through the relation api
+ * @returns {Promise} resourcePromise object.
+ *
+ */
+ copy: function (args) {
+ if (!args) {
+ throw "args cannot be null";
+ }
+ if (!args.parentId) {
+ throw "args.parentId cannot be null";
+ }
+ if (!args.id) {
+ throw "args.id cannot be null";
+ }
+
+ return umbRequestHelper.resourcePromise(
+ $http.post(umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostCopy"),
+ args),
+ 'Failed to copy content');
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#unPublish
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Unpublishes a content item with a given Id
+ *
+ * ##usage
+ *
+ * contentResource.unPublish(1234)
+ * .then(function() {
+ * alert("node was unpulished");
+ * }, function(err){
+ * alert("node wasnt unpublished:" + err.data.Message);
+ * });
+ *
+ * @param {Int} id the ID of the node to unpublish
+ * @returns {Promise} resourcePromise object.
+ *
+ */
+ unPublish: function (id) {
+ if (!id) {
+ throw "id cannot be null";
+ }
+
+ return umbRequestHelper.resourcePromise(
+ $http.post(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "PostUnPublish",
+ [{ id: id }])),
+ 'Failed to publish content with id ' + id);
+ },
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#emptyRecycleBin
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Empties the content recycle bin
+ *
+ * ##usage
+ *
+ * contentResource.emptyRecycleBin()
+ * .then(function() {
+ * alert('its empty!');
+ * });
+ *
+ *
+ * @returns {Promise} resourcePromise object.
+ *
+ */
+ emptyRecycleBin: function () {
+ return umbRequestHelper.resourcePromise(
+ $http.post(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "EmptyRecycleBin")),
+ 'Failed to empty the recycle bin');
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#deleteById
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Deletes a content item with a given id
+ *
+ * ##usage
+ *
+ * contentResource.deleteById(1234)
+ * .then(function() {
+ * alert('its gone!');
+ * });
+ *
+ *
+ * @param {Int} id id of content item to delete
+ * @returns {Promise} resourcePromise object.
+ *
+ */
+ deleteById: function (id) {
+ return umbRequestHelper.resourcePromise(
+ $http.post(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "DeleteById",
+ [{ id: id }])),
+ 'Failed to delete item ' + id);
+ },
+
+ deleteBlueprint: function (id) {
+ return umbRequestHelper.resourcePromise(
+ $http.post(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "DeleteBlueprint",
+ [{ id: id }])),
+ 'Failed to delete blueprint ' + id);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#getById
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Gets a content item with a given id
+ *
+ * ##usage
+ *
+ * contentResource.getById(1234)
+ * .then(function(content) {
+ * var myDoc = content;
+ * alert('its here!');
+ * });
+ *
+ *
+ * @param {Int} id id of content item to return
+ * @param {Object} options optional options object
+ * @param {Bool} options.ignoreUserStartNodes set to true to allow a user to choose nodes that they normally don't have access to
+ * @returns {Promise} resourcePromise object containing the content item.
+ *
+ */
+ getById: function (id, options) {
+ var defaults = {
+ ignoreUserStartNodes: false
+ };
+ if (options === undefined) {
+ options = {};
+ }
+ //overwrite the defaults if there are any specified
+ angular.extend(defaults, options);
+ //now copy back to the options we will use
+ options = defaults;
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "GetById",
+ [{ id: id }, { ignoreUserStartNodes: options.ignoreUserStartNodes }])),
+ 'Failed to retrieve data for content id ' + id);
+ },
+
+ getBlueprintById: function (id) {
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "GetBlueprintById",
+ [{ id: id }])),
+ 'Failed to retrieve data for content id ' + id);
+ },
+
+ getNotifySettingsById: function (id) {
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "GetNotificationOptions",
+ [{ contentId: id }])),
+ 'Failed to retrieve data for content id ' + id);
+ },
+
+ setNotifySettingsById: function (id, options) {
+ if (!id) {
+ throw "contentId cannot be null";
+ }
+ return umbRequestHelper.resourcePromise(
+ $http.post(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "PostNotificationOptions",
+ { contentId: id, notifyOptions: options })),
+ 'Failed to set notify settings for content id ' + id);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#getByIds
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Gets an array of content items, given a collection of ids
+ *
+ * ##usage
+ *
+ * contentResource.getByIds( [1234,2526,28262])
+ * .then(function(contentArray) {
+ * var myDoc = contentArray;
+ * alert('they are here!');
+ * });
+ *
+ *
+ * @param {Array} ids ids of content items to return as an array
+ * @returns {Promise} resourcePromise object containing the content items array.
+ *
+ */
+ getByIds: function (ids) {
+
+ var idQuery = "";
+ _.each(ids, function (item) {
+ idQuery += "ids=" + item + "&";
+ });
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "GetByIds",
+ idQuery)),
+ 'Failed to retrieve data for content with multiple ids');
+ },
+
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#getScaffold
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Returns a scaffold of an empty content item, given the id of the content item to place it underneath and the content type alias.
+ *
+ * - Parent Id must be provided so umbraco knows where to store the content
+ * - Content Type alias must be provided so umbraco knows which properties to put on the content scaffold
+ *
+ * The scaffold is used to build editors for content that has not yet been populated with data.
+ *
+ * ##usage
+ *
+ * contentResource.getScaffold(1234, 'homepage')
+ * .then(function(scaffold) {
+ * var myDoc = scaffold;
+ * myDoc.name = "My new document";
+ *
+ * contentResource.publish(myDoc, true)
+ * .then(function(content){
+ * alert("Retrieved, updated and published again");
+ * });
+ * });
+ *
+ *
+ * @param {Int} parentId id of content item to return
+ * @param {String} alias contenttype alias to base the scaffold on
+ * @returns {Promise} resourcePromise object containing the content scaffold.
+ *
+ */
+ getScaffold: function (parentId, alias) {
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "GetEmpty",
+ [{ contentTypeAlias: alias }, { parentId: parentId }])),
+ 'Failed to retrieve data for empty content item type ' + alias);
+ },
+
+ getBlueprintScaffold: function (parentId, blueprintId) {
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "GetEmpty",
+ [{ blueprintId: blueprintId }, { parentId: parentId}])),
+ 'Failed to retrieve blueprint for id ' + blueprintId);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#getNiceUrl
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Returns a url, given a node ID
+ *
+ * ##usage
+ *
+ * contentResource.getNiceUrl(id)
+ * .then(function(url) {
+ * alert('its here!');
+ * });
+ *
+ *
+ * @param {Int} id Id of node to return the public url to
+ * @returns {Promise} resourcePromise object containing the url.
+ *
+ */
+ getNiceUrl: function (id) {
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "GetNiceUrl", [{ id: id }])),
+ 'Failed to retrieve url for id:' + id);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#getChildren
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Gets children of a content item with a given id
+ *
+ * ##usage
+ *
+ * contentResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
+ * .then(function(contentArray) {
+ * var children = contentArray;
+ * alert('they are here!');
+ * });
+ *
+ *
+ * @param {Int} parentid id of content item to return children of
+ * @param {Object} options optional options object
+ * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0
+ * @param {Int} options.pageNumber if paging data, current page index, default = 0
+ * @param {String} options.filter if provided, query will only return those with names matching the filter
+ * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending`
+ * @param {String} options.orderBy property to order items by, default: `SortOrder`
+ * @returns {Promise} resourcePromise object containing an array of content items.
+ *
+ */
+ getChildren: function (parentId, options) {
+
+ var defaults = {
+ includeProperties: [],
+ pageSize: 0,
+ pageNumber: 0,
+ filter: '',
+ orderDirection: "Ascending",
+ orderBy: "SortOrder",
+ orderBySystemField: true
+ };
+ if (options === undefined) {
+ options = {};
+ }
+ //overwrite the defaults if there are any specified
+ angular.extend(defaults, options);
+ //now copy back to the options we will use
+ options = defaults;
+ //change asc/desct
+ if (options.orderDirection === "asc") {
+ options.orderDirection = "Ascending";
+ }
+ else if (options.orderDirection === "desc") {
+ options.orderDirection = "Descending";
+ }
+
+ //converts the value to a js bool
+ function toBool(v) {
+ if (angular.isNumber(v)) {
+ return v > 0;
+ }
+ if (angular.isString(v)) {
+ return v === "true";
+ }
+ if (typeof v === "boolean") {
+ return v;
+ }
+ return false;
+ }
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "GetChildren",
+ {
+ id: parentId,
+ includeProperties: _.pluck(options.includeProperties, 'alias').join(","),
+ pageNumber: options.pageNumber,
+ pageSize: options.pageSize,
+ orderBy: options.orderBy,
+ orderDirection: options.orderDirection,
+ orderBySystemField: toBool(options.orderBySystemField),
+ filter: options.filter
+ })),
+ 'Failed to retrieve children for content item ' + parentId);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#hasPermission
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Returns true/false given a permission char to check against a nodeID
+ * for the current user
+ *
+ * ##usage
+ *
+ * contentResource.hasPermission('p',1234)
+ * .then(function() {
+ * alert('You are allowed to publish this item');
+ * });
+ *
+ *
+ * @param {String} permission char representing the permission to check
+ * @param {Int} id id of content item to delete
+ * @returns {Promise} resourcePromise object.
+ *
+ */
+ checkPermission: function (permission, id) {
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "HasPermission",
+ [{ permissionToCheck: permission }, { nodeId: id }])),
+ 'Failed to check permission for item ' + id);
+ },
+
+ getDetailedPermissions: function (contentId) {
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "GetDetailedPermissions", { contentId: contentId })),
+ 'Failed to retrieve permissions for content item ' + contentId);
+ },
+
+ getPermissions: function (nodeIds) {
+ return umbRequestHelper.resourcePromise(
+ $http.post(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "GetPermissions"),
+ nodeIds),
+ 'Failed to get permissions');
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#save
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Saves changes made to a content item to its current version, if the content item is new, the isNew paramater must be passed to force creation
+ * if the content item needs to have files attached, they must be provided as the files param and passed separately
+ *
+ *
+ * ##usage
+ *
+ * contentResource.getById(1234)
+ * .then(function(content) {
+ * content.name = "I want a new name!";
+ * contentResource.save(content, false)
+ * .then(function(content){
+ * alert("Retrieved, updated and saved again");
+ * });
+ * });
+ *
+ *
+ * @param {Object} content The content item object with changes applied
+ * @param {Bool} isNew set to true to create a new item or to update an existing
+ * @param {Array} files collection of files for the document
+ * @returns {Promise} resourcePromise object containing the saved content item.
+ *
+ */
+ save: function (content, isNew, files) {
+ var endpoint = umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "PostSave");
+ return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint);
+ },
+
+ saveBlueprint: function (content, isNew, files) {
+ var endpoint = umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "PostSaveBlueprint");
+ return saveContentItem(content, "save" + (isNew ? "New" : ""), files, endpoint);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#publish
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Saves and publishes changes made to a content item to a new version, if the content item is new, the isNew paramater must be passed to force creation
+ * if the content item needs to have files attached, they must be provided as the files param and passed separately
+ *
+ *
+ * ##usage
+ *
+ * contentResource.getById(1234)
+ * .then(function(content) {
+ * content.name = "I want a new name, and be published!";
+ * contentResource.publish(content, false)
+ * .then(function(content){
+ * alert("Retrieved, updated and published again");
+ * });
+ * });
+ *
+ *
+ * @param {Object} content The content item object with changes applied
+ * @param {Bool} isNew set to true to create a new item or to update an existing
+ * @param {Array} files collection of files for the document
+ * @returns {Promise} resourcePromise object containing the saved content item.
+ *
+ */
+ publish: function (content, isNew, files) {
+ var endpoint = umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "PostSave");
+ return saveContentItem(content, "publish" + (isNew ? "New" : ""), files, endpoint);
+ },
+
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#sendToPublish
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Saves changes made to a content item, and notifies any subscribers about a pending publication
+ *
+ * ##usage
+ *
+ * contentResource.getById(1234)
+ * .then(function(content) {
+ * content.name = "I want a new name, and be published!";
+ * contentResource.sendToPublish(content, false)
+ * .then(function(content){
+ * alert("Retrieved, updated and notication send off");
+ * });
+ * });
+ *
+ *
+ * @param {Object} content The content item object with changes applied
+ * @param {Bool} isNew set to true to create a new item or to update an existing
+ * @param {Array} files collection of files for the document
+ * @returns {Promise} resourcePromise object containing the saved content item.
+ *
+ */
+ sendToPublish: function (content, isNew, files) {
+ var endpoint = umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "PostSave");
+ return saveContentItem(content, "sendPublish" + (isNew ? "New" : ""), files, endpoint);
+ },
+
+ /**
+ * @ngdoc method
+ * @name umbraco.resources.contentResource#publishByid
+ * @methodOf umbraco.resources.contentResource
+ *
+ * @description
+ * Publishes a content item with a given ID
+ *
+ * ##usage
+ *
+ * contentResource.publishById(1234)
+ * .then(function(content) {
+ * alert("published");
+ * });
+ *
+ *
+ * @param {Int} id The ID of the conten to publish
+ * @returns {Promise} resourcePromise object containing the published content item.
+ *
+ */
+ publishById: function (id) {
+
+ if (!id) {
+ throw "id cannot be null";
+ }
+
+ return umbRequestHelper.resourcePromise(
+ $http.post(
+ umbRequestHelper.getApiUrl(
+ "contentApiBaseUrl",
+ "PostPublishById",
+ [{ id: id }])),
+ 'Failed to publish content with id ' + id);
+
+ },
+
+ createBlueprintFromContent: function (contentId, name) {
+ return umbRequestHelper.resourcePromise(
+ $http.post(
+ umbRequestHelper.getApiUrl("contentApiBaseUrl", "CreateBlueprintFromContent", {
+ contentId: contentId, name: name
+ })
+ ),
+ "Failed to create blueprint from content with id " + contentId
+ );
+ }
+
+
+ };
+}
+
+angular.module('umbraco.resources').factory('contentResource', contentResource);
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js
index 72f8ad5539..4875491dc6 100644
--- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js
@@ -292,14 +292,29 @@ function entityResource($q, $http, umbRequestHelper) {
* @returns {Promise} resourcePromise object containing the entity.
*
*/
- getAncestors: function (id, type) {
+ getAncestors: function (id, type, options) {
+ var defaults = {
+ ignoreUserStartNodes: false
+ };
+ if (options === undefined) {
+ options = {};
+ }
+ //overwrite the defaults if there are any specified
+ angular.extend(defaults, options);
+ //now copy back to the options we will use
+ options = defaults;
+
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"entityApiBaseUrl",
"GetAncestors",
- [{id: id}, {type: type}])),
- 'Failed to retrieve ancestor data for id ' + id);
+ [
+ { id: id },
+ { type: type },
+ { ignoreUserStartNodes: options.ignoreUserStartNodes }
+ ])),
+ 'Failed to retrieve ancestor data for id ' + id);
},
/**
@@ -431,7 +446,8 @@ function entityResource($q, $http, umbRequestHelper) {
pageNumber: 1,
filter: '',
orderDirection: "Ascending",
- orderBy: "SortOrder"
+ orderBy: "SortOrder",
+ ignoreUserStartNodes: false
};
if (options === undefined) {
options = {};
@@ -460,7 +476,8 @@ function entityResource($q, $http, umbRequestHelper) {
pageSize: options.pageSize,
orderBy: options.orderBy,
orderDirection: options.orderDirection,
- filter: encodeURIComponent(options.filter)
+ filter: encodeURIComponent(options.filter),
+ ignoreUserStartNodes: options.ignoreUserStartNodes
}
)),
'Failed to retrieve child data for id ' + parentId);
@@ -488,12 +505,19 @@ function entityResource($q, $http, umbRequestHelper) {
* @returns {Promise} resourcePromise object containing the entity array.
*
*/
- search: function (query, type, searchFrom, canceler) {
+ search: function (query, type, options, canceler) {
- var args = [{ query: query }, { type: type }];
- if (searchFrom) {
- args.push({ searchFrom: searchFrom });
+ var defaults = {
+ searchFrom: null,
+ ignoreUserStartNodes: false
+ };
+ if (options === undefined) {
+ options = {};
}
+ //overwrite the defaults if there are any specified
+ angular.extend(defaults, options);
+ //now copy back to the options we will use
+ options = defaults;
var httpConfig = {};
if (canceler) {
@@ -505,7 +529,12 @@ function entityResource($q, $http, umbRequestHelper) {
umbRequestHelper.getApiUrl(
"entityApiBaseUrl",
"Search",
- args),
+ {
+ query: query,
+ type: type,
+ searchFrom: options.searchFrom,
+ ignoreUserStartNodes: options.ignoreUserStartNodes
+ }),
httpConfig),
'Failed to retrieve entity data for query ' + query);
},
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js
index 8c27f20aea..e968913047 100644
--- a/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/media.resource.js
@@ -329,7 +329,8 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
filter: '',
orderDirection: "Ascending",
orderBy: "SortOrder",
- orderBySystemField: true
+ orderBySystemField: true,
+ ignoreUserStartNodes: false
};
if (options === undefined) {
options = {};
@@ -372,7 +373,8 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
{ orderBy: options.orderBy },
{ orderDirection: options.orderDirection },
{ orderBySystemField: toBool(options.orderBySystemField) },
- { filter: options.filter }
+ { filter: options.filter },
+ { ignoreUserStartNodes: options.ignoreUserStartNodes }
])),
'Failed to retrieve children for media item ' + parentId);
},
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js
index 8738c1011e..0d00678282 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js
@@ -42,7 +42,11 @@ angular.module('umbraco.services')
throw "args.term is required";
}
- return entityResource.search(args.term, "Member", args.searchFrom).then(function (data) {
+ var options = {
+ searchFrom: args.searchFrom
+ }
+
+ return entityResource.search(args.term, "Member", options).then(function (data) {
_.each(data, function (item) {
searchResultFormatter.configureMemberResult(item);
});
@@ -67,7 +71,12 @@ angular.module('umbraco.services')
throw "args.term is required";
}
- return entityResource.search(args.term, "Document", args.searchFrom, args.canceler).then(function (data) {
+ var options = {
+ searchFrom: args.searchFrom,
+ ignoreUserStartNodes: args.ignoreUserStartNodes
+ }
+
+ return entityResource.search(args.term, "Document", options, args.canceler).then(function (data) {
_.each(data, function (item) {
searchResultFormatter.configureContentResult(item);
});
@@ -92,7 +101,12 @@ angular.module('umbraco.services')
throw "args.term is required";
}
- return entityResource.search(args.term, "Media", args.searchFrom).then(function (data) {
+ var options = {
+ searchFrom: args.searchFrom,
+ ignoreUserStartNodes: args.ignoreUserStartNodes
+ }
+
+ return entityResource.search(args.term, "Media", options).then(function (data) {
_.each(data, function (item) {
searchResultFormatter.configureMediaResult(item);
});
@@ -157,4 +171,4 @@ angular.module('umbraco.services')
var currentSection = sectionAlias;
}
};
- });
\ No newline at end of file
+ });
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html
index 43eab532d4..4391e50c28 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/contentpicker/contentpicker.html
@@ -9,6 +9,7 @@
search-from-id="{{searchInfo.searchFromId}}"
search-from-name="{{searchInfo.searchFromName}}"
show-search="{{searchInfo.showSearch}}"
+ ignore-user-startnodes="{{searchInfo.ignoreUserStartNodes}}"
section="content">
@@ -45,4 +46,4 @@
on-close="closeMiniListView()">
-
\ No newline at end of file
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js
index fcce34621b..79b9362d3f 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.controller.js
@@ -1,57 +1,58 @@
//used for the media picker dialog
angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController",
- function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) {
- var dialogOptions = $scope.model;
+ function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) {
+ var dialogOptions = $scope.model;
- var searchText = "Search...";
- localizationService.localize("general_search").then(function (value) {
- searchText = value + "...";
- });
+ var searchText = "Search...";
+ localizationService.localize("general_search").then(function (value) {
+ searchText = value + "...";
+ });
- if (!$scope.model.title) {
- $scope.model.title = localizationService.localize("defaultdialogs_selectLink");
- }
+ if (!$scope.model.title) {
+ $scope.model.title = localizationService.localize("defaultdialogs_selectLink");
+ }
- $scope.dialogTreeEventHandler = $({});
- $scope.model.target = {};
- $scope.searchInfo = {
- searchFromId: null,
- searchFromName: null,
- showSearch: false,
- results: [],
- selectedSearchResults: []
- };
+ $scope.dialogTreeEventHandler = $({});
+ $scope.model.target = {};
+ $scope.searchInfo = {
+ searchFromId: null,
+ searchFromName: null,
+ showSearch: false,
+ ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes,
+ results: [],
+ selectedSearchResults: []
+ };
+ $scope.customTreeParams = dialogOptions.ignoreUserStartNodes ? "ignoreUserStartNodes=" + dialogOptions.ignoreUserStartNodes : "";
+ $scope.showTarget = $scope.model.hideTarget !== true;
- $scope.showTarget = $scope.model.hideTarget !== true;
+ if (dialogOptions.currentTarget) {
+ // clone the current target so we don't accidentally update the caller's model while manipulating $scope.model.target
+ $scope.model.target = angular.copy(dialogOptions.currentTarget);
+ //if we have a node ID, we fetch the current node to build the form data
+ if ($scope.model.target.id || $scope.model.target.udi) {
- if (dialogOptions.currentTarget) {
- // clone the current target so we don't accidentally update the caller's model while manipulating $scope.model.target
- $scope.model.target = angular.copy(dialogOptions.currentTarget);
- //if we have a node ID, we fetch the current node to build the form data
- if ($scope.model.target.id || $scope.model.target.udi) {
+ //will be either a udi or an int
+ var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id;
- //will be either a udi or an int
- var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id;
+ // is it a content link?
+ if (!$scope.model.target.isMedia) {
+ // get the content path
+ entityResource.getPath(id, "Document").then(function (path) {
+ //now sync the tree to this path
+ $scope.dialogTreeEventHandler.syncTree({
+ path: path,
+ tree: "content"
+ });
+ });
- // is it a content link?
- if (!$scope.model.target.isMedia) {
- // get the content path
- entityResource.getPath(id, "Document").then(function(path) {
- //now sync the tree to this path
- $scope.dialogTreeEventHandler.syncTree({
- path: path,
- tree: "content"
- });
- });
-
- // get the content properties to build the anchor name list
- contentResource.getById(id).then(function (resp) {
- $scope.model.target.url = resp.urls[0];
- $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties));
- });
- }
- } else if ($scope.model.target.url.length) {
- // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs
+ // if a link exists, get the properties to build the anchor name list
+ contentResource.getById(id, { ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes }).then(function (resp) {
+ $scope.model.target.url = resp.urls[0];
+ $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties));
+ });
+ }
+ } else if ($scope.model.target.url.length) {
+ // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs
// only do the substring if there's a # or a ?
var indexOfAnchor = $scope.model.target.url.search(/(#|\?)/);
if (indexOfAnchor > -1) {
@@ -60,124 +61,132 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController",
// then rewrite the model and populate the link
$scope.model.target.url = $scope.model.target.url.substring(0, indexOfAnchor);
}
- }
- } else if (dialogOptions.anchors) {
- $scope.anchorValues = dialogOptions.anchors;
- }
+ }
+ } else if (dialogOptions.anchors) {
+ $scope.anchorValues = dialogOptions.anchors;
+ }
- function nodeSelectHandler(ev, args) {
- if (args && args.event) {
- args.event.preventDefault();
- args.event.stopPropagation();
- }
+ function nodeSelectHandler(ev, args) {
+ if (args && args.event) {
+ args.event.preventDefault();
+ args.event.stopPropagation();
+ }
- eventsService.emit("dialogs.linkPicker.select", args);
+ eventsService.emit("dialogs.linkPicker.select", args);
- if ($scope.currentNode) {
- //un-select if there's a current one selected
- $scope.currentNode.selected = false;
- }
+ if ($scope.currentNode) {
+ //un-select if there's a current one selected
+ $scope.currentNode.selected = false;
+ }
- $scope.currentNode = args.node;
- $scope.currentNode.selected = true;
- $scope.model.target.id = args.node.id;
- $scope.model.target.udi = args.node.udi;
- $scope.model.target.name = args.node.name;
+ $scope.currentNode = args.node;
+ $scope.currentNode.selected = true;
+ $scope.model.target.id = args.node.id;
+ $scope.model.target.udi = args.node.udi;
+ $scope.model.target.name = args.node.name;
- if (args.node.id < 0) {
- $scope.model.target.url = "/";
- } else {
- contentResource.getById(args.node.id).then(function (resp) {
- $scope.model.target.url = resp.urls[0];
- $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties));
- });
- }
+ if (args.node.id < 0) {
+ $scope.model.target.url = "/";
+ } else {
+ contentResource.getById(args.node.id, { ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes }).then(function (resp) {
+ $scope.model.target.url = resp.urls[0];
+ $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties));
+ });
+ }
- if (!angular.isUndefined($scope.model.target.isMedia)) {
- delete $scope.model.target.isMedia;
- }
- }
+ if (!angular.isUndefined($scope.model.target.isMedia)) {
+ delete $scope.model.target.isMedia;
+ }
+ }
- function nodeExpandedHandler(ev, args) {
- // open mini list view for list views
- if (args.node.metaData.isContainer) {
- openMiniListView(args.node);
- }
- }
+ function nodeExpandedHandler(ev, args) {
+ // open mini list view for list views
+ if (args.node.metaData.isContainer) {
+ openMiniListView(args.node);
+ }
+ }
- $scope.switchToMediaPicker = function () {
- userService.getCurrentUser().then(function (userData) {
- $scope.mediaPickerOverlay = {
- view: "mediapicker",
- startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0],
- startNodeIsVirtual: userData.startMediaIds.length !== 1,
- show: true,
- submit: function (model) {
- var media = model.selectedImages[0];
+ $scope.switchToMediaPicker = function () {
+ userService.getCurrentUser().then(function (userData) {
+ var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
+ var startNodeIsVirtual = userData.startMediaIds.length !== 1;
- $scope.model.target.id = media.id;
- $scope.model.target.udi = media.udi;
- $scope.model.target.isMedia = true;
- $scope.model.target.name = media.name;
- $scope.model.target.url = mediaHelper.resolveFile(media);
+ if (dialogOptions.ignoreUserStartNodes) {
+ startNodeId = -1;
+ startNodeIsVirtual = true;
+ }
+ $scope.mediaPickerOverlay = {
+ view: "mediapicker",
+ startNodeId: startNodeId,
+ startNodeIsVirtual: startNodeIsVirtual,
+ show: true,
+ ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes,
+ submit: function (model) {
+ var media = model.selectedImages[0];
- $scope.mediaPickerOverlay.show = false;
- $scope.mediaPickerOverlay = null;
+ $scope.model.target.id = media.id;
+ $scope.model.target.udi = media.udi;
+ $scope.model.target.isMedia = true;
+ $scope.model.target.name = media.name;
+ $scope.model.target.url = mediaHelper.resolveFile(media);
- // make sure the content tree has nothing highlighted
- $scope.dialogTreeEventHandler.syncTree({
- path: "-1",
- tree: "content"
- });
- }
- };
- });
- };
+ $scope.mediaPickerOverlay.show = false;
+ $scope.mediaPickerOverlay = null;
- $scope.hideSearch = function () {
- $scope.searchInfo.showSearch = false;
- $scope.searchInfo.searchFromId = null;
- $scope.searchInfo.searchFromName = null;
- $scope.searchInfo.results = [];
- }
+ // make sure the content tree has nothing highlighted
+ $scope.dialogTreeEventHandler.syncTree({
+ path: "-1",
+ tree: "content"
+ });
+ }
+ };
+ });
+ };
- // method to select a search result
- $scope.selectResult = function (evt, result) {
- result.selected = result.selected === true ? false : true;
- nodeSelectHandler(evt, {
- event: evt,
- node: result
- });
- };
+ $scope.hideSearch = function () {
+ $scope.searchInfo.showSearch = false;
+ $scope.searchInfo.searchFromId = null;
+ $scope.searchInfo.searchFromName = null;
+ $scope.searchInfo.results = [];
+ }
- //callback when there are search results
- $scope.onSearchResults = function (results) {
- $scope.searchInfo.results = results;
- $scope.searchInfo.showSearch = true;
- };
+ // method to select a search result
+ $scope.selectResult = function (evt, result) {
+ result.selected = result.selected === true ? false : true;
+ nodeSelectHandler(evt, {
+ event: evt,
+ node: result
+ });
+ };
- $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
- $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
+ //callback when there are search results
+ $scope.onSearchResults = function (results) {
+ $scope.searchInfo.results = results;
+ $scope.searchInfo.showSearch = true;
+ };
- $scope.$on('$destroy', function () {
- $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
- $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
- });
+ $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
+ $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
- // Mini list view
- $scope.selectListViewNode = function (node) {
- node.selected = node.selected === true ? false : true;
- nodeSelectHandler({}, {
- node: node
- });
- };
+ $scope.$on('$destroy', function () {
+ $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
+ $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
+ });
- $scope.closeMiniListView = function () {
- $scope.miniListView = undefined;
- };
+ // Mini list view
+ $scope.selectListViewNode = function (node) {
+ node.selected = node.selected === true ? false : true;
+ nodeSelectHandler({}, {
+ node: node
+ });
+ };
- function openMiniListView(node) {
- $scope.miniListView = node;
- }
+ $scope.closeMiniListView = function () {
+ $scope.miniListView = undefined;
+ };
- });
+ function openMiniListView(node) {
+ $scope.miniListView = node;
+ }
+
+ });
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html
index deba19aa11..c9f3ad54a7 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/linkpicker/linkpicker.html
@@ -50,6 +50,7 @@
search-from-id="{{searchInfo.searchFromId}}"
search-from-name="{{searchInfo.searchFromName}}"
show-search="{{searchInfo.showSearch}}"
+ ignore-user-startnodes="{{searchInfo.ignoreUserStartNodes}}"
section="{{section}}">
@@ -64,6 +65,7 @@
0) {
- entityResource.getAncestors(folder.id, "media")
- .then(function(anc) {
+ entityResource.getAncestors(folder.id, "media", { ignoreUserStartNodes: $scope.model.ignoreUserStartNodes })
+ .then(function(anc) {
$scope.path = _.filter(anc,
function(f) {
return f.path.indexOf($scope.startNodeId) !== -1;
@@ -161,13 +168,13 @@ angular.module("umbraco")
} else {
$scope.path = [];
}
-
+
mediaTypeHelper.getAllowedImagetypes(folder.id)
.then(function (types) {
$scope.acceptedMediatypes = types;
});
-
- $scope.lockedFolder = folder.id === -1 && $scope.model.startNodeIsVirtual;
+
+ $scope.lockedFolder = (folder.id === -1 && $scope.model.startNodeIsVirtual) || hasFolderAccess(folder) === false;
$scope.currentFolder = folder;
localStorageService.set("umbLastOpenedMediaNodeId", folder.id);
@@ -263,6 +270,17 @@ angular.module("umbraco")
}
}
+ function hasFolderAccess(node) {
+ var nodePath = node.path ? node.path.split(',') : [node.id];
+
+ for (var i = 0; i < nodePath.length; i++) {
+ if (userStartNodes.indexOf(parseInt(nodePath[i])) !== -1)
+ return true;
+ }
+
+ return false;
+ }
+
function gotoStartNode(err) {
$scope.gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" });
}
@@ -297,7 +315,8 @@ angular.module("umbraco")
pageSize: 100,
totalItems: 0,
totalPages: 0,
- filter: ''
+ filter: '',
+ ignoreUserStartNodes: $scope.model.ignoreUserStartNodes
};
getChildren($scope.currentFolder.id);
}
@@ -369,7 +388,7 @@ angular.module("umbraco")
function getChildren(id) {
$scope.loading = true;
- return mediaResource.getChildren(id)
+ return mediaResource.getChildren(id, { ignoreUserStartNodes: $scope.model.ignoreUserStartNodes })
.then(function(data) {
$scope.searchOptions.filter = "";
$scope.images = data.items ? data.items : [];
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js
index fa7a797125..1ff2228d62 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.controller.js
@@ -1,518 +1,530 @@
-//used for the media picker dialog
-angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
- function ($scope, $q, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService, contentResource, mediaResource, memberResource) {
-
- var tree = null;
- var dialogOptions = $scope.model;
- $scope.treeReady = false;
- $scope.dialogTreeEventHandler = $({});
- $scope.section = dialogOptions.section;
- $scope.treeAlias = dialogOptions.treeAlias;
- $scope.multiPicker = dialogOptions.multiPicker;
- $scope.hideHeader = (typeof dialogOptions.hideHeader) === "boolean" ? dialogOptions.hideHeader : true;
- // if you need to load a not initialized tree set this value to false - default is true
- $scope.onlyInitialized = dialogOptions.onlyInitialized;
- $scope.searchInfo = {
- searchFromId: dialogOptions.startNodeId,
- searchFromName: null,
- showSearch: false,
- results: [],
- selectedSearchResults: []
- }
-
- $scope.model.selection = [];
-
- //Used for toggling an empty-state message
- //Some trees can have no items (dictionary & forms email templates)
- $scope.hasItems = true;
- $scope.emptyStateMessage = dialogOptions.emptyStateMessage;
- var node = dialogOptions.currentNode;
-
- //This is called from ng-init
- //it turns out it is called from the angular html : / Have a look at views/common / overlays / contentpicker / contentpicker.html you'll see ng-init.
- //this is probably an anti pattern IMO and shouldn't be used
- $scope.init = function (contentType) {
-
- if (contentType === "content") {
- $scope.entityType = "Document";
- if (!$scope.model.title) {
- $scope.model.title = localizationService.localize("defaultdialogs_selectContent");
- }
- } else if (contentType === "member") {
- $scope.entityType = "Member";
- if (!$scope.model.title) {
- $scope.model.title = localizationService.localize("defaultdialogs_selectMember");
- }
- } else if (contentType === "media") {
- $scope.entityType = "Media";
- if (!$scope.model.title) {
- $scope.model.title = localizationService.localize("defaultdialogs_selectMedia");
- }
- }
- }
-
- var searchText = "Search...";
- localizationService.localize("general_search").then(function (value) {
- searchText = value + "...";
- });
-
- // Allow the entity type to be passed in but defaults to Document for backwards compatibility.
- $scope.entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document";
-
-
- //min / max values
- if (dialogOptions.minNumber) {
- dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10);
- }
- if (dialogOptions.maxNumber) {
- dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10);
- }
-
- if (dialogOptions.section === "member") {
- $scope.entityType = "Member";
- }
- else if (dialogOptions.section === "media") {
- $scope.entityType = "Media";
- }
-
- // Search and listviews is only working for content, media and member section
- var searchableSections = ["content", "media", "member"];
-
- $scope.enableSearh = searchableSections.indexOf($scope.section) !== -1;
-
- //if a alternative startnode is used, we need to check if it is a container
- if ($scope.enableSearh && dialogOptions.startNodeId && dialogOptions.startNodeId !== -1 && dialogOptions.startNodeId !== "-1") {
- entityResource.getById(dialogOptions.startNodeId, $scope.entityType).then(function(node) {
- if (node.metaData.IsContainer) {
- openMiniListView(node);
- }
- initTree();
- });
- }
- else {
- initTree();
- }
-
- //Configures filtering
- if (dialogOptions.filter) {
-
- dialogOptions.filterExclude = false;
- dialogOptions.filterAdvanced = false;
-
- //used advanced filtering
- if (angular.isFunction(dialogOptions.filter)) {
- dialogOptions.filterAdvanced = true;
- }
- else if (angular.isObject(dialogOptions.filter)) {
- dialogOptions.filterAdvanced = true;
- }
- else {
- if (dialogOptions.filter.startsWith("!")) {
- dialogOptions.filterExclude = true;
- dialogOptions.filterTypes = dialogOptions.filter.substring(1);
- } else {
- dialogOptions.filterExclude = false;
- dialogOptions.filterTypes = dialogOptions.filter;
- }
-
- //used advanced filtering
- if (dialogOptions.filter.startsWith("{")) {
- dialogOptions.filterAdvanced = true;
- //convert to object
- dialogOptions.filter = angular.fromJson(dialogOptions.filter);
- }
- }
-
- $scope.filter = {
- filterAdvanced: dialogOptions.filterAdvanced,
- filterExclude: dialogOptions.filterExclude,
- filter: dialogOptions.filterTypes
- };
- }
-
- function initTree() {
- //create the custom query string param for this tree
- $scope.customTreeParams = dialogOptions.startNodeId ? "startNodeId=" + dialogOptions.startNodeId : "";
- $scope.customTreeParams += dialogOptions.customTreeParams ? "&" + dialogOptions.customTreeParams : "";
- $scope.treeReady = true;
- }
-
- function nodeExpandedHandler(ev, args) {
-
- // open mini list view for list views
- if (args.node.metaData.isContainer) {
- openMiniListView(args.node);
- }
-
- if (angular.isArray(args.children)) {
-
- //iterate children
- _.each(args.children, function (child) {
-
- //now we need to look in the already selected search results and
- // toggle the check boxes for those ones that are listed
- var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
- return child.id == selected.id;
- });
- if (exists) {
- child.selected = true;
- }
- });
-
- //check filter
- performFiltering(args.children);
- }
- }
-
- //gets the tree object when it loads
- function treeLoadedHandler(ev, args) {
- //args.tree contains children (args.tree.root.children)
- $scope.hasItems = args.tree.root.children.length > 0;
-
- tree = args.tree;
-
- var nodeHasPath = typeof node !== "undefined" && typeof node.path !== "undefined";
- var startNodeNotDefined = typeof dialogOptions.startNodeId === "undefined" || dialogOptions.startNodeId === "" || dialogOptions.startNodeId === "-1";
- if (startNodeNotDefined && nodeHasPath) {
- $scope.dialogTreeEventHandler.syncTree({ path: node.path, activate: false });
- }
-
- }
-
- //wires up selection
- function nodeSelectHandler(ev, args) {
- args.event.preventDefault();
- args.event.stopPropagation();
-
- if (args.node.metaData.isSearchResult) {
- //check if the item selected was a search result from a list view
-
- //unselect
- select(args.node.name, args.node.id);
-
- //remove it from the list view children
- var listView = args.node.parent();
- listView.children = _.reject(listView.children, function (child) {
- return child.id == args.node.id;
- });
-
- //remove it from the custom tracked search result list
- $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
- return i.id == args.node.id;
- });
- }
- else {
- eventsService.emit("dialogs.treePickerController.select", args);
-
- if (args.node.filtered) {
- return;
- }
-
- //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
- //from the server in this method.
- if ($scope.model.select) {
- $scope.model.select(args.node)
- } else {
- select(args.node.name, args.node.id);
- //toggle checked state
- args.node.selected = args.node.selected === true ? false : true;
- }
-
- }
- }
-
- /** Method used for selecting a node */
- function select(text, id, entity) {
- //if we get the root, we just return a constructed entity, no need for server data
- if (id < 0) {
-
- var rootNode = {
- alias: null,
- icon: "icon-folder",
- id: id,
- name: text
- };
-
- if ($scope.multiPicker) {
- if (entity) {
- multiSelectItem(entity);
- } else {
- multiSelectItem(rootNode);
- }
- }
- else {
- $scope.model.selection.push(rootNode);
- $scope.model.submit($scope.model);
- }
- }
- else {
-
- if ($scope.multiPicker) {
-
- if (entity) {
- multiSelectItem(entity);
- } else {
- //otherwise we have to get it from the server
- entityResource.getById(id, $scope.entityType).then(function (ent) {
- multiSelectItem(ent);
- });
- }
-
- }
-
- else {
-
- $scope.hideSearch();
-
- //if an entity has been passed in, use it
- if (entity) {
- $scope.model.selection.push(entity);
- $scope.model.submit($scope.model);
- } else {
- //otherwise we have to get it from the server
- entityResource.getById(id, $scope.entityType).then(function (ent) {
- $scope.model.selection.push(ent);
- $scope.model.submit($scope.model);
- });
- }
- }
- }
- }
-
- function multiSelectItem(item) {
-
- var found = false;
- var foundIndex = 0;
-
- if ($scope.model.selection.length > 0) {
- for (i = 0; $scope.model.selection.length > i; i++) {
- var selectedItem = $scope.model.selection[i];
- if (selectedItem.id === item.id) {
- found = true;
- foundIndex = i;
- }
- }
- }
-
- if (found) {
- $scope.model.selection.splice(foundIndex, 1);
- } else {
- $scope.model.selection.push(item);
- }
-
- }
-
- function performFiltering(nodes) {
-
- if (!dialogOptions.filter) {
- return;
- }
-
- //remove any list view search nodes from being filtered since these are special nodes that always must
- // be allowed to be clicked on
- nodes = _.filter(nodes, function (n) {
- return !angular.isObject(n.metaData.listViewNode);
- });
-
- if (dialogOptions.filterAdvanced) {
-
- //filter either based on a method or an object
- var filtered = angular.isFunction(dialogOptions.filter)
- ? _.filter(nodes, dialogOptions.filter)
- : _.where(nodes, dialogOptions.filter);
-
- angular.forEach(filtered, function (value, key) {
- value.filtered = true;
- if (dialogOptions.filterCssClass) {
- if (!value.cssClasses) {
- value.cssClasses = [];
- }
- value.cssClasses.push(dialogOptions.filterCssClass);
- }
- });
- } else {
- var a = dialogOptions.filterTypes.toLowerCase().replace(/\s/g, '').split(',');
- angular.forEach(nodes, function (value, key) {
-
- var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0;
-
- if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) {
- value.filtered = true;
-
- if (dialogOptions.filterCssClass) {
- if (!value.cssClasses) {
- value.cssClasses = [];
- }
- value.cssClasses.push(dialogOptions.filterCssClass);
- }
- }
- });
- }
- }
-
- $scope.multiSubmit = function (result) {
- entityResource.getByIds(result, $scope.entityType).then(function (ents) {
- $scope.submit(ents);
- });
- };
-
- /** method to select a search result */
- $scope.selectResult = function (evt, result) {
-
- if (result.filtered) {
- return;
- }
-
- result.selected = result.selected === true ? false : true;
-
- //since result = an entity, we'll pass it in so we don't have to go back to the server
- select(result.name, result.id, result);
-
- //add/remove to our custom tracked list of selected search results
- if (result.selected) {
- $scope.searchInfo.selectedSearchResults.push(result);
- }
- else {
- $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
- return i.id == result.id;
- });
- }
-
- //ensure the tree node in the tree is checked/unchecked if it already exists there
- if (tree) {
- var found = treeService.getDescendantNode(tree.root, result.id);
- if (found) {
- found.selected = result.selected;
- }
- }
-
- };
-
- $scope.hideSearch = function () {
-
- //Traverse the entire displayed tree and update each node to sync with the selected search results
- if (tree) {
-
- //we need to ensure that any currently displayed nodes that get selected
- // from the search get updated to have a check box!
- function checkChildren(children) {
- _.each(children, function (child) {
- //check if the id is in the selection, if so ensure it's flagged as selected
- var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
- return child.id == selected.id;
- });
- //if the curr node exists in selected search results, ensure it's checked
- if (exists) {
- child.selected = true;
- }
- //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result
- else if (child.metaData.isSearchResult) {
- //if this tree node is under a list view it means that the node was added
- // to the tree dynamically under the list view that was searched, so we actually want to remove
- // it all together from the tree
- var listView = child.parent();
- listView.children = _.reject(listView.children, function (c) {
- return c.id == child.id;
- });
- }
-
- //check if the current node is a list view and if so, check if there's any new results
- // that need to be added as child nodes to it based on search results selected
- if (child.metaData.isContainer) {
-
- child.cssClasses = _.reject(child.cssClasses, function (c) {
- return c === 'tree-node-slide-up-hide-active';
- });
-
- var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) {
- return i.parentId == child.id;
- });
- _.each(listViewResults, function (item) {
- var childExists = _.find(child.children, function (c) {
- return c.id == item.id;
- });
- if (!childExists) {
- var parent = child;
- child.children.unshift({
- id: item.id,
- name: item.name,
- cssClass: "icon umb-tree-icon sprTree " + item.icon,
- level: child.level + 1,
- metaData: {
- isSearchResult: true
- },
- hasChildren: false,
- parent: function () {
- return parent;
- }
- });
- }
- });
- }
-
- //recurse
- if (child.children && child.children.length > 0) {
- checkChildren(child.children);
- }
- });
- }
- checkChildren(tree.root.children);
- }
-
-
- $scope.searchInfo.showSearch = false;
- $scope.searchInfo.searchFromId = dialogOptions.startNodeId;
- $scope.searchInfo.searchFromName = null;
- $scope.searchInfo.results = [];
- }
-
- $scope.onSearchResults = function (results) {
-
- //filter all items - this will mark an item as filtered
- performFiltering(results);
-
- //now actually remove all filtered items so they are not even displayed
- results = _.filter(results, function (item) {
- return !item.filtered;
- });
-
- $scope.searchInfo.results = results;
-
- //sync with the curr selected results
- _.each($scope.searchInfo.results, function (result) {
- var exists = _.find($scope.model.selection, function (selectedId) {
- return result.id == selectedId;
- });
- if (exists) {
- result.selected = true;
- }
- });
-
- $scope.searchInfo.showSearch = true;
- };
-
- $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler);
- $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
- $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
-
- $scope.$on('$destroy', function () {
- $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler);
- $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
- $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
- });
-
- $scope.selectListViewNode = function (node) {
- select(node.name, node.id);
- //toggle checked state
- node.selected = node.selected === true ? false : true;
- };
-
- $scope.closeMiniListView = function () {
- $scope.miniListView = undefined;
- };
-
- function openMiniListView(node) {
- $scope.miniListView = node;
- }
-
- });
+//used for the media picker dialog
+angular.module("umbraco").controller("Umbraco.Overlays.TreePickerController",
+ function ($scope, $q, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService, contentResource, mediaResource, memberResource) {
+
+ var tree = null;
+ var dialogOptions = $scope.model;
+ $scope.treeReady = false;
+ $scope.dialogTreeEventHandler = $({});
+ $scope.section = dialogOptions.section;
+ $scope.treeAlias = dialogOptions.treeAlias;
+ $scope.multiPicker = dialogOptions.multiPicker;
+ $scope.hideHeader = (typeof dialogOptions.hideHeader) === "boolean" ? dialogOptions.hideHeader : true;
+ // if you need to load a not initialized tree set this value to false - default is true
+ $scope.onlyInitialized = dialogOptions.onlyInitialized;
+ $scope.searchInfo = {
+ searchFromId: dialogOptions.startNodeId,
+ searchFromName: null,
+ showSearch: false,
+ ignoreUserStartNodes: dialogOptions.ignoreUserStartNodes,
+ results: [],
+ selectedSearchResults: []
+ }
+
+ $scope.model.selection = [];
+
+ //Used for toggling an empty-state message
+ //Some trees can have no items (dictionary & forms email templates)
+ $scope.hasItems = true;
+ $scope.emptyStateMessage = dialogOptions.emptyStateMessage;
+ var node = dialogOptions.currentNode;
+
+ //This is called from ng-init
+ //it turns out it is called from the angular html : / Have a look at views/common / overlays / contentpicker / contentpicker.html you'll see ng-init.
+ //this is probably an anti pattern IMO and shouldn't be used
+ $scope.init = function (contentType) {
+
+ if (contentType === "content") {
+ $scope.entityType = "Document";
+ if (!$scope.model.title) {
+ $scope.model.title = localizationService.localize("defaultdialogs_selectContent");
+ }
+ } else if (contentType === "member") {
+ $scope.entityType = "Member";
+ if (!$scope.model.title) {
+ $scope.model.title = localizationService.localize("defaultdialogs_selectMember");
+ }
+ } else if (contentType === "media") {
+ $scope.entityType = "Media";
+ if (!$scope.model.title) {
+ $scope.model.title = localizationService.localize("defaultdialogs_selectMedia");
+ }
+ }
+ }
+
+ var searchText = "Search...";
+ localizationService.localize("general_search").then(function (value) {
+ searchText = value + "...";
+ });
+
+ // Allow the entity type to be passed in but defaults to Document for backwards compatibility.
+ $scope.entityType = dialogOptions.entityType ? dialogOptions.entityType : "Document";
+
+
+ //min / max values
+ if (dialogOptions.minNumber) {
+ dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10);
+ }
+ if (dialogOptions.maxNumber) {
+ dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10);
+ }
+
+ if (dialogOptions.section === "member") {
+ $scope.entityType = "Member";
+ }
+ else if (dialogOptions.section === "media") {
+ $scope.entityType = "Media";
+ }
+
+ // Search and listviews is only working for content, media and member section
+ var searchableSections = ["content", "media", "member"];
+
+ $scope.enableSearh = searchableSections.indexOf($scope.section) !== -1;
+
+ //if a alternative startnode is used, we need to check if it is a container
+ if ($scope.enableSearh && dialogOptions.startNodeId && dialogOptions.startNodeId !== -1 && dialogOptions.startNodeId !== "-1") {
+ entityResource.getById(dialogOptions.startNodeId, $scope.entityType).then(function(node) {
+ if (node.metaData.IsContainer) {
+ openMiniListView(node);
+ }
+ initTree();
+ });
+ }
+ else {
+ initTree();
+ }
+
+ //Configures filtering
+ if (dialogOptions.filter) {
+
+ dialogOptions.filterExclude = false;
+ dialogOptions.filterAdvanced = false;
+
+ //used advanced filtering
+ if (angular.isFunction(dialogOptions.filter)) {
+ dialogOptions.filterAdvanced = true;
+ }
+ else if (angular.isObject(dialogOptions.filter)) {
+ dialogOptions.filterAdvanced = true;
+ }
+ else {
+ if (dialogOptions.filter.startsWith("!")) {
+ dialogOptions.filterExclude = true;
+ dialogOptions.filterTypes = dialogOptions.filter.substring(1);
+ } else {
+ dialogOptions.filterExclude = false;
+ dialogOptions.filterTypes = dialogOptions.filter;
+ }
+
+ //used advanced filtering
+ if (dialogOptions.filter.startsWith("{")) {
+ dialogOptions.filterAdvanced = true;
+ //convert to object
+ dialogOptions.filter = angular.fromJson(dialogOptions.filter);
+ }
+ }
+
+ $scope.filter = {
+ filterAdvanced: dialogOptions.filterAdvanced,
+ filterExclude: dialogOptions.filterExclude,
+ filter: dialogOptions.filterTypes
+ };
+ }
+
+ function initTree() {
+ //create the custom query string param for this tree
+ var params = [];
+
+ if (dialogOptions.startNodeId)
+ params.push("startNodeId=" + dialogOptions.startNodeId);
+
+ if (dialogOptions.ignoreUserStartNodes)
+ params.push("ignoreUserStartNodes=" + dialogOptions.ignoreUserStartNodes);
+
+ if (dialogOptions.customTreeParams)
+ params.push(dialogOptions.customTreeParams);
+
+ $scope.customTreeParams = params.join('&');
+
+ $scope.treeReady = true;
+ }
+
+ function nodeExpandedHandler(ev, args) {
+
+ // open mini list view for list views
+ if (args.node.metaData.isContainer) {
+ openMiniListView(args.node);
+ }
+
+ if (angular.isArray(args.children)) {
+
+ //iterate children
+ _.each(args.children, function (child) {
+
+ //now we need to look in the already selected search results and
+ // toggle the check boxes for those ones that are listed
+ var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
+ return child.id == selected.id;
+ });
+ if (exists) {
+ child.selected = true;
+ }
+ });
+
+ //check filter
+ performFiltering(args.children);
+ }
+ }
+
+ //gets the tree object when it loads
+ function treeLoadedHandler(ev, args) {
+ //args.tree contains children (args.tree.root.children)
+ $scope.hasItems = args.tree.root.children.length > 0;
+
+ tree = args.tree;
+
+ var nodeHasPath = typeof node !== "undefined" && typeof node.path !== "undefined";
+ var startNodeNotDefined = typeof dialogOptions.startNodeId === "undefined" || dialogOptions.startNodeId === "" || dialogOptions.startNodeId === "-1";
+ if (startNodeNotDefined && nodeHasPath) {
+ $scope.dialogTreeEventHandler.syncTree({ path: node.path, activate: false });
+ }
+
+ }
+
+ //wires up selection
+ function nodeSelectHandler(ev, args) {
+ args.event.preventDefault();
+ args.event.stopPropagation();
+
+ if (args.node.metaData.isSearchResult) {
+ //check if the item selected was a search result from a list view
+
+ //unselect
+ select(args.node.name, args.node.id);
+
+ //remove it from the list view children
+ var listView = args.node.parent();
+ listView.children = _.reject(listView.children, function (child) {
+ return child.id == args.node.id;
+ });
+
+ //remove it from the custom tracked search result list
+ $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
+ return i.id == args.node.id;
+ });
+ }
+ else {
+ eventsService.emit("dialogs.treePickerController.select", args);
+
+ if (args.node.filtered) {
+ return;
+ }
+
+ //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
+ //from the server in this method.
+ if ($scope.model.select) {
+ $scope.model.select(args.node)
+ } else {
+ select(args.node.name, args.node.id);
+ //toggle checked state
+ args.node.selected = args.node.selected === true ? false : true;
+ }
+
+ }
+ }
+
+ /** Method used for selecting a node */
+ function select(text, id, entity) {
+ //if we get the root, we just return a constructed entity, no need for server data
+ if (id < 0) {
+
+ var rootNode = {
+ alias: null,
+ icon: "icon-folder",
+ id: id,
+ name: text
+ };
+
+ if ($scope.multiPicker) {
+ if (entity) {
+ multiSelectItem(entity);
+ } else {
+ multiSelectItem(rootNode);
+ }
+ }
+ else {
+ $scope.model.selection.push(rootNode);
+ $scope.model.submit($scope.model);
+ }
+ }
+ else {
+
+ if ($scope.multiPicker) {
+
+ if (entity) {
+ multiSelectItem(entity);
+ } else {
+ //otherwise we have to get it from the server
+ entityResource.getById(id, $scope.entityType).then(function (ent) {
+ multiSelectItem(ent);
+ });
+ }
+
+ }
+
+ else {
+
+ $scope.hideSearch();
+
+ //if an entity has been passed in, use it
+ if (entity) {
+ $scope.model.selection.push(entity);
+ $scope.model.submit($scope.model);
+ } else {
+ //otherwise we have to get it from the server
+ entityResource.getById(id, $scope.entityType).then(function (ent) {
+ $scope.model.selection.push(ent);
+ $scope.model.submit($scope.model);
+ });
+ }
+ }
+ }
+ }
+
+ function multiSelectItem(item) {
+
+ var found = false;
+ var foundIndex = 0;
+
+ if ($scope.model.selection.length > 0) {
+ for (i = 0; $scope.model.selection.length > i; i++) {
+ var selectedItem = $scope.model.selection[i];
+ if (selectedItem.id === item.id) {
+ found = true;
+ foundIndex = i;
+ }
+ }
+ }
+
+ if (found) {
+ $scope.model.selection.splice(foundIndex, 1);
+ } else {
+ $scope.model.selection.push(item);
+ }
+
+ }
+
+ function performFiltering(nodes) {
+
+ if (!dialogOptions.filter) {
+ return;
+ }
+
+ //remove any list view search nodes from being filtered since these are special nodes that always must
+ // be allowed to be clicked on
+ nodes = _.filter(nodes, function (n) {
+ return !angular.isObject(n.metaData.listViewNode);
+ });
+
+ if (dialogOptions.filterAdvanced) {
+
+ //filter either based on a method or an object
+ var filtered = angular.isFunction(dialogOptions.filter)
+ ? _.filter(nodes, dialogOptions.filter)
+ : _.where(nodes, dialogOptions.filter);
+
+ angular.forEach(filtered, function (value, key) {
+ value.filtered = true;
+ if (dialogOptions.filterCssClass) {
+ if (!value.cssClasses) {
+ value.cssClasses = [];
+ }
+ value.cssClasses.push(dialogOptions.filterCssClass);
+ }
+ });
+ } else {
+ var a = dialogOptions.filterTypes.toLowerCase().replace(/\s/g, '').split(',');
+ angular.forEach(nodes, function (value, key) {
+
+ var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0;
+
+ if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) {
+ value.filtered = true;
+
+ if (dialogOptions.filterCssClass) {
+ if (!value.cssClasses) {
+ value.cssClasses = [];
+ }
+ value.cssClasses.push(dialogOptions.filterCssClass);
+ }
+ }
+ });
+ }
+ }
+
+ $scope.multiSubmit = function (result) {
+ entityResource.getByIds(result, $scope.entityType).then(function (ents) {
+ $scope.submit(ents);
+ });
+ };
+
+ /** method to select a search result */
+ $scope.selectResult = function (evt, result) {
+
+ if (result.filtered) {
+ return;
+ }
+
+ result.selected = result.selected === true ? false : true;
+
+ //since result = an entity, we'll pass it in so we don't have to go back to the server
+ select(result.name, result.id, result);
+
+ //add/remove to our custom tracked list of selected search results
+ if (result.selected) {
+ $scope.searchInfo.selectedSearchResults.push(result);
+ }
+ else {
+ $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
+ return i.id == result.id;
+ });
+ }
+
+ //ensure the tree node in the tree is checked/unchecked if it already exists there
+ if (tree) {
+ var found = treeService.getDescendantNode(tree.root, result.id);
+ if (found) {
+ found.selected = result.selected;
+ }
+ }
+
+ };
+
+ $scope.hideSearch = function () {
+
+ //Traverse the entire displayed tree and update each node to sync with the selected search results
+ if (tree) {
+
+ //we need to ensure that any currently displayed nodes that get selected
+ // from the search get updated to have a check box!
+ function checkChildren(children) {
+ _.each(children, function (child) {
+ //check if the id is in the selection, if so ensure it's flagged as selected
+ var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
+ return child.id == selected.id;
+ });
+ //if the curr node exists in selected search results, ensure it's checked
+ if (exists) {
+ child.selected = true;
+ }
+ //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result
+ else if (child.metaData.isSearchResult) {
+ //if this tree node is under a list view it means that the node was added
+ // to the tree dynamically under the list view that was searched, so we actually want to remove
+ // it all together from the tree
+ var listView = child.parent();
+ listView.children = _.reject(listView.children, function (c) {
+ return c.id == child.id;
+ });
+ }
+
+ //check if the current node is a list view and if so, check if there's any new results
+ // that need to be added as child nodes to it based on search results selected
+ if (child.metaData.isContainer) {
+
+ child.cssClasses = _.reject(child.cssClasses, function (c) {
+ return c === 'tree-node-slide-up-hide-active';
+ });
+
+ var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) {
+ return i.parentId == child.id;
+ });
+ _.each(listViewResults, function (item) {
+ var childExists = _.find(child.children, function (c) {
+ return c.id == item.id;
+ });
+ if (!childExists) {
+ var parent = child;
+ child.children.unshift({
+ id: item.id,
+ name: item.name,
+ cssClass: "icon umb-tree-icon sprTree " + item.icon,
+ level: child.level + 1,
+ metaData: {
+ isSearchResult: true
+ },
+ hasChildren: false,
+ parent: function () {
+ return parent;
+ }
+ });
+ }
+ });
+ }
+
+ //recurse
+ if (child.children && child.children.length > 0) {
+ checkChildren(child.children);
+ }
+ });
+ }
+ checkChildren(tree.root.children);
+ }
+
+
+ $scope.searchInfo.showSearch = false;
+ $scope.searchInfo.searchFromId = dialogOptions.startNodeId;
+ $scope.searchInfo.searchFromName = null;
+ $scope.searchInfo.results = [];
+ }
+
+ $scope.onSearchResults = function (results) {
+
+ //filter all items - this will mark an item as filtered
+ performFiltering(results);
+
+ //now actually remove all filtered items so they are not even displayed
+ results = _.filter(results, function (item) {
+ return !item.filtered;
+ });
+
+ $scope.searchInfo.results = results;
+
+ //sync with the curr selected results
+ _.each($scope.searchInfo.results, function (result) {
+ var exists = _.find($scope.model.selection, function (selectedId) {
+ return result.id == selectedId;
+ });
+ if (exists) {
+ result.selected = true;
+ }
+ });
+
+ $scope.searchInfo.showSearch = true;
+ };
+
+ $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler);
+ $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
+ $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
+
+ $scope.$on('$destroy', function () {
+ $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler);
+ $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
+ $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
+ });
+
+ $scope.selectListViewNode = function (node) {
+ select(node.name, node.id);
+ //toggle checked state
+ node.selected = node.selected === true ? false : true;
+ };
+
+ $scope.closeMiniListView = function () {
+ $scope.miniListView = undefined;
+ };
+
+ function openMiniListView(node) {
+ $scope.miniListView = node;
+ }
+
+ });
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html
index a3a5fd6107..c338e3402c 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/treepicker/treepicker.html
@@ -10,6 +10,7 @@
search-from-id="{{searchInfo.searchFromId}}"
search-from-name="{{searchInfo.searchFromName}}"
show-search="{{searchInfo.showSearch}}"
+ ignore-user-startnodes="{{searchInfo.ignoreUserStartNodes}}"
section="{{section}}">
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js
index ee8c193b4b..ce836a8d68 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js
@@ -66,6 +66,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper
showOpenButton: false,
showEditButton: false,
showPathOnHover: false,
+ ignoreUserStartNodes: false,
maxNumber: 1,
minNumber : 0,
startNode: {
@@ -99,7 +100,8 @@ function contentPickerController($scope, entityResource, editorState, iconHelper
$scope.model.config.showOpenButton = ($scope.model.config.showOpenButton === "1" ? true : false);
$scope.model.config.showEditButton = ($scope.model.config.showEditButton === "1" ? true : false);
$scope.model.config.showPathOnHover = ($scope.model.config.showPathOnHover === "1" ? true : false);
-
+ $scope.model.config.ignoreUserStartNodes = ($scope.model.config.ignoreUserStartNodes === "1" ? true : false);
+
var entityType = $scope.model.config.startNode.type === "member"
? "Member"
: $scope.model.config.startNode.type === "media"
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js
index 21f9534848..a093ccb034 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js
@@ -1,12 +1,20 @@
angular.module("umbraco")
.controller("Umbraco.PropertyEditors.Grid.MediaController",
function ($scope, $rootScope, $timeout, userService) {
+
+ var ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false;
if (!$scope.model.config.startNodeId) {
- userService.getCurrentUser().then(function (userData) {
- $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
- $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1;
- });
+ if (ignoreUserStartNodes === true) {
+ $scope.model.config.startNodeId = -1;
+ $scope.model.config.startNodeIsVirtual = true;
+
+ } else {
+ userService.getCurrentUser().then(function (userData) {
+ $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
+ $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1;
+ });
+ }
}
$scope.setImage = function(){
@@ -14,6 +22,7 @@ angular.module("umbraco")
$scope.mediaPickerOverlay.view = "mediapicker";
$scope.mediaPickerOverlay.startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined;
$scope.mediaPickerOverlay.startNodeIsVirtual = $scope.mediaPickerOverlay.startNodeId ? $scope.model.config.startNodeIsVirtual : undefined;
+ $scope.mediaPickerOverlay.ignoreUserStartNodes = ignoreUserStartNodes;
$scope.mediaPickerOverlay.cropSize = $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined;
$scope.mediaPickerOverlay.showDetails = true;
$scope.mediaPickerOverlay.disableFolderSelect = true;
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js
index 397438d5a0..cf81300b32 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/rte.controller.js
@@ -16,6 +16,7 @@
view: "linkpicker",
currentTarget: currentTarget,
anchors: tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)),
+ ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1",
show: true,
submit: function(model) {
tinyMceService.insertLinkInEditor(editor, model.target, anchorElement);
@@ -26,11 +27,23 @@
}
function openMediaPicker(editor, currentTarget, userData) {
+ var ignoreUserStartNodes = false;
+ var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
+ var startNodeIsVirtual = userData.startMediaIds.length !== 1;
+
+ if ($scope.model.config.ignoreUserStartNodes === "1") {
+ ignoreUserStartNodes = true;
+ startNodeId = -1;
+ startNodeIsVirtual = true;
+ }
+
vm.mediaPickerOverlay = {
currentTarget: currentTarget,
onlyImages: true,
- showDetails: true,
- startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0],
+ showDetails: true,
+ startNodeId: startNodeId,
+ startNodeIsVirtual: startNodeIsVirtual,
+ ignoreUserStartNodes: ignoreUserStartNodes,
view: "mediapicker",
show: true,
submit: function(model) {
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js
index 7b8445a4dc..1f1305b0f0 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js
@@ -7,12 +7,19 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false;
var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false;
var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false;
+ var ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false;
if (!$scope.model.config.startNodeId) {
- userService.getCurrentUser().then(function(userData) {
- $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
- $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1;
- });
+ if (ignoreUserStartNodes === true) {
+ $scope.model.config.startNodeId = -1;
+ $scope.model.config.startNodeIsVirtual = true;
+
+ } else {
+ userService.getCurrentUser().then(function (userData) {
+ $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
+ $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1;
+ });
+ }
}
function setupViewModel() {
@@ -105,6 +112,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl
title: "Select media",
startNodeId: $scope.model.config.startNodeId,
startNodeIsVirtual: $scope.model.config.startNodeIsVirtual,
+ ignoreUserStartNodes: ignoreUserStartNodes,
multiPicker: multiPicker,
onlyImages: onlyImages,
disableFolderSelect: disableFolderSelect,
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js
index e4f2ae303f..c7e67a0a42 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js
@@ -67,10 +67,11 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en
url: link.url,
target: link.target
} : null;
-
+
$scope.linkPickerOverlay = {
view: "linkpicker",
currentTarget: target,
+ ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1",
show: true,
submit: function (model) {
if (model.target.url || model.target.anchor) {
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js
index 142298e0d8..6047169c54 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js
@@ -25,6 +25,7 @@
$scope.contentPickerOverlay.view = "contentpicker";
$scope.contentPickerOverlay.multiPicker = false;
$scope.contentPickerOverlay.show = true;
+ $scope.contentPickerOverlay.ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true: false;
$scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int";
$scope.contentPickerOverlay.submit = function(model) {
@@ -50,6 +51,7 @@
$scope.contentPickerOverlay.view = "contentpicker";
$scope.contentPickerOverlay.multiPicker = false;
$scope.contentPickerOverlay.show = true;
+ $scope.contentPickerOverlay.ignoreUserStartNodes = $scope.model.config.ignoreUserStartNodes === "1" ? true : false;
$scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : "int";
$scope.contentPickerOverlay.submit = function(model) {
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js
index 1fc648083e..f8dea2ee8b 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js
@@ -274,6 +274,7 @@ angular.module("umbraco")
view: "linkpicker",
currentTarget: currentTarget,
anchors: editorState.current ? tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)) : [],
+ ignoreUserStartNodes: $scope.model.config.ignoreUserStartNodes === "1",
show: true,
submit: function(model) {
tinyMceService.insertLinkInEditor(editor, model.target, anchorElement);
@@ -285,14 +286,24 @@ angular.module("umbraco")
//Create the insert media plugin
tinyMceService.createMediaPicker(editor, $scope, function(currentTarget, userData){
+ var ignoreUserStartNodes = false;
+ var startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
+ var startNodeIsVirtual = userData.startMediaIds.length !== 1;
+
+ if ($scope.model.config.ignoreUserStartNodes === "1") {
+ ignoreUserStartNodes = true;
+ startNodeId = -1;
+ startNodeIsVirtual = true;
+ }
$scope.mediaPickerOverlay = {
currentTarget: currentTarget,
onlyImages: true,
showDetails: true,
disableFolderSelect: true,
- startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0],
- startNodeIsVirtual: userData.startMediaIds.length !== 1,
+ startNodeId: startNodeId,
+ startNodeIsVirtual: startNodeIsVirtual,
+ ignoreUserStartNodes: ignoreUserStartNodes,
view: "mediapicker",
show: true,
submit: function(model) {
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html
index b9d63df9f3..9d7ff0cf40 100644
--- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html
+++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html
@@ -40,4 +40,5 @@
Pixels
+
\ No newline at end of file
diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs
index 5f3022b2a9..3d1e9d321c 100644
--- a/src/Umbraco.Web/Editors/ContentController.cs
+++ b/src/Umbraco.Web/Editors/ContentController.cs
@@ -1,1219 +1,1227 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Net.Http;
-using System.Text;
-using System.Web.Http;
-using System.Web.Http.Controllers;
-using System.Web.Http.ModelBinding;
-using AutoMapper;
-using Umbraco.Core;
-using Umbraco.Core.Logging;
-using Umbraco.Core.Models;
-using Umbraco.Core.Models.Membership;
-using Umbraco.Core.Persistence.DatabaseModelDefinitions;
-using Umbraco.Core.Publishing;
-using Umbraco.Core.Services;
-using Umbraco.Web.Models.ContentEditing;
-using Umbraco.Web.Models.Mapping;
-using Umbraco.Web.Mvc;
-using Umbraco.Web.WebApi;
-using Umbraco.Web.WebApi.Binders;
-using Umbraco.Web.WebApi.Filters;
-using Umbraco.Core.Events;
-using Constants = Umbraco.Core.Constants;
-using umbraco.cms.businesslogic;
-using System.Collections;
-using umbraco;
-
-namespace Umbraco.Web.Editors
-{
- ///
- /// The API controller used for editing content
- ///
- ///
- /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting
- /// access to ALL of the methods on this controller will need access to the content application.
- ///
- [PluginController("UmbracoApi")]
- [UmbracoApplicationAuthorize(Constants.Applications.Content)]
- [ContentControllerConfiguration]
- public class ContentController : ContentControllerBase
- {
- ///
- /// Constructor
- ///
- public ContentController()
- : this(UmbracoContext.Current)
- {
- }
-
- ///
- /// Constructor
- ///
- ///
- public ContentController(UmbracoContext umbracoContext)
- : base(umbracoContext)
- {
- }
-
- ///
- /// Configures this controller with a custom action selector
- ///
- private class ContentControllerConfigurationAttribute : Attribute, IControllerConfiguration
- {
- public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
- {
- controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector(
- new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetNiceUrl", "id", typeof(int), typeof(Guid), typeof(Udi)),
- new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi))
- ));
- }
- }
-
- ///
- /// Return content for the specified ids
- ///
- ///
- ///
- [FilterAllowedOutgoingContent(typeof(IEnumerable))]
- public IEnumerable GetByIds([FromUri]int[] ids)
- {
- var foundContent = Services.ContentService.GetByIds(ids);
- return foundContent.Select(content => AutoMapperExtensions.MapWithUmbracoContext(content, UmbracoContext));
- }
-
- ///
- /// Updates the permissions for a content item for a particular user group
- ///
- ///
- ///
- ///
- /// Permission check is done for letter 'R' which is for which the user must have access to to update
- ///
- [EnsureUserPermissionForContent("saveModel.ContentId", 'R')]
- public IEnumerable PostSaveUserGroupPermissions(UserGroupPermissionsSave saveModel)
- {
- if (saveModel.ContentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
-
- //TODO: Should non-admins be alowed to set granular permissions?
-
- var content = Services.ContentService.GetById(saveModel.ContentId);
- if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
-
- //current permissions explicitly assigned to this content item
- var contentPermissions = Services.ContentService.GetPermissionsForEntity(content)
- .ToDictionary(x => x.UserGroupId, x => x);
-
- var allUserGroups = Services.UserService.GetAllUserGroups().ToArray();
-
- //loop through each user group
- foreach (var userGroup in allUserGroups)
- {
- //check if there's a permission set posted up for this user group
- IEnumerable groupPermissions;
- if (saveModel.AssignedPermissions.TryGetValue(userGroup.Id, out groupPermissions))
- {
- //create a string collection of the assigned letters
- var groupPermissionCodes = groupPermissions.ToArray();
-
- //check if there are no permissions assigned for this group save model, if that is the case we want to reset the permissions
- //for this group/node which will go back to the defaults
- if (groupPermissionCodes.Length == 0)
- {
- Services.UserService.RemoveUserGroupPermissions(userGroup.Id, content.Id);
- }
- //check if they are the defaults, if so we should just remove them if they exist since it's more overhead having them stored
- else if (userGroup.Permissions.UnsortedSequenceEqual(groupPermissionCodes))
- {
- //only remove them if they are actually currently assigned
- if (contentPermissions.ContainsKey(userGroup.Id))
- {
- //remove these permissions from this node for this group since the ones being assigned are the same as the defaults
- Services.UserService.RemoveUserGroupPermissions(userGroup.Id, content.Id);
- }
- }
- //if they are different we need to update, otherwise there's nothing to update
- else if (contentPermissions.ContainsKey(userGroup.Id) == false || contentPermissions[userGroup.Id].AssignedPermissions.UnsortedSequenceEqual(groupPermissionCodes) == false)
- {
-
- Services.UserService.ReplaceUserGroupPermissions(userGroup.Id, groupPermissionCodes.Select(x => x[0]), content.Id);
- }
- }
- }
-
- return GetDetailedPermissions(content, allUserGroups);
- }
-
- ///
- /// Returns the user group permissions for user groups assigned to this node
- ///
- ///
- ///
- ///
- /// Permission check is done for letter 'R' which is for which the user must have access to to view
- ///
- [EnsureUserPermissionForContent("contentId", 'R')]
- public IEnumerable GetDetailedPermissions(int contentId)
- {
- if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
- var content = Services.ContentService.GetById(contentId);
- if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
-
- //TODO: Should non-admins be able to see detailed permissions?
-
- var allUserGroups = Services.UserService.GetAllUserGroups();
-
- return GetDetailedPermissions(content, allUserGroups);
- }
-
- private IEnumerable GetDetailedPermissions(IContent content, IEnumerable allUserGroups)
- {
- //get all user groups and map their default permissions to the AssignedUserGroupPermissions model.
- //we do this because not all groups will have true assigned permissions for this node so if they don't have assigned permissions, we need to show the defaults.
-
- var defaultPermissionsByGroup = Mapper.Map>(allUserGroups).ToArray();
-
- var defaultPermissionsAsDictionary = defaultPermissionsByGroup
- .ToDictionary(x => Convert.ToInt32(x.Id), x => x);
-
- //get the actual assigned permissions
- var assignedPermissionsByGroup = Services.ContentService.GetPermissionsForEntity(content).ToArray();
-
- //iterate over assigned and update the defaults with the real values
- foreach (var assignedGroupPermission in assignedPermissionsByGroup)
- {
- var defaultUserGroupPermissions = defaultPermissionsAsDictionary[assignedGroupPermission.UserGroupId];
-
- //clone the default permissions model to the assigned ones
- defaultUserGroupPermissions.AssignedPermissions = AssignedUserGroupPermissions.ClonePermissions(defaultUserGroupPermissions.DefaultPermissions);
-
- //since there is custom permissions assigned to this node for this group, we need to clear all of the default permissions
- //and we'll re-check it if it's one of the explicitly assigned ones
- foreach (var permission in defaultUserGroupPermissions.AssignedPermissions.SelectMany(x => x.Value))
- {
- permission.Checked = false;
- permission.Checked = assignedGroupPermission.AssignedPermissions.Contains(permission.PermissionCode, StringComparer.InvariantCulture);
- }
-
- }
-
- return defaultPermissionsByGroup;
- }
-
- ///
- /// Returns an item to be used to display the recycle bin for content
- ///
- ///
- public ContentItemDisplay GetRecycleBin()
- {
- var display = new ContentItemDisplay
- {
- Id = Constants.System.RecycleBinContent,
- Alias = "recycleBin",
- ParentId = -1,
- Name = Services.TextService.Localize("general/recycleBin"),
- ContentTypeAlias = "recycleBin",
- CreateDate = DateTime.Now,
- IsContainer = true,
- Path = "-1," + Constants.System.RecycleBinContent
- };
-
- TabsAndPropertiesResolver.AddListView(display, "content", Services.DataTypeService, Services.TextService);
-
- return display;
- }
-
- public ContentItemDisplay GetBlueprintById(int id)
- {
- var foundContent = Services.ContentService.GetBlueprintById(id);
- if (foundContent == null)
- {
- HandleContentNotFound(id);
- }
-
- var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext);
-
- SetupBlueprint(content, foundContent);
-
- return content;
- }
-
- private static void SetupBlueprint(ContentItemDisplay content, IContent persistedContent)
- {
- content.AllowPreview = false;
-
- //set a custom path since the tree that renders this has the content type id as the parent
- content.Path = string.Format("-1,{0},{1}", persistedContent.ContentTypeId, content.Id);
-
- content.AllowedActions = new[] { "A" };
- content.IsBlueprint = true;
-
- var excludeProps = new[] { "_umb_urls", "_umb_releasedate", "_umb_expiredate", "_umb_template" };
- var propsTab = content.Tabs.Last();
- propsTab.Properties = propsTab.Properties
- .Where(p => excludeProps.Contains(p.Alias) == false);
- }
-
- ///
- /// Gets the content json for the content id
- ///
- ///
- ///
- [OutgoingEditorModelEvent]
- [EnsureUserPermissionForContent("id")]
- public ContentItemDisplay GetById(int id)
- {
- var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));
- if (foundContent == null)
- {
- HandleContentNotFound(id);
- }
-
- var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext);
- return content;
- }
-
- ///
- /// Gets the content json for the content id
- ///
- ///
- ///
- [OutgoingEditorModelEvent]
- [EnsureUserPermissionForContent("id")]
- public ContentItemDisplay GetById(Guid id)
- {
- var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));
- if (foundContent == null)
- {
- HandleContentNotFound(id);
- }
-
- var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext);
- return content;
- }
-
- ///
- /// Gets the content json for the content id
- ///
- ///
- ///
- [OutgoingEditorModelEvent]
- [EnsureUserPermissionForContent("id")]
- public ContentItemDisplay GetById(Udi id)
- {
- var guidUdi = id as GuidUdi;
- if (guidUdi != null)
- {
- return GetById(guidUdi.Guid);
- }
-
- throw new HttpResponseException(HttpStatusCode.NotFound);
- }
-
- [EnsureUserPermissionForContent("id")]
- public ContentItemDisplay GetWithTreeDefinition(int id)
- {
- var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));
- if (foundContent == null)
- {
- HandleContentNotFound(id);
- }
-
- var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext);
- return content;
- }
-
- ///
- /// Gets an empty content item for the
- ///
- ///
- ///
- ///
- /// If this is a container type, we'll remove the umbContainerView tab for a new item since
- /// it cannot actually list children if it doesn't exist yet.
- ///
- [OutgoingEditorModelEvent]
- public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId)
- {
- var contentType = Services.ContentTypeService.GetContentType(contentTypeAlias);
- if (contentType == null)
- {
- throw new HttpResponseException(HttpStatusCode.NotFound);
- }
-
- var emptyContent = Services.ContentService.CreateContent("", parentId, contentType.Alias, UmbracoUser.Id);
- var mapped = AutoMapperExtensions.MapWithUmbracoContext(emptyContent, UmbracoContext);
- // translate the content type name if applicable
- mapped.ContentTypeName = Services.TextService.UmbracoDictionaryTranslate(mapped.ContentTypeName);
- // if your user type doesn't have access to the Settings section it would not get this property mapped
- if(mapped.DocumentType != null)
- mapped.DocumentType.Name = Services.TextService.UmbracoDictionaryTranslate(mapped.DocumentType.Name);
-
- //remove this tab if it exists: umbContainerView
- var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName);
- mapped.Tabs = mapped.Tabs.Except(new[] { containerTab });
- return mapped;
- }
-
- [OutgoingEditorModelEvent]
- public ContentItemDisplay GetEmpty(int blueprintId, int parentId)
- {
- var blueprint = Services.ContentService.GetBlueprintById(blueprintId);
- if (blueprint == null)
- {
- throw new HttpResponseException(HttpStatusCode.NotFound);
- }
-
- blueprint.Id = 0;
- blueprint.Name = string.Empty;
- blueprint.ParentId = parentId;
-
- var mapped = Mapper.Map(blueprint);
-
- //remove this tab if it exists: umbContainerView
- var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName);
- mapped.Tabs = mapped.Tabs.Except(new[] { containerTab });
- return mapped;
- }
-
- ///
- /// Gets the Url for a given node ID
- ///
- ///
- ///
- public HttpResponseMessage GetNiceUrl(int id)
- {
- var url = Umbraco.NiceUrl(id);
- var response = Request.CreateResponse(HttpStatusCode.OK);
- response.Content = new StringContent(url, Encoding.UTF8, "application/json");
- return response;
- }
-
- ///
- /// Gets the Url for a given node ID
- ///
- ///
- ///
- public HttpResponseMessage GetNiceUrl(Guid id)
- {
- var url = Umbraco.UrlProvider.GetUrl(id);
- var response = Request.CreateResponse(HttpStatusCode.OK);
- response.Content = new StringContent(url, Encoding.UTF8, "application/json");
- return response;
- }
-
- ///
- /// Gets the Url for a given node ID
- ///
- ///
- ///
- public HttpResponseMessage GetNiceUrl(Udi id)
- {
- var guidUdi = id as GuidUdi;
- if (guidUdi != null)
- {
- return GetNiceUrl(guidUdi.Guid);
- }
- throw new HttpResponseException(HttpStatusCode.NotFound);
- }
-
- ///
- /// Gets the children for the content id passed in
- ///
- ///
- [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")]
- public PagedResult> GetChildren(
- int id,
- int pageNumber = 0, //TODO: This should be '1' as it's not the index
- int pageSize = 0,
- string orderBy = "SortOrder",
- Direction orderDirection = Direction.Ascending,
- bool orderBySystemField = true,
- string filter = "")
- {
- return GetChildren(id, null, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter);
- }
-
- ///
- /// Gets the children for the content id passed in
- ///
- ///
- [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")]
- public PagedResult> GetChildren(
- int id,
- string includeProperties,
- int pageNumber = 0, //TODO: This should be '1' as it's not the index
- int pageSize = 0,
- string orderBy = "SortOrder",
- Direction orderDirection = Direction.Ascending,
- bool orderBySystemField = true,
- string filter = "")
- {
- long totalChildren;
- IContent[] children;
- if (pageNumber > 0 && pageSize > 0)
- {
- children = Services.ContentService
- .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren
- , orderBy, orderDirection, orderBySystemField, filter).ToArray();
- }
- else
- {
- children = Services.ContentService.GetChildren(id).ToArray();
- totalChildren = children.Length;
- }
-
- if (totalChildren == 0)
- {
- return new PagedResult>(0, 0, 0);
- }
-
- var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize);
- pagedResult.Items = children.Select(content =>
- Mapper.Map>(content,
- opts =>
- {
- // if there's a list of property aliases to map - we will make sure to store this in the mapping context.
- if (String.IsNullOrWhiteSpace(includeProperties) == false)
- {
- opts.Items["IncludeProperties"] = includeProperties.Split(new[] { ", ", "," }, StringSplitOptions.RemoveEmptyEntries);
- }
- }));
-
- return pagedResult;
- }
-
- [Obsolete("Dont use this, it is incorrectly named, use HasPermission instead")]
- public bool GetHasPermission(string permissionToCheck, int nodeId)
- {
- return HasPermission(permissionToCheck, nodeId);
- }
-
- ///
- /// Returns permissions for all nodes passed in for the current user
- /// TODO: This should be moved to the CurrentUserController?
- ///
- ///
- ///
- [HttpPost]
- public Dictionary GetPermissions(int[] nodeIds)
- {
- var permissions = Services.UserService
- .GetPermissions(Security.CurrentUser, nodeIds);
-
- var permissionsDictionary = new Dictionary();
- foreach (var nodeId in nodeIds)
- {
- var aggregatePerms = permissions.GetAllPermissions(nodeId).ToArray();
- permissionsDictionary.Add(nodeId, aggregatePerms);
- }
-
- return permissionsDictionary;
- }
-
- ///
- /// Checks a nodes permission for the current user
- /// TODO: This should be moved to the CurrentUserController?
- ///
- ///
- ///
- ///
- [HttpGet]
- public bool HasPermission(string permissionToCheck, int nodeId)
- {
- var p = Services.UserService.GetPermissions(Security.CurrentUser, nodeId).GetAllPermissions();
- if (p.Contains(permissionToCheck.ToString(CultureInfo.InvariantCulture)))
- {
- return true;
- }
-
- return false;
- }
-
- ///
- /// Creates a blueprint from a content item
- ///
- /// The content id to copy
- /// The name of the blueprint
- ///
- [HttpPost]
- public SimpleNotificationModel CreateBlueprintFromContent([FromUri]int contentId, [FromUri]string name)
- {
- if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name");
-
- var content = Services.ContentService.GetById(contentId);
- if (content == null)
- throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
-
- EnsureUniqueName(name, content, "name");
-
- var blueprint = Services.ContentService.CreateContentFromBlueprint(content, name, Security.CurrentUser.Id);
-
- Services.ContentService.SaveBlueprint(blueprint, Security.CurrentUser.Id);
-
- var notificationModel = new SimpleNotificationModel();
- notificationModel.AddSuccessNotification(
- Services.TextService.Localize("blueprints/createdBlueprintHeading"),
- Services.TextService.Localize("blueprints/createdBlueprintMessage", new[] { content.Name })
- );
-
- return notificationModel;
- }
-
- private void EnsureUniqueName(string name, IContent content, string modelName)
- {
- var existing = Services.ContentService.GetBlueprintsForContentTypes(content.ContentTypeId);
- if (existing.Any(x => x.Name == name && x.Id != content.Id))
- {
- ModelState.AddModelError(modelName, Services.TextService.Localize("blueprints/duplicateBlueprintMessage"));
- throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState));
- }
- }
-
- ///
- /// Saves content
- ///
- ///
- [FileUploadCleanupFilter]
- [ContentPostValidate]
- public ContentItemDisplay PostSaveBlueprint(
- [ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem)
- {
- var contentItemDisplay = PostSaveInternal(contentItem,
- content =>
- {
- EnsureUniqueName(content.Name, content, "Name");
-
- Services.ContentService.SaveBlueprint(contentItem.PersistedContent, Security.CurrentUser.Id);
- //we need to reuse the underlying logic so return the result that it wants
- return Attempt.Succeed(new OperationStatus(OperationStatusType.Success, new EventMessages()));
- });
- SetupBlueprint(contentItemDisplay, contentItemDisplay.PersistedContent);
-
- return contentItemDisplay;
- }
-
- ///
- /// Saves content
- ///
- ///
- [FileUploadCleanupFilter]
- [ContentPostValidate]
- [OutgoingEditorModelEvent]
- public ContentItemDisplay PostSave(
- [ModelBinder(typeof(ContentItemBinder))]
- ContentItemSave contentItem)
- {
- return PostSaveInternal(contentItem,
- content => Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id));
- }
-
- private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func> saveMethod)
- {
- //Recent versions of IE/Edge may send in the full clientside file path instead of just the file name.
- //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all
- //uploaded files to being *only* the actual file name (as it should be).
- if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any())
- {
- foreach (var file in contentItem.UploadedFiles)
- {
- file.FileName = Path.GetFileName(file.FileName);
- }
- }
-
- //If we've reached here it means:
- // * Our model has been bound
- // * and validated
- // * any file attachments have been saved to their temporary location for us to use
- // * we have a reference to the DTO object and the persisted object
- // * Permissions are valid
- MapPropertyValues(contentItem);
-
- //We need to manually check the validation results here because:
- // * We still need to save the entity even if there are validation value errors
- // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null)
- // then we cannot continue saving, we can only display errors
- // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display
- // a message indicating this
- if (ModelState.IsValid == false)
- {
- if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) && IsCreatingAction(contentItem.Action))
- {
- //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue!
- // add the modelstate to the outgoing object and throw a validation message
- var forDisplay = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext);
- forDisplay.Errors = ModelState.ToErrorDictionary();
- throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
-
- }
-
- //if the model state is not valid we cannot publish so change it to save
- switch (contentItem.Action)
- {
- case ContentSaveAction.Publish:
- contentItem.Action = ContentSaveAction.Save;
- break;
- case ContentSaveAction.PublishNew:
- contentItem.Action = ContentSaveAction.SaveNew;
- break;
- }
- }
-
- //initialize this to successful
- var publishStatus = Attempt.Succeed();
- var wasCancelled = false;
-
- if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew)
- {
- //save the item
- var saveResult = saveMethod(contentItem.PersistedContent);
-
- wasCancelled = saveResult.Success == false && saveResult.Result.StatusType == OperationStatusType.FailedCancelledByEvent;
- }
- else if (contentItem.Action == ContentSaveAction.SendPublish || contentItem.Action == ContentSaveAction.SendPublishNew)
- {
- var sendResult = Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id);
- wasCancelled = sendResult == false;
- }
- else
- {
- //publish the item and check if it worked, if not we will show a diff msg below
- publishStatus = Services.ContentService.SaveAndPublishWithStatus(contentItem.PersistedContent, Security.CurrentUser.Id);
- wasCancelled = publishStatus.Result.StatusType == PublishStatusType.FailedCancelledByEvent;
- }
-
- //return the updated model
- var display = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext);
-
- //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403
- HandleInvalidModelState(display);
-
- //put the correct msgs in
- switch (contentItem.Action)
- {
- case ContentSaveAction.Save:
- case ContentSaveAction.SaveNew:
- if (wasCancelled == false)
- {
- display.AddSuccessNotification(
- Services.TextService.Localize("speechBubbles/editContentSavedHeader"),
- contentItem.ReleaseDate.HasValue
- ? Services.TextService.Localize("speechBubbles/editContentSavedWithReleaseDateText", new [] { contentItem.ReleaseDate.Value.ToLongDateString(), contentItem.ReleaseDate.Value.ToShortTimeString() })
- : Services.TextService.Localize("speechBubbles/editContentSavedText")
- );
- }
- else
- {
- AddCancelMessage(display);
- }
- break;
- case ContentSaveAction.SendPublish:
- case ContentSaveAction.SendPublishNew:
- if (wasCancelled == false)
- {
- display.AddSuccessNotification(
- Services.TextService.Localize("speechBubbles/editContentSendToPublish"),
- Services.TextService.Localize("speechBubbles/editContentSendToPublishText"));
- }
- else
- {
- AddCancelMessage(display);
- }
- break;
- case ContentSaveAction.Publish:
- case ContentSaveAction.PublishNew:
- ShowMessageForPublishStatus(publishStatus.Result, display, contentItem.ExpireDate);
- break;
- }
-
- //If the item is new and the operation was cancelled, we need to return a different
- // status code so the UI can handle it since it won't be able to redirect since there
- // is no Id to redirect to!
- if (wasCancelled && IsCreatingAction(contentItem.Action))
- {
- throw new HttpResponseException(Request.CreateValidationErrorResponse(display));
- }
-
- display.PersistedContent = contentItem.PersistedContent;
-
- return display;
- }
-
- ///
- /// Publishes a document with a given ID
- ///
- ///
- ///
- ///
- /// The CanAccessContentAuthorize attribute will deny access to this method if the current user
- /// does not have Publish access to this node.
- ///
- ///
- [EnsureUserPermissionForContent("id", 'U')]
- public HttpResponseMessage PostPublishById(int id)
- {
- var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));
-
- if (foundContent == null)
- {
- return HandleContentNotFound(id, false);
- }
-
- var publishResult = Services.ContentService.PublishWithStatus(foundContent, Security.CurrentUser.Id);
- if (publishResult.Success == false)
- {
- var notificationModel = new SimpleNotificationModel();
- ShowMessageForPublishStatus(publishResult.Result, notificationModel, foundContent.ExpireDate);
- return Request.CreateValidationErrorResponse(notificationModel);
- }
-
- //return ok
- return Request.CreateResponse(HttpStatusCode.OK);
-
- }
-
- [HttpDelete]
- [HttpPost]
- public HttpResponseMessage DeleteBlueprint(int id)
- {
- var found = Services.ContentService.GetBlueprintById(id);
-
- if (found == null)
- {
- return HandleContentNotFound(id, false);
- }
-
- Services.ContentService.DeleteBlueprint(found);
-
- return Request.CreateResponse(HttpStatusCode.OK);
- }
-
- ///
- /// Moves an item to the recycle bin, if it is already there then it will permanently delete it
- ///
- ///
- ///
- ///
- /// The CanAccessContentAuthorize attribute will deny access to this method if the current user
- /// does not have Delete access to this node.
- ///
- [EnsureUserPermissionForContent("id", 'D')]
- [HttpDelete]
- [HttpPost]
- public HttpResponseMessage DeleteById(int id)
- {
- var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));
-
- if (foundContent == null)
- {
- return HandleContentNotFound(id, false);
- }
-
- //if the current item is in the recycle bin
- if (foundContent.IsInRecycleBin() == false)
- {
- var moveResult = Services.ContentService.WithResult().MoveToRecycleBin(foundContent, Security.CurrentUser.Id);
- if (moveResult == false)
- {
- //returning an object of INotificationModel will ensure that any pending
- // notification messages are added to the response.
- return Request.CreateValidationErrorResponse(new SimpleNotificationModel());
- }
- }
- else
- {
- var deleteResult = Services.ContentService.WithResult().Delete(foundContent, Security.CurrentUser.Id);
- if (deleteResult == false)
- {
- //returning an object of INotificationModel will ensure that any pending
- // notification messages are added to the response.
- return Request.CreateValidationErrorResponse(new SimpleNotificationModel());
- }
- }
-
- return Request.CreateResponse(HttpStatusCode.OK);
- }
-
- ///
- /// Empties the recycle bin
- ///
- ///
- ///
- /// attributed with EnsureUserPermissionForContent to verify the user has access to the recycle bin
- ///
- [HttpDelete]
- [HttpPost]
- [EnsureUserPermissionForContent(Constants.System.RecycleBinContent, 'D')]
- public HttpResponseMessage EmptyRecycleBin()
- {
- Services.ContentService.EmptyRecycleBin();
-
- return Request.CreateNotificationSuccessResponse(Services.TextService.Localize("defaultdialogs/recycleBinIsEmpty"));
- }
-
- ///
- /// Change the sort order for content
- ///
- ///
- ///
- [EnsureUserPermissionForContent("sorted.ParentId", 'S')]
- public HttpResponseMessage PostSort(ContentSortOrder sorted)
- {
- if (sorted == null)
- {
- return Request.CreateResponse(HttpStatusCode.NotFound);
- }
-
- //if there's nothing to sort just return ok
- if (sorted.IdSortOrder.Length == 0)
- {
- return Request.CreateResponse(HttpStatusCode.OK);
- }
-
- try
- {
- var contentService = Services.ContentService;
-
- // Save content with new sort order and update content xml in db accordingly
- if (contentService.Sort(sorted.IdSortOrder, Security.CurrentUser.Id) == false)
- {
- LogHelper.Warn("Content sorting failed, this was probably caused by an event being cancelled");
- return Request.CreateValidationErrorResponse("Content sorting failed, this was probably caused by an event being cancelled");
- }
- return Request.CreateResponse(HttpStatusCode.OK);
- }
- catch (Exception ex)
- {
- LogHelper.Error("Could not update content sort order", ex);
- throw;
- }
- }
-
- ///
- /// Change the sort order for media
- ///
- ///
- ///
- [EnsureUserPermissionForContent("move.ParentId", 'M')]
- public HttpResponseMessage PostMove(MoveOrCopy move)
- {
- var toMove = ValidateMoveOrCopy(move);
-
- Services.ContentService.Move(toMove, move.ParentId, Security.CurrentUser.Id);
-
- var response = Request.CreateResponse(HttpStatusCode.OK);
- response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json");
- return response;
- }
-
- ///
- /// Copies a content item and places the copy as a child of a given parent Id
- ///
- ///
- ///
- [EnsureUserPermissionForContent("copy.ParentId", 'C')]
- public HttpResponseMessage PostCopy(MoveOrCopy copy)
- {
- var toCopy = ValidateMoveOrCopy(copy);
-
- var c = Services.ContentService.Copy(toCopy, copy.ParentId, copy.RelateToOriginal, copy.Recursive, Security.CurrentUser.Id);
-
- var response = Request.CreateResponse(HttpStatusCode.OK);
- response.Content = new StringContent(c.Path, Encoding.UTF8, "application/json");
- return response;
- }
-
- ///
- /// Unpublishes a node with a given Id and returns the unpublished entity
- ///
- ///
- ///
- [EnsureUserPermissionForContent("id", 'U')]
- [OutgoingEditorModelEvent]
- public ContentItemDisplay PostUnPublish(int id)
- {
- var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));
-
- if (foundContent == null)
- HandleContentNotFound(id);
-
- var unpublishResult = Services.ContentService.WithResult().UnPublish(foundContent, Security.CurrentUser.Id);
-
- var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext);
-
- if (unpublishResult == false)
- {
- AddCancelMessage(content);
- throw new HttpResponseException(Request.CreateValidationErrorResponse(content));
- }
- else
- {
- content.AddSuccessNotification(Services.TextService.Localize("content/unPublish"), Services.TextService.Localize("speechBubbles/contentUnpublished"));
- return content;
- }
- }
-
- ///
- /// Maps the dto property values to the persisted model
- ///
- ///
- private void MapPropertyValues(ContentItemSave contentItem)
- {
- UpdateName(contentItem);
-
- //TODO: We need to support 'send to publish'
-
- contentItem.PersistedContent.ExpireDate = contentItem.ExpireDate;
- contentItem.PersistedContent.ReleaseDate = contentItem.ReleaseDate;
- //only set the template if it didn't change
- var templateChanged = (contentItem.PersistedContent.Template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false)
- || (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias)
- || (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace());
- if (templateChanged)
- {
- var template = Services.FileService.GetTemplate(contentItem.TemplateAlias);
- if (template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false)
- {
- //ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias);
- LogHelper.Warn("No template exists with the specified alias: " + contentItem.TemplateAlias);
- }
- else
- {
- //NOTE: this could be null if there was a template and the posted template is null, this should remove the assigned template
- contentItem.PersistedContent.Template = template;
- }
- }
-
- base.MapPropertyValues(contentItem);
- }
-
- ///
- /// Ensures the item can be moved/copied to the new location
- ///
- ///
- ///
- private IContent ValidateMoveOrCopy(MoveOrCopy model)
- {
- if (model == null)
- {
- throw new HttpResponseException(HttpStatusCode.NotFound);
- }
-
- var contentService = Services.ContentService;
- var toMove = contentService.GetById(model.Id);
- if (toMove == null)
- {
- throw new HttpResponseException(HttpStatusCode.NotFound);
- }
- if (model.ParentId < 0)
- {
- //cannot move if the content item is not allowed at the root
- if (toMove.ContentType.AllowedAsRoot == false)
- {
- throw new HttpResponseException(
- Request.CreateNotificationValidationErrorResponse(
- Services.TextService.Localize("moveOrCopy/notAllowedAtRoot")));
- }
- }
- else
- {
- var parent = contentService.GetById(model.ParentId);
- if (parent == null)
- {
- throw new HttpResponseException(HttpStatusCode.NotFound);
- }
-
- //check if the item is allowed under this one
- if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray()
- .Any(x => x.Value == toMove.ContentType.Id) == false)
- {
- throw new HttpResponseException(
- Request.CreateNotificationValidationErrorResponse(
- Services.TextService.Localize("moveOrCopy/notAllowedByContentType")));
- }
-
- // Check on paths
- if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1)
- {
- throw new HttpResponseException(
- Request.CreateNotificationValidationErrorResponse(
- Services.TextService.Localize("moveOrCopy/notAllowedByPath")));
- }
- }
-
- return toMove;
- }
-
- private void ShowMessageForPublishStatus(PublishStatus status, INotificationModel display, DateTime? expireDate)
- {
- switch (status.StatusType)
- {
- case PublishStatusType.Success:
- case PublishStatusType.SuccessAlreadyPublished:
- display.AddSuccessNotification(
- Services.TextService.Localize("speechBubbles/editContentPublishedHeader"),
- expireDate.HasValue
- ? Services.TextService.Localize("speechBubbles/editContentPublishedWithExpireDateText", new [] { expireDate.Value.ToLongDateString(), expireDate.Value.ToShortTimeString() })
- : Services.TextService.Localize("speechBubbles/editContentPublishedText")
- );
- break;
- case PublishStatusType.FailedPathNotPublished:
- display.AddWarningNotification(
- Services.TextService.Localize("publish"),
- Services.TextService.Localize("publish/contentPublishedFailedByParent",
- new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim());
- break;
- case PublishStatusType.FailedCancelledByEvent:
- AddCancelMessage(display, "publish", "speechBubbles/contentPublishedFailedByEvent");
- break;
- case PublishStatusType.FailedAwaitingRelease:
- display.AddWarningNotification(
- Services.TextService.Localize("publish"),
- Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease",
- new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim());
- break;
- case PublishStatusType.FailedHasExpired:
- display.AddWarningNotification(
- Services.TextService.Localize("publish"),
- Services.TextService.Localize("publish/contentPublishedFailedExpired",
- new[]
- {
- string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id),
- }).Trim());
- break;
- case PublishStatusType.FailedIsTrashed:
- //TODO: We should add proper error messaging for this!
- break;
- case PublishStatusType.FailedContentInvalid:
- display.AddWarningNotification(
- Services.TextService.Localize("publish"),
- Services.TextService.Localize("publish/contentPublishedFailedInvalid",
- new[]
- {
- string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id),
- string.Join(",", status.InvalidProperties.Select(x => x.Alias))
- }).Trim());
- break;
- default:
- throw new IndexOutOfRangeException();
- }
- }
-
-
- ///
- /// Performs a permissions check for the user to check if it has access to the node based on
- /// start node and/or permissions for the node
- ///
- /// The storage to add the content item to so it can be reused
- ///
- ///
- ///
- ///
- /// The content to lookup, if the contentItem is not specified
- ///
- /// Specifies the already resolved content item to check against
- ///
- internal static bool CheckPermissions(
- IDictionary storage,
- IUser user,
- IUserService userService,
- IContentService contentService,
- IEntityService entityService,
- int nodeId,
- char[] permissionsToCheck = null,
- IContent contentItem = null)
- {
- if (storage == null) throw new ArgumentNullException("storage");
- if (user == null) throw new ArgumentNullException("user");
- if (userService == null) throw new ArgumentNullException("userService");
- if (contentService == null) throw new ArgumentNullException("contentService");
- if (entityService == null) throw new ArgumentNullException("entityService");
-
- if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent)
- {
- contentItem = contentService.GetById(nodeId);
- //put the content item into storage so it can be retreived
- // in the controller (saves a lookup)
- storage[typeof(IContent).ToString()] = contentItem;
- }
-
- if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent)
- {
- throw new HttpResponseException(HttpStatusCode.NotFound);
- }
-
- var hasPathAccess = (nodeId == Constants.System.Root)
- ? user.HasContentRootAccess(entityService)
- : (nodeId == Constants.System.RecycleBinContent)
- ? user.HasContentBinAccess(entityService)
- : user.HasPathAccess(contentItem, entityService);
-
- if (hasPathAccess == false)
- {
- return false;
- }
-
- if (permissionsToCheck == null || permissionsToCheck.Length == 0)
- {
- return true;
- }
-
- //get the implicit/inherited permissions for the user for this path,
- //if there is no content item for this id, than just use the id as the path (i.e. -1 or -20)
- var path = contentItem != null ? contentItem.Path : nodeId.ToString();
- var permission = userService.GetPermissionsForPath(user, path);
-
- var allowed = true;
- foreach (var p in permissionsToCheck)
- {
- if (permission == null
- || permission.GetAllPermissions().Contains(p.ToString(CultureInfo.InvariantCulture)) == false)
- {
- allowed = false;
- }
- }
- return allowed;
- }
-
- [EnsureUserPermissionForContent("contentId", 'F')]
- public IEnumerable GetNotificationOptions(int contentId)
- {
- var notifications = new List();
- if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
-
- var content = Services.ContentService.GetById(contentId);
- if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
-
- var actionList = ActionsResolver.Current.Actions;
- foreach (var a in actionList)
- {
- if (a.ShowInNotifier)
- {
- NotifySetting n = new NotifySetting
- {
- Name = ui.Text("actions", a.Alias),
- Checked = (UmbracoUser.GetNotifications(content.Path).IndexOf(a.Letter) > -1),
- NotifyCode = a.Letter.ToString()
- };
- notifications.Add(n);
- }
- }
- return notifications;
- }
-
- public void PostNotificationOptions(int contentId, string notifyOptions = "")
- {
- if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
- var content = Services.ContentService.GetById(contentId);
-
- if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
- var node = new CMSNode(contentId);
-
- global::umbraco.cms.businesslogic.workflow.Notification.UpdateNotifications(UmbracoUser, node, notifyOptions ?? "");
- }
- }
-}
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Web.Http;
+using System.Web.Http.Controllers;
+using System.Web.Http.ModelBinding;
+using AutoMapper;
+using Umbraco.Core;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.Membership;
+using Umbraco.Core.Persistence.DatabaseModelDefinitions;
+using Umbraco.Core.Publishing;
+using Umbraco.Core.Services;
+using Umbraco.Web.Models.ContentEditing;
+using Umbraco.Web.Models.Mapping;
+using Umbraco.Web.Mvc;
+using Umbraco.Web.WebApi;
+using Umbraco.Web.WebApi.Binders;
+using Umbraco.Web.WebApi.Filters;
+using Umbraco.Core.Events;
+using Constants = Umbraco.Core.Constants;
+using umbraco.cms.businesslogic;
+using System.Collections;
+using umbraco;
+
+namespace Umbraco.Web.Editors
+{
+ ///
+ /// The API controller used for editing content
+ ///
+ ///
+ /// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting
+ /// access to ALL of the methods on this controller will need access to the content application.
+ ///
+ [PluginController("UmbracoApi")]
+ [UmbracoApplicationAuthorize(Constants.Applications.Content)]
+ [ContentControllerConfiguration]
+ public class ContentController : ContentControllerBase
+ {
+ ///
+ /// Constructor
+ ///
+ public ContentController()
+ : this(UmbracoContext.Current)
+ {
+ }
+
+ ///
+ /// Constructor
+ ///
+ ///
+ public ContentController(UmbracoContext umbracoContext)
+ : base(umbracoContext)
+ {
+ }
+
+ ///
+ /// Configures this controller with a custom action selector
+ ///
+ private class ContentControllerConfigurationAttribute : Attribute, IControllerConfiguration
+ {
+ public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
+ {
+ controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector(
+ new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetNiceUrl", "id", typeof(int), typeof(Guid), typeof(Udi)),
+ new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi))
+ ));
+ }
+ }
+
+ ///
+ /// Return content for the specified ids
+ ///
+ ///
+ ///
+ [FilterAllowedOutgoingContent(typeof(IEnumerable))]
+ public IEnumerable GetByIds([FromUri]int[] ids)
+ {
+ var foundContent = Services.ContentService.GetByIds(ids);
+ return foundContent.Select(content => AutoMapperExtensions.MapWithUmbracoContext(content, UmbracoContext));
+ }
+
+ ///
+ /// Updates the permissions for a content item for a particular user group
+ ///
+ ///
+ ///
+ ///
+ /// Permission check is done for letter 'R' which is for which the user must have access to to update
+ ///
+ [EnsureUserPermissionForContent("saveModel.ContentId", 'R')]
+ public IEnumerable PostSaveUserGroupPermissions(UserGroupPermissionsSave saveModel)
+ {
+ if (saveModel.ContentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
+
+ //TODO: Should non-admins be alowed to set granular permissions?
+
+ var content = Services.ContentService.GetById(saveModel.ContentId);
+ if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
+
+ //current permissions explicitly assigned to this content item
+ var contentPermissions = Services.ContentService.GetPermissionsForEntity(content)
+ .ToDictionary(x => x.UserGroupId, x => x);
+
+ var allUserGroups = Services.UserService.GetAllUserGroups().ToArray();
+
+ //loop through each user group
+ foreach (var userGroup in allUserGroups)
+ {
+ //check if there's a permission set posted up for this user group
+ IEnumerable groupPermissions;
+ if (saveModel.AssignedPermissions.TryGetValue(userGroup.Id, out groupPermissions))
+ {
+ //create a string collection of the assigned letters
+ var groupPermissionCodes = groupPermissions.ToArray();
+
+ //check if there are no permissions assigned for this group save model, if that is the case we want to reset the permissions
+ //for this group/node which will go back to the defaults
+ if (groupPermissionCodes.Length == 0)
+ {
+ Services.UserService.RemoveUserGroupPermissions(userGroup.Id, content.Id);
+ }
+ //check if they are the defaults, if so we should just remove them if they exist since it's more overhead having them stored
+ else if (userGroup.Permissions.UnsortedSequenceEqual(groupPermissionCodes))
+ {
+ //only remove them if they are actually currently assigned
+ if (contentPermissions.ContainsKey(userGroup.Id))
+ {
+ //remove these permissions from this node for this group since the ones being assigned are the same as the defaults
+ Services.UserService.RemoveUserGroupPermissions(userGroup.Id, content.Id);
+ }
+ }
+ //if they are different we need to update, otherwise there's nothing to update
+ else if (contentPermissions.ContainsKey(userGroup.Id) == false || contentPermissions[userGroup.Id].AssignedPermissions.UnsortedSequenceEqual(groupPermissionCodes) == false)
+ {
+
+ Services.UserService.ReplaceUserGroupPermissions(userGroup.Id, groupPermissionCodes.Select(x => x[0]), content.Id);
+ }
+ }
+ }
+
+ return GetDetailedPermissions(content, allUserGroups);
+ }
+
+ ///
+ /// Returns the user group permissions for user groups assigned to this node
+ ///
+ ///
+ ///
+ ///
+ /// Permission check is done for letter 'R' which is for which the user must have access to to view
+ ///
+ [EnsureUserPermissionForContent("contentId", 'R')]
+ public IEnumerable GetDetailedPermissions(int contentId)
+ {
+ if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
+ var content = Services.ContentService.GetById(contentId);
+ if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
+
+ //TODO: Should non-admins be able to see detailed permissions?
+
+ var allUserGroups = Services.UserService.GetAllUserGroups();
+
+ return GetDetailedPermissions(content, allUserGroups);
+ }
+
+ private IEnumerable GetDetailedPermissions(IContent content, IEnumerable allUserGroups)
+ {
+ //get all user groups and map their default permissions to the AssignedUserGroupPermissions model.
+ //we do this because not all groups will have true assigned permissions for this node so if they don't have assigned permissions, we need to show the defaults.
+
+ var defaultPermissionsByGroup = Mapper.Map>(allUserGroups).ToArray();
+
+ var defaultPermissionsAsDictionary = defaultPermissionsByGroup
+ .ToDictionary(x => Convert.ToInt32(x.Id), x => x);
+
+ //get the actual assigned permissions
+ var assignedPermissionsByGroup = Services.ContentService.GetPermissionsForEntity(content).ToArray();
+
+ //iterate over assigned and update the defaults with the real values
+ foreach (var assignedGroupPermission in assignedPermissionsByGroup)
+ {
+ var defaultUserGroupPermissions = defaultPermissionsAsDictionary[assignedGroupPermission.UserGroupId];
+
+ //clone the default permissions model to the assigned ones
+ defaultUserGroupPermissions.AssignedPermissions = AssignedUserGroupPermissions.ClonePermissions(defaultUserGroupPermissions.DefaultPermissions);
+
+ //since there is custom permissions assigned to this node for this group, we need to clear all of the default permissions
+ //and we'll re-check it if it's one of the explicitly assigned ones
+ foreach (var permission in defaultUserGroupPermissions.AssignedPermissions.SelectMany(x => x.Value))
+ {
+ permission.Checked = false;
+ permission.Checked = assignedGroupPermission.AssignedPermissions.Contains(permission.PermissionCode, StringComparer.InvariantCulture);
+ }
+
+ }
+
+ return defaultPermissionsByGroup;
+ }
+
+ ///
+ /// Returns an item to be used to display the recycle bin for content
+ ///
+ ///
+ public ContentItemDisplay GetRecycleBin()
+ {
+ var display = new ContentItemDisplay
+ {
+ Id = Constants.System.RecycleBinContent,
+ Alias = "recycleBin",
+ ParentId = -1,
+ Name = Services.TextService.Localize("general/recycleBin"),
+ ContentTypeAlias = "recycleBin",
+ CreateDate = DateTime.Now,
+ IsContainer = true,
+ Path = "-1," + Constants.System.RecycleBinContent
+ };
+
+ TabsAndPropertiesResolver.AddListView(display, "content", Services.DataTypeService, Services.TextService);
+
+ return display;
+ }
+
+ public ContentItemDisplay GetBlueprintById(int id)
+ {
+ var foundContent = Services.ContentService.GetBlueprintById(id);
+ if (foundContent == null)
+ {
+ HandleContentNotFound(id);
+ }
+
+ var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext);
+
+ SetupBlueprint(content, foundContent);
+
+ return content;
+ }
+
+ private static void SetupBlueprint(ContentItemDisplay content, IContent persistedContent)
+ {
+ content.AllowPreview = false;
+
+ //set a custom path since the tree that renders this has the content type id as the parent
+ content.Path = string.Format("-1,{0},{1}", persistedContent.ContentTypeId, content.Id);
+
+ content.AllowedActions = new[] { "A" };
+ content.IsBlueprint = true;
+
+ var excludeProps = new[] { "_umb_urls", "_umb_releasedate", "_umb_expiredate", "_umb_template" };
+ var propsTab = content.Tabs.Last();
+ propsTab.Properties = propsTab.Properties
+ .Where(p => excludeProps.Contains(p.Alias) == false);
+ }
+
+ ///
+ /// Gets the content json for the content id
+ ///
+ ///
+ /// If set to true, user and group start node permissions will be ignored.
+ ///
+ [OutgoingEditorModelEvent]
+ [EnsureUserPermissionForContent("id")]
+ public ContentItemDisplay GetById(int id, [FromUri]bool ignoreUserStartNodes = false)
+ {
+ var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));
+ if (foundContent == null)
+ {
+ HandleContentNotFound(id);
+ }
+
+ var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext);
+ return content;
+ }
+
+ ///
+ /// Gets the content json for the content id
+ ///
+ ///
+ ///
+ [OutgoingEditorModelEvent]
+ [EnsureUserPermissionForContent("id")]
+ public ContentItemDisplay GetById(Guid id)
+ {
+ var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));
+ if (foundContent == null)
+ {
+ HandleContentNotFound(id);
+ }
+
+ var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext);
+ return content;
+ }
+
+ ///
+ /// Gets the content json for the content id
+ ///
+ ///
+ ///
+ [OutgoingEditorModelEvent]
+ [EnsureUserPermissionForContent("id")]
+ public ContentItemDisplay GetById(Udi id)
+ {
+ var guidUdi = id as GuidUdi;
+ if (guidUdi != null)
+ {
+ return GetById(guidUdi.Guid);
+ }
+
+ throw new HttpResponseException(HttpStatusCode.NotFound);
+ }
+
+ [EnsureUserPermissionForContent("id")]
+ public ContentItemDisplay GetWithTreeDefinition(int id)
+ {
+ var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));
+ if (foundContent == null)
+ {
+ HandleContentNotFound(id);
+ }
+
+ var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext);
+ return content;
+ }
+
+ ///
+ /// Gets an empty content item for the
+ ///
+ ///
+ ///
+ ///
+ /// If this is a container type, we'll remove the umbContainerView tab for a new item since
+ /// it cannot actually list children if it doesn't exist yet.
+ ///
+ [OutgoingEditorModelEvent]
+ public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId)
+ {
+ var contentType = Services.ContentTypeService.GetContentType(contentTypeAlias);
+ if (contentType == null)
+ {
+ throw new HttpResponseException(HttpStatusCode.NotFound);
+ }
+
+ var emptyContent = Services.ContentService.CreateContent("", parentId, contentType.Alias, UmbracoUser.Id);
+ var mapped = AutoMapperExtensions.MapWithUmbracoContext(emptyContent, UmbracoContext);
+ // translate the content type name if applicable
+ mapped.ContentTypeName = Services.TextService.UmbracoDictionaryTranslate(mapped.ContentTypeName);
+ // if your user type doesn't have access to the Settings section it would not get this property mapped
+ if(mapped.DocumentType != null)
+ mapped.DocumentType.Name = Services.TextService.UmbracoDictionaryTranslate(mapped.DocumentType.Name);
+
+ //remove this tab if it exists: umbContainerView
+ var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName);
+ mapped.Tabs = mapped.Tabs.Except(new[] { containerTab });
+ return mapped;
+ }
+
+ [OutgoingEditorModelEvent]
+ public ContentItemDisplay GetEmpty(int blueprintId, int parentId)
+ {
+ var blueprint = Services.ContentService.GetBlueprintById(blueprintId);
+ if (blueprint == null)
+ {
+ throw new HttpResponseException(HttpStatusCode.NotFound);
+ }
+
+ blueprint.Id = 0;
+ blueprint.Name = string.Empty;
+ blueprint.ParentId = parentId;
+
+ var mapped = Mapper.Map(blueprint);
+
+ //remove this tab if it exists: umbContainerView
+ var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName);
+ mapped.Tabs = mapped.Tabs.Except(new[] { containerTab });
+ return mapped;
+ }
+
+ ///
+ /// Gets the Url for a given node ID
+ ///
+ ///
+ ///
+ public HttpResponseMessage GetNiceUrl(int id)
+ {
+ var url = Umbraco.NiceUrl(id);
+ var response = Request.CreateResponse(HttpStatusCode.OK);
+ response.Content = new StringContent(url, Encoding.UTF8, "application/json");
+ return response;
+ }
+
+ ///
+ /// Gets the Url for a given node ID
+ ///
+ ///
+ ///
+ public HttpResponseMessage GetNiceUrl(Guid id)
+ {
+ var url = Umbraco.UrlProvider.GetUrl(id);
+ var response = Request.CreateResponse(HttpStatusCode.OK);
+ response.Content = new StringContent(url, Encoding.UTF8, "application/json");
+ return response;
+ }
+
+ ///
+ /// Gets the Url for a given node ID
+ ///
+ ///
+ ///
+ public HttpResponseMessage GetNiceUrl(Udi id)
+ {
+ var guidUdi = id as GuidUdi;
+ if (guidUdi != null)
+ {
+ return GetNiceUrl(guidUdi.Guid);
+ }
+ throw new HttpResponseException(HttpStatusCode.NotFound);
+ }
+
+ ///
+ /// Gets the children for the content id passed in
+ ///
+ ///
+ [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")]
+ public PagedResult> GetChildren(
+ int id,
+ int pageNumber = 0, //TODO: This should be '1' as it's not the index
+ int pageSize = 0,
+ string orderBy = "SortOrder",
+ Direction orderDirection = Direction.Ascending,
+ bool orderBySystemField = true,
+ string filter = "")
+ {
+ return GetChildren(id, null, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter);
+ }
+
+ ///
+ /// Gets the children for the content id passed in
+ ///
+ ///
+ [FilterAllowedOutgoingContent(typeof(IEnumerable>), "Items")]
+ public PagedResult> GetChildren(
+ int id,
+ string includeProperties,
+ int pageNumber = 0, //TODO: This should be '1' as it's not the index
+ int pageSize = 0,
+ string orderBy = "SortOrder",
+ Direction orderDirection = Direction.Ascending,
+ bool orderBySystemField = true,
+ string filter = "")
+ {
+ long totalChildren;
+ IContent[] children;
+ if (pageNumber > 0 && pageSize > 0)
+ {
+ children = Services.ContentService
+ .GetPagedChildren(id, (pageNumber - 1), pageSize, out totalChildren
+ , orderBy, orderDirection, orderBySystemField, filter).ToArray();
+ }
+ else
+ {
+ children = Services.ContentService.GetChildren(id).ToArray();
+ totalChildren = children.Length;
+ }
+
+ if (totalChildren == 0)
+ {
+ return new PagedResult>(0, 0, 0);
+ }
+
+ var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize);
+ pagedResult.Items = children.Select(content =>
+ Mapper.Map>(content,
+ opts =>
+ {
+ // if there's a list of property aliases to map - we will make sure to store this in the mapping context.
+ if (String.IsNullOrWhiteSpace(includeProperties) == false)
+ {
+ opts.Items["IncludeProperties"] = includeProperties.Split(new[] { ", ", "," }, StringSplitOptions.RemoveEmptyEntries);
+ }
+ }));
+
+ return pagedResult;
+ }
+
+ [Obsolete("Dont use this, it is incorrectly named, use HasPermission instead")]
+ public bool GetHasPermission(string permissionToCheck, int nodeId)
+ {
+ return HasPermission(permissionToCheck, nodeId);
+ }
+
+ ///
+ /// Returns permissions for all nodes passed in for the current user
+ /// TODO: This should be moved to the CurrentUserController?
+ ///
+ ///
+ ///
+ [HttpPost]
+ public Dictionary GetPermissions(int[] nodeIds)
+ {
+ var permissions = Services.UserService
+ .GetPermissions(Security.CurrentUser, nodeIds);
+
+ var permissionsDictionary = new Dictionary();
+ foreach (var nodeId in nodeIds)
+ {
+ var aggregatePerms = permissions.GetAllPermissions(nodeId).ToArray();
+ permissionsDictionary.Add(nodeId, aggregatePerms);
+ }
+
+ return permissionsDictionary;
+ }
+
+ ///
+ /// Checks a nodes permission for the current user
+ /// TODO: This should be moved to the CurrentUserController?
+ ///
+ ///
+ ///
+ ///
+ [HttpGet]
+ public bool HasPermission(string permissionToCheck, int nodeId)
+ {
+ var p = Services.UserService.GetPermissions(Security.CurrentUser, nodeId).GetAllPermissions();
+ if (p.Contains(permissionToCheck.ToString(CultureInfo.InvariantCulture)))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Creates a blueprint from a content item
+ ///
+ /// The content id to copy
+ /// The name of the blueprint
+ ///
+ [HttpPost]
+ public SimpleNotificationModel CreateBlueprintFromContent([FromUri]int contentId, [FromUri]string name)
+ {
+ if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name");
+
+ var content = Services.ContentService.GetById(contentId);
+ if (content == null)
+ throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
+
+ EnsureUniqueName(name, content, "name");
+
+ var blueprint = Services.ContentService.CreateContentFromBlueprint(content, name, Security.CurrentUser.Id);
+
+ Services.ContentService.SaveBlueprint(blueprint, Security.CurrentUser.Id);
+
+ var notificationModel = new SimpleNotificationModel();
+ notificationModel.AddSuccessNotification(
+ Services.TextService.Localize("blueprints/createdBlueprintHeading"),
+ Services.TextService.Localize("blueprints/createdBlueprintMessage", new[] { content.Name })
+ );
+
+ return notificationModel;
+ }
+
+ private void EnsureUniqueName(string name, IContent content, string modelName)
+ {
+ var existing = Services.ContentService.GetBlueprintsForContentTypes(content.ContentTypeId);
+ if (existing.Any(x => x.Name == name && x.Id != content.Id))
+ {
+ ModelState.AddModelError(modelName, Services.TextService.Localize("blueprints/duplicateBlueprintMessage"));
+ throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState));
+ }
+ }
+
+ ///
+ /// Saves content
+ ///
+ ///
+ [FileUploadCleanupFilter]
+ [ContentPostValidate]
+ public ContentItemDisplay PostSaveBlueprint(
+ [ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem)
+ {
+ var contentItemDisplay = PostSaveInternal(contentItem,
+ content =>
+ {
+ EnsureUniqueName(content.Name, content, "Name");
+
+ Services.ContentService.SaveBlueprint(contentItem.PersistedContent, Security.CurrentUser.Id);
+ //we need to reuse the underlying logic so return the result that it wants
+ return Attempt.Succeed(new OperationStatus(OperationStatusType.Success, new EventMessages()));
+ });
+ SetupBlueprint(contentItemDisplay, contentItemDisplay.PersistedContent);
+
+ return contentItemDisplay;
+ }
+
+ ///
+ /// Saves content
+ ///
+ ///
+ [FileUploadCleanupFilter]
+ [ContentPostValidate]
+ [OutgoingEditorModelEvent]
+ public ContentItemDisplay PostSave(
+ [ModelBinder(typeof(ContentItemBinder))]
+ ContentItemSave contentItem)
+ {
+ return PostSaveInternal(contentItem,
+ content => Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id));
+ }
+
+ private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func> saveMethod)
+ {
+ //Recent versions of IE/Edge may send in the full clientside file path instead of just the file name.
+ //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all
+ //uploaded files to being *only* the actual file name (as it should be).
+ if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any())
+ {
+ foreach (var file in contentItem.UploadedFiles)
+ {
+ file.FileName = Path.GetFileName(file.FileName);
+ }
+ }
+
+ //If we've reached here it means:
+ // * Our model has been bound
+ // * and validated
+ // * any file attachments have been saved to their temporary location for us to use
+ // * we have a reference to the DTO object and the persisted object
+ // * Permissions are valid
+ MapPropertyValues(contentItem);
+
+ //We need to manually check the validation results here because:
+ // * We still need to save the entity even if there are validation value errors
+ // * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null)
+ // then we cannot continue saving, we can only display errors
+ // * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display
+ // a message indicating this
+ if (ModelState.IsValid == false)
+ {
+ if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem) && IsCreatingAction(contentItem.Action))
+ {
+ //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue!
+ // add the modelstate to the outgoing object and throw a validation message
+ var forDisplay = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext);
+ forDisplay.Errors = ModelState.ToErrorDictionary();
+ throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
+
+ }
+
+ //if the model state is not valid we cannot publish so change it to save
+ switch (contentItem.Action)
+ {
+ case ContentSaveAction.Publish:
+ contentItem.Action = ContentSaveAction.Save;
+ break;
+ case ContentSaveAction.PublishNew:
+ contentItem.Action = ContentSaveAction.SaveNew;
+ break;
+ }
+ }
+
+ //initialize this to successful
+ var publishStatus = Attempt.Succeed();
+ var wasCancelled = false;
+
+ if (contentItem.Action == ContentSaveAction.Save || contentItem.Action == ContentSaveAction.SaveNew)
+ {
+ //save the item
+ var saveResult = saveMethod(contentItem.PersistedContent);
+
+ wasCancelled = saveResult.Success == false && saveResult.Result.StatusType == OperationStatusType.FailedCancelledByEvent;
+ }
+ else if (contentItem.Action == ContentSaveAction.SendPublish || contentItem.Action == ContentSaveAction.SendPublishNew)
+ {
+ var sendResult = Services.ContentService.SendToPublication(contentItem.PersistedContent, Security.CurrentUser.Id);
+ wasCancelled = sendResult == false;
+ }
+ else
+ {
+ //publish the item and check if it worked, if not we will show a diff msg below
+ publishStatus = Services.ContentService.SaveAndPublishWithStatus(contentItem.PersistedContent, Security.CurrentUser.Id);
+ wasCancelled = publishStatus.Result.StatusType == PublishStatusType.FailedCancelledByEvent;
+ }
+
+ //return the updated model
+ var display = AutoMapperExtensions.MapWithUmbracoContext(contentItem.PersistedContent, UmbracoContext);
+
+ //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403
+ HandleInvalidModelState(display);
+
+ //put the correct msgs in
+ switch (contentItem.Action)
+ {
+ case ContentSaveAction.Save:
+ case ContentSaveAction.SaveNew:
+ if (wasCancelled == false)
+ {
+ display.AddSuccessNotification(
+ Services.TextService.Localize("speechBubbles/editContentSavedHeader"),
+ contentItem.ReleaseDate.HasValue
+ ? Services.TextService.Localize("speechBubbles/editContentSavedWithReleaseDateText", new [] { contentItem.ReleaseDate.Value.ToLongDateString(), contentItem.ReleaseDate.Value.ToShortTimeString() })
+ : Services.TextService.Localize("speechBubbles/editContentSavedText")
+ );
+ }
+ else
+ {
+ AddCancelMessage(display);
+ }
+ break;
+ case ContentSaveAction.SendPublish:
+ case ContentSaveAction.SendPublishNew:
+ if (wasCancelled == false)
+ {
+ display.AddSuccessNotification(
+ Services.TextService.Localize("speechBubbles/editContentSendToPublish"),
+ Services.TextService.Localize("speechBubbles/editContentSendToPublishText"));
+ }
+ else
+ {
+ AddCancelMessage(display);
+ }
+ break;
+ case ContentSaveAction.Publish:
+ case ContentSaveAction.PublishNew:
+ ShowMessageForPublishStatus(publishStatus.Result, display, contentItem.ExpireDate);
+ break;
+ }
+
+ //If the item is new and the operation was cancelled, we need to return a different
+ // status code so the UI can handle it since it won't be able to redirect since there
+ // is no Id to redirect to!
+ if (wasCancelled && IsCreatingAction(contentItem.Action))
+ {
+ throw new HttpResponseException(Request.CreateValidationErrorResponse(display));
+ }
+
+ display.PersistedContent = contentItem.PersistedContent;
+
+ return display;
+ }
+
+ ///
+ /// Publishes a document with a given ID
+ ///
+ ///
+ ///
+ ///
+ /// The CanAccessContentAuthorize attribute will deny access to this method if the current user
+ /// does not have Publish access to this node.
+ ///
+ ///
+ [EnsureUserPermissionForContent("id", 'U')]
+ public HttpResponseMessage PostPublishById(int id)
+ {
+ var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));
+
+ if (foundContent == null)
+ {
+ return HandleContentNotFound(id, false);
+ }
+
+ var publishResult = Services.ContentService.PublishWithStatus(foundContent, Security.CurrentUser.Id);
+ if (publishResult.Success == false)
+ {
+ var notificationModel = new SimpleNotificationModel();
+ ShowMessageForPublishStatus(publishResult.Result, notificationModel, foundContent.ExpireDate);
+ return Request.CreateValidationErrorResponse(notificationModel);
+ }
+
+ //return ok
+ return Request.CreateResponse(HttpStatusCode.OK);
+
+ }
+
+ [HttpDelete]
+ [HttpPost]
+ public HttpResponseMessage DeleteBlueprint(int id)
+ {
+ var found = Services.ContentService.GetBlueprintById(id);
+
+ if (found == null)
+ {
+ return HandleContentNotFound(id, false);
+ }
+
+ Services.ContentService.DeleteBlueprint(found);
+
+ return Request.CreateResponse(HttpStatusCode.OK);
+ }
+
+ ///
+ /// Moves an item to the recycle bin, if it is already there then it will permanently delete it
+ ///
+ ///
+ ///
+ ///
+ /// The CanAccessContentAuthorize attribute will deny access to this method if the current user
+ /// does not have Delete access to this node.
+ ///
+ [EnsureUserPermissionForContent("id", 'D')]
+ [HttpDelete]
+ [HttpPost]
+ public HttpResponseMessage DeleteById(int id)
+ {
+ var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));
+
+ if (foundContent == null)
+ {
+ return HandleContentNotFound(id, false);
+ }
+
+ //if the current item is in the recycle bin
+ if (foundContent.IsInRecycleBin() == false)
+ {
+ var moveResult = Services.ContentService.WithResult().MoveToRecycleBin(foundContent, Security.CurrentUser.Id);
+ if (moveResult == false)
+ {
+ //returning an object of INotificationModel will ensure that any pending
+ // notification messages are added to the response.
+ return Request.CreateValidationErrorResponse(new SimpleNotificationModel());
+ }
+ }
+ else
+ {
+ var deleteResult = Services.ContentService.WithResult().Delete(foundContent, Security.CurrentUser.Id);
+ if (deleteResult == false)
+ {
+ //returning an object of INotificationModel will ensure that any pending
+ // notification messages are added to the response.
+ return Request.CreateValidationErrorResponse(new SimpleNotificationModel());
+ }
+ }
+
+ return Request.CreateResponse(HttpStatusCode.OK);
+ }
+
+ ///
+ /// Empties the recycle bin
+ ///
+ ///
+ ///
+ /// attributed with EnsureUserPermissionForContent to verify the user has access to the recycle bin
+ ///
+ [HttpDelete]
+ [HttpPost]
+ [EnsureUserPermissionForContent(Constants.System.RecycleBinContent, 'D')]
+ public HttpResponseMessage EmptyRecycleBin()
+ {
+ Services.ContentService.EmptyRecycleBin();
+
+ return Request.CreateNotificationSuccessResponse(Services.TextService.Localize("defaultdialogs/recycleBinIsEmpty"));
+ }
+
+ ///
+ /// Change the sort order for content
+ ///
+ ///
+ ///
+ [EnsureUserPermissionForContent("sorted.ParentId", 'S')]
+ public HttpResponseMessage PostSort(ContentSortOrder sorted)
+ {
+ if (sorted == null)
+ {
+ return Request.CreateResponse(HttpStatusCode.NotFound);
+ }
+
+ //if there's nothing to sort just return ok
+ if (sorted.IdSortOrder.Length == 0)
+ {
+ return Request.CreateResponse(HttpStatusCode.OK);
+ }
+
+ try
+ {
+ var contentService = Services.ContentService;
+
+ // Save content with new sort order and update content xml in db accordingly
+ if (contentService.Sort(sorted.IdSortOrder, Security.CurrentUser.Id) == false)
+ {
+ LogHelper.Warn("Content sorting failed, this was probably caused by an event being cancelled");
+ return Request.CreateValidationErrorResponse("Content sorting failed, this was probably caused by an event being cancelled");
+ }
+ return Request.CreateResponse(HttpStatusCode.OK);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.Error("Could not update content sort order", ex);
+ throw;
+ }
+ }
+
+ ///
+ /// Change the sort order for media
+ ///
+ ///
+ ///
+ [EnsureUserPermissionForContent("move.ParentId", 'M')]
+ public HttpResponseMessage PostMove(MoveOrCopy move)
+ {
+ var toMove = ValidateMoveOrCopy(move);
+
+ Services.ContentService.Move(toMove, move.ParentId, Security.CurrentUser.Id);
+
+ var response = Request.CreateResponse(HttpStatusCode.OK);
+ response.Content = new StringContent(toMove.Path, Encoding.UTF8, "application/json");
+ return response;
+ }
+
+ ///
+ /// Copies a content item and places the copy as a child of a given parent Id
+ ///
+ ///
+ ///
+ [EnsureUserPermissionForContent("copy.ParentId", 'C')]
+ public HttpResponseMessage PostCopy(MoveOrCopy copy)
+ {
+ var toCopy = ValidateMoveOrCopy(copy);
+
+ var c = Services.ContentService.Copy(toCopy, copy.ParentId, copy.RelateToOriginal, copy.Recursive, Security.CurrentUser.Id);
+
+ var response = Request.CreateResponse(HttpStatusCode.OK);
+ response.Content = new StringContent(c.Path, Encoding.UTF8, "application/json");
+ return response;
+ }
+
+ ///
+ /// Unpublishes a node with a given Id and returns the unpublished entity
+ ///
+ ///
+ ///
+ [EnsureUserPermissionForContent("id", 'U')]
+ [OutgoingEditorModelEvent]
+ public ContentItemDisplay PostUnPublish(int id)
+ {
+ var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));
+
+ if (foundContent == null)
+ HandleContentNotFound(id);
+
+ var unpublishResult = Services.ContentService.WithResult().UnPublish(foundContent, Security.CurrentUser.Id);
+
+ var content = AutoMapperExtensions.MapWithUmbracoContext(foundContent, UmbracoContext);
+
+ if (unpublishResult == false)
+ {
+ AddCancelMessage(content);
+ throw new HttpResponseException(Request.CreateValidationErrorResponse(content));
+ }
+ else
+ {
+ content.AddSuccessNotification(Services.TextService.Localize("content/unPublish"), Services.TextService.Localize("speechBubbles/contentUnpublished"));
+ return content;
+ }
+ }
+
+ ///
+ /// Maps the dto property values to the persisted model
+ ///
+ ///
+ private void MapPropertyValues(ContentItemSave contentItem)
+ {
+ UpdateName(contentItem);
+
+ //TODO: We need to support 'send to publish'
+
+ contentItem.PersistedContent.ExpireDate = contentItem.ExpireDate;
+ contentItem.PersistedContent.ReleaseDate = contentItem.ReleaseDate;
+ //only set the template if it didn't change
+ var templateChanged = (contentItem.PersistedContent.Template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false)
+ || (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias)
+ || (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace());
+ if (templateChanged)
+ {
+ var template = Services.FileService.GetTemplate(contentItem.TemplateAlias);
+ if (template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false)
+ {
+ //ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias);
+ LogHelper.Warn("No template exists with the specified alias: " + contentItem.TemplateAlias);
+ }
+ else
+ {
+ //NOTE: this could be null if there was a template and the posted template is null, this should remove the assigned template
+ contentItem.PersistedContent.Template = template;
+ }
+ }
+
+ base.MapPropertyValues(contentItem);
+ }
+
+ ///
+ /// Ensures the item can be moved/copied to the new location
+ ///
+ ///
+ ///
+ private IContent ValidateMoveOrCopy(MoveOrCopy model)
+ {
+ if (model == null)
+ {
+ throw new HttpResponseException(HttpStatusCode.NotFound);
+ }
+
+ var contentService = Services.ContentService;
+ var toMove = contentService.GetById(model.Id);
+ if (toMove == null)
+ {
+ throw new HttpResponseException(HttpStatusCode.NotFound);
+ }
+ if (model.ParentId < 0)
+ {
+ //cannot move if the content item is not allowed at the root
+ if (toMove.ContentType.AllowedAsRoot == false)
+ {
+ throw new HttpResponseException(
+ Request.CreateNotificationValidationErrorResponse(
+ Services.TextService.Localize("moveOrCopy/notAllowedAtRoot")));
+ }
+ }
+ else
+ {
+ var parent = contentService.GetById(model.ParentId);
+ if (parent == null)
+ {
+ throw new HttpResponseException(HttpStatusCode.NotFound);
+ }
+
+ //check if the item is allowed under this one
+ if (parent.ContentType.AllowedContentTypes.Select(x => x.Id).ToArray()
+ .Any(x => x.Value == toMove.ContentType.Id) == false)
+ {
+ throw new HttpResponseException(
+ Request.CreateNotificationValidationErrorResponse(
+ Services.TextService.Localize("moveOrCopy/notAllowedByContentType")));
+ }
+
+ // Check on paths
+ if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1)
+ {
+ throw new HttpResponseException(
+ Request.CreateNotificationValidationErrorResponse(
+ Services.TextService.Localize("moveOrCopy/notAllowedByPath")));
+ }
+ }
+
+ return toMove;
+ }
+
+ private void ShowMessageForPublishStatus(PublishStatus status, INotificationModel display, DateTime? expireDate)
+ {
+ switch (status.StatusType)
+ {
+ case PublishStatusType.Success:
+ case PublishStatusType.SuccessAlreadyPublished:
+ display.AddSuccessNotification(
+ Services.TextService.Localize("speechBubbles/editContentPublishedHeader"),
+ expireDate.HasValue
+ ? Services.TextService.Localize("speechBubbles/editContentPublishedWithExpireDateText", new [] { expireDate.Value.ToLongDateString(), expireDate.Value.ToShortTimeString() })
+ : Services.TextService.Localize("speechBubbles/editContentPublishedText")
+ );
+ break;
+ case PublishStatusType.FailedPathNotPublished:
+ display.AddWarningNotification(
+ Services.TextService.Localize("publish"),
+ Services.TextService.Localize("publish/contentPublishedFailedByParent",
+ new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim());
+ break;
+ case PublishStatusType.FailedCancelledByEvent:
+ AddCancelMessage(display, "publish", "speechBubbles/contentPublishedFailedByEvent");
+ break;
+ case PublishStatusType.FailedAwaitingRelease:
+ display.AddWarningNotification(
+ Services.TextService.Localize("publish"),
+ Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease",
+ new[] { string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id) }).Trim());
+ break;
+ case PublishStatusType.FailedHasExpired:
+ display.AddWarningNotification(
+ Services.TextService.Localize("publish"),
+ Services.TextService.Localize("publish/contentPublishedFailedExpired",
+ new[]
+ {
+ string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id),
+ }).Trim());
+ break;
+ case PublishStatusType.FailedIsTrashed:
+ //TODO: We should add proper error messaging for this!
+ break;
+ case PublishStatusType.FailedContentInvalid:
+ display.AddWarningNotification(
+ Services.TextService.Localize("publish"),
+ Services.TextService.Localize("publish/contentPublishedFailedInvalid",
+ new[]
+ {
+ string.Format("{0} ({1})", status.ContentItem.Name, status.ContentItem.Id),
+ string.Join(",", status.InvalidProperties.Select(x => x.Alias))
+ }).Trim());
+ break;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+
+
+ ///
+ /// Performs a permissions check for the user to check if it has access to the node based on
+ /// start node and/or permissions for the node
+ ///
+ /// The storage to add the content item to so it can be reused
+ ///
+ ///
+ ///
+ ///
+ /// The content to lookup, if the contentItem is not specified
+ ///
+ /// Specifies the already resolved content item to check against
+ /// If set to true, user and group start node permissions will be ignored.
+ ///
+ internal static bool CheckPermissions(
+ IDictionary storage,
+ IUser user,
+ IUserService userService,
+ IContentService contentService,
+ IEntityService entityService,
+ int nodeId,
+ char[] permissionsToCheck = null,
+ IContent contentItem = null,
+ bool ignoreUserStartNodes = false)
+ {
+ if (storage == null) throw new ArgumentNullException("storage");
+ if (user == null) throw new ArgumentNullException("user");
+ if (userService == null) throw new ArgumentNullException("userService");
+ if (contentService == null) throw new ArgumentNullException("contentService");
+ if (entityService == null) throw new ArgumentNullException("entityService");
+
+ if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent)
+ {
+ contentItem = contentService.GetById(nodeId);
+ //put the content item into storage so it can be retreived
+ // in the controller (saves a lookup)
+ storage[typeof(IContent).ToString()] = contentItem;
+ }
+
+ if (contentItem == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinContent)
+ {
+ throw new HttpResponseException(HttpStatusCode.NotFound);
+ }
+
+ if(ignoreUserStartNodes)
+ {
+ return true;
+ }
+
+ var hasPathAccess = (nodeId == Constants.System.Root)
+ ? user.HasContentRootAccess(entityService)
+ : (nodeId == Constants.System.RecycleBinContent)
+ ? user.HasContentBinAccess(entityService)
+ : user.HasPathAccess(contentItem, entityService);
+
+ if (hasPathAccess == false)
+ {
+ return false;
+ }
+
+ if (permissionsToCheck == null || permissionsToCheck.Length == 0)
+ {
+ return true;
+ }
+
+ //get the implicit/inherited permissions for the user for this path,
+ //if there is no content item for this id, than just use the id as the path (i.e. -1 or -20)
+ var path = contentItem != null ? contentItem.Path : nodeId.ToString();
+ var permission = userService.GetPermissionsForPath(user, path);
+
+ var allowed = true;
+ foreach (var p in permissionsToCheck)
+ {
+ if (permission == null
+ || permission.GetAllPermissions().Contains(p.ToString(CultureInfo.InvariantCulture)) == false)
+ {
+ allowed = false;
+ }
+ }
+ return allowed;
+ }
+
+ [EnsureUserPermissionForContent("contentId", 'F')]
+ public IEnumerable GetNotificationOptions(int contentId)
+ {
+ var notifications = new List();
+ if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
+
+ var content = Services.ContentService.GetById(contentId);
+ if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
+
+ var actionList = ActionsResolver.Current.Actions;
+ foreach (var a in actionList)
+ {
+ if (a.ShowInNotifier)
+ {
+ NotifySetting n = new NotifySetting
+ {
+ Name = ui.Text("actions", a.Alias),
+ Checked = (UmbracoUser.GetNotifications(content.Path).IndexOf(a.Letter) > -1),
+ NotifyCode = a.Letter.ToString()
+ };
+ notifications.Add(n);
+ }
+ }
+ return notifications;
+ }
+
+ public void PostNotificationOptions(int contentId, string notifyOptions = "")
+ {
+ if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
+ var content = Services.ContentService.GetById(contentId);
+
+ if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
+ var node = new CMSNode(contentId);
+
+ global::umbraco.cms.businesslogic.workflow.Notification.UpdateNotifications(UmbracoUser, node, notifyOptions ?? "");
+ }
+ }
+}
diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs
index 8bde435ef6..045b4e6f0d 100644
--- a/src/Umbraco.Web/Editors/EntityController.cs
+++ b/src/Umbraco.Web/Editors/EntityController.cs
@@ -82,9 +82,10 @@ namespace Umbraco.Web.Editors
///
/// A starting point for the search, generally a node id, but for members this is a member type alias
///
+ /// If set to true, user and group start node permissions will be ignored.
///
[HttpGet]
- public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null)
+ public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null, bool? ignoreUserStartNodes = false)
{
//TODO: Should we restrict search results based on what app the user has access to?
// - Theoretically you shouldn't be able to see member data if you don't have access to members right?
@@ -92,7 +93,7 @@ namespace Umbraco.Web.Editors
if (string.IsNullOrEmpty(query))
return Enumerable.Empty();
- return ExamineSearch(query, type, searchFrom);
+ return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes != null && ignoreUserStartNodes.Value);
}
///
@@ -534,7 +535,8 @@ namespace Umbraco.Web.Editors
int pageSize,
string orderBy = "SortOrder",
Direction orderDirection = Direction.Ascending,
- string filter = "")
+ string filter = "",
+ bool ignoreUserStartNodes = false)
{
if (pageNumber <= 0)
throw new HttpResponseException(HttpStatusCode.NotFound);
@@ -562,7 +564,7 @@ namespace Umbraco.Web.Editors
break;
}
- entities = aids == null || aids.Contains(Constants.System.Root)
+ entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes
? Services.EntityService.GetPagedDescendantsFromRoot(objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter, includeTrashed: false)
: Services.EntityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter);
}
@@ -598,9 +600,9 @@ namespace Umbraco.Web.Editors
}
}
- public IEnumerable GetAncestors(int id, UmbracoEntityTypes type)
+ public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, bool ignoreUserStartNodes = false)
{
- return GetResultForAncestors(id, type);
+ return GetResultForAncestors(id, type, ignoreUserStartNodes);
}
public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter, [FromUri]IDictionary postFilterParams)
@@ -614,11 +616,12 @@ namespace Umbraco.Web.Editors
///
///
///
+ /// If set to true, user and group start node permissions will be ignored.
///
- private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null)
+ private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false)
{
long total;
- return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, searchFrom);
+ return _treeSearcher.ExamineSearch(Umbraco, query, entityType, 200, 0, out total, searchFrom, ignoreUserStartNodes);
}
@@ -645,7 +648,7 @@ namespace Umbraco.Web.Editors
}
}
- private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType)
+ private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, bool ignoreUserStartNodes = false)
{
var objectType = ConvertToObjectType(entityType);
if (objectType.HasValue)
@@ -654,35 +657,38 @@ namespace Umbraco.Web.Editors
var ids = Services.EntityService.Get(id).Path.Split(',').Select(int.Parse).Distinct().ToArray();
- int[] aids = null;
- switch (entityType)
+ if (ignoreUserStartNodes == false)
{
- case UmbracoEntityTypes.Document:
- aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService);
- break;
- case UmbracoEntityTypes.Media:
- aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService);
- break;
- }
-
- if (aids != null)
- {
- var lids = new List();
- var ok = false;
- foreach (var i in ids)
+ int[] aids = null;
+ switch (entityType)
{
- if (ok)
- {
- lids.Add(i);
- continue;
- }
- if (aids.Contains(i))
- {
- lids.Add(i);
- ok = true;
- }
+ case UmbracoEntityTypes.Document:
+ aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService);
+ break;
+ case UmbracoEntityTypes.Media:
+ aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService);
+ break;
+ }
+
+ if (aids != null)
+ {
+ var lids = new List();
+ var ok = false;
+ foreach (var i in ids)
+ {
+ if (ok)
+ {
+ lids.Add(i);
+ continue;
+ }
+ if (aids.Contains(i))
+ {
+ lids.Add(i);
+ ok = true;
+ }
+ }
+ ids = lids.ToArray();
}
- ids = lids.ToArray();
}
return ids.Length == 0
diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs
index e3f2365565..97e24ee6b8 100644
--- a/src/Umbraco.Web/Editors/MediaController.cs
+++ b/src/Umbraco.Web/Editors/MediaController.cs
@@ -264,11 +264,12 @@ namespace Umbraco.Web.Editors
string orderBy = "SortOrder",
Direction orderDirection = Direction.Ascending,
bool orderBySystemField = true,
- string filter = "")
+ string filter = "",
+ bool ignoreUserStartNodes = false)
{
//if a request is made for the root node data but the user's start node is not the default, then
// we need to return their start nodes
- if (id == Constants.System.Root && UserStartNodes.Length > 0 && UserStartNodes.Contains(Constants.System.Root) == false)
+ if (id == Constants.System.Root && UserStartNodes.Length > 0 && (UserStartNodes.Contains(Constants.System.Root) == false && ignoreUserStartNodes == false))
{
if (pageNumber > 0)
return new PagedResult>(0, 0, 0);
@@ -321,6 +322,7 @@ namespace Umbraco.Web.Editors
///
///
///
+ /// If set to true, user and group start node permissions will be ignored.
///
[FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")]
public PagedResult> GetChildren(Guid id,
@@ -329,12 +331,13 @@ namespace Umbraco.Web.Editors
string orderBy = "SortOrder",
Direction orderDirection = Direction.Ascending,
bool orderBySystemField = true,
- string filter = "")
+ string filter = "",
+ bool ignoreUserStartNodes = false)
{
var entity = Services.EntityService.GetByKey(id);
if (entity != null)
{
- return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter);
+ return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, ignoreUserStartNodes);
}
throw new HttpResponseException(HttpStatusCode.NotFound);
}
@@ -349,6 +352,7 @@ namespace Umbraco.Web.Editors
///
///
///
+ /// If set to true, user and group start node permissions will be ignored.
///
[FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")]
public PagedResult> GetChildren(Udi id,
@@ -357,7 +361,8 @@ namespace Umbraco.Web.Editors
string orderBy = "SortOrder",
Direction orderDirection = Direction.Ascending,
bool orderBySystemField = true,
- string filter = "")
+ string filter = "",
+ bool ignoreUserStartNodes = false)
{
var guidUdi = id as GuidUdi;
if (guidUdi != null)
@@ -365,7 +370,7 @@ namespace Umbraco.Web.Editors
var entity = Services.EntityService.GetByKey(guidUdi.Guid);
if (entity != null)
{
- return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter);
+ return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter, ignoreUserStartNodes);
}
}
@@ -381,7 +386,8 @@ namespace Umbraco.Web.Editors
string orderBy = "SortOrder",
Direction orderDirection = Direction.Ascending,
bool orderBySystemField = true,
- string filter = "")
+ string filter = "",
+ bool ignoreUserStartNodes = false)
{
foreach (var type in new[] { typeof(int), typeof(Guid) })
{
diff --git a/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs
index 5b99264113..0039385e5f 100644
--- a/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/ContentPicker2PropertyEditor.cs
@@ -15,10 +15,11 @@ namespace Umbraco.Web.PropertyEditors
{
InternalPreValues = new Dictionary
{
- {"startNodeId", "-1"},
+ {"startNodeId", "-1"},
{"showOpenButton", "0"},
{"showEditButton", "0"},
{"showPathOnHover", "0"},
+ {"ignoreUserStartNodes", "0"},
{"idType", "udi"}
};
}
@@ -39,7 +40,7 @@ namespace Umbraco.Web.PropertyEditors
{
public ContentPickerPreValueEditor()
{
- //create the fields
+ //create the fields
Fields.Add(new PreValueField()
{
Key = "showOpenButton",
@@ -48,6 +49,13 @@ namespace Umbraco.Web.PropertyEditors
Description = "Opens the node in a dialog"
});
Fields.Add(new PreValueField()
+ {
+ Key = "ignoreUserStartNodes",
+ View = "boolean",
+ Name = "Ignore user start nodes",
+ Description = "Selecting this option allows a user to choose nodes that they normally don't have access to."
+ });
+ Fields.Add(new PreValueField()
{
Key = "startNodeId",
View = "treepicker",
@@ -60,4 +68,4 @@ namespace Umbraco.Web.PropertyEditors
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs
index af30b4ceeb..4a8803a099 100644
--- a/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs
@@ -136,6 +136,9 @@ namespace Umbraco.Web.PropertyEditors
[PreValueField("rte", "Rich text editor", "views/propertyeditors/rte/rte.prevalues.html", Description = "Rich text editor configuration")]
public string Rte { get; set; }
+
+ [PreValueField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")]
+ public bool IgnoreUserStartNodes { get; set; }
}
#region Application event handler, used to bind to events on startup
diff --git a/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs
index 2d9ee68b3b..6aa7ab4a54 100644
--- a/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/MediaPicker2PropertyEditor.cs
@@ -57,6 +57,13 @@ namespace Umbraco.Web.PropertyEditors
Description = "Do not allow folders to be picked."
});
Fields.Add(new PreValueField()
+ {
+ Key = "ignoreUserStartNodes",
+ View = "boolean",
+ Name = "Ignore user start nodes",
+ Description = "Selecting this option allows a user to choose nodes that they normally don't have access to."
+ });
+ Fields.Add(new PreValueField()
{
Key = "startNodeId",
View = "mediapicker",
@@ -69,4 +76,4 @@ namespace Umbraco.Web.PropertyEditors
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs
index f43ecd48be..f57a9951b6 100644
--- a/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/MultiNodeTreePicker2PropertyEditor.cs
@@ -16,6 +16,7 @@ namespace Umbraco.Web.PropertyEditors
{"showOpenButton", "0"},
{"showEditButton", "0"},
{"showPathOnHover", "0"},
+ {"ignoreUserStartNodes", "0"},
{"idType", "udi"}
};
}
@@ -38,6 +39,13 @@ namespace Umbraco.Web.PropertyEditors
{
//create the fields
Fields.Add(new PreValueField()
+ {
+ Key = "ignoreUserStartNodes",
+ View = "boolean",
+ Name = "Ignore user start nodes",
+ Description = "Selecting this option allows a user to choose nodes that they normally don't have access to."
+ });
+ Fields.Add(new PreValueField()
{
Key = "startNode",
View = "treesource",
@@ -118,4 +126,4 @@ namespace Umbraco.Web.PropertyEditors
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs
index a058a16b8a..f3ae317efa 100644
--- a/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/MultiUrlPickerPropertyEditor.cs
@@ -32,6 +32,13 @@ namespace Umbraco.Web.PropertyEditors
{
public MultiUrlPickerPreValueEditor()
{
+ Fields.Add(new PreValueField()
+ {
+ Key = "ignoreUserStartNodes",
+ View = "boolean",
+ Name = "Ignore user start nodes",
+ Description = "Selecting this option allows a user to choose nodes that they normally don't have access to."
+ });
Fields.Add(new PreValueField
{
Key = "minNumber",
diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs
index 541dccaa4e..a96c0724ff 100644
--- a/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/RelatedLinks2PropertyEditor.cs
@@ -14,6 +14,7 @@ namespace Umbraco.Web.PropertyEditors
{
InternalPreValues = new Dictionary
{
+ {"ignoreUserStartNodes", "0"},
{"idType", "udi"}
};
}
@@ -32,8 +33,11 @@ namespace Umbraco.Web.PropertyEditors
internal class RelatedLinksPreValueEditor : PreValueEditor
{
+ [PreValueField("ignoreUserStartNodes", "Ignore user start nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")]
+ public bool IgnoreUserStartNodes { get; set; }
+
[PreValueField("max", "Maximum number of links", "number", Description = "Enter the maximum amount of links to be added, enter 0 for unlimited")]
- public int Maximum { get; set; }
+ public int Maximum { get; set; }
}
}
}
diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs
index cd08a8ad45..69445bc304 100644
--- a/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs
+++ b/src/Umbraco.Web/PropertyEditors/RichTextPreValueEditor.cs
@@ -23,6 +23,14 @@ namespace Umbraco.Web.PropertyEditors
Key = "editor"
});
+ Fields.Add(new PreValueField()
+ {
+ Key = "ignoreUserStartNodes",
+ View = "boolean",
+ Name = "Ignore user start nodes",
+ Description = "Selecting this option allows a user to choose nodes that they normally don't have access to."
+ });
+
Fields.Add(new PreValueField()
{
Name = "Hide Label",
diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs
index 38347de9ed..9714c6b946 100644
--- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs
+++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs
@@ -27,13 +27,14 @@ namespace Umbraco.Web.Search
///
///
///
+ /// If set to true, user and group start node permissions will be ignored.
///
public IEnumerable ExamineSearch(
UmbracoHelper umbracoHelper,
string query,
UmbracoEntityTypes entityType,
int pageSize,
- long pageIndex, out long totalFound, string searchFrom = null)
+ long pageIndex, out long totalFound, string searchFrom = null, bool ignoreUserStartNodes = false)
{
var sb = new StringBuilder();
@@ -61,12 +62,12 @@ namespace Umbraco.Web.Search
case UmbracoEntityTypes.Media:
type = "media";
var allMediaStartNodes = umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(appContext.Services.EntityService);
- AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, appContext.Services.EntityService);
+ AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, appContext.Services.EntityService);
break;
case UmbracoEntityTypes.Document:
type = "content";
var allContentStartNodes = umbracoContext.Security.CurrentUser.CalculateContentStartNodeIds(appContext.Services.EntityService);
- AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, appContext.Services.EntityService);
+ AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, ignoreUserStartNodes, appContext.Services.EntityService);
break;
default:
throw new NotSupportedException("The " + typeof(UmbracoTreeSearcher) + " currently does not support searching against object type " + entityType);
@@ -203,7 +204,7 @@ namespace Umbraco.Web.Search
}
}
- private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, int[] startNodeIds, string searchFrom, IEntityService entityService)
+ private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, int[] startNodeIds, string searchFrom, bool ignoreUserStartNodes, IEntityService entityService)
{
if (sb == null) throw new ArgumentNullException("sb");
if (entityService == null) throw new ArgumentNullException("entityService");
@@ -228,7 +229,7 @@ namespace Umbraco.Web.Search
// make sure we don't find anything
sb.Append("+__Path:none ");
}
- else if (startNodeIds.Contains(-1) == false) // -1 = no restriction
+ else if (startNodeIds.Contains(-1) == false && ignoreUserStartNodes == false) // -1 = no restriction
{
var entityPaths = entityService.GetAllPaths(objectType, startNodeIds);
diff --git a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs
index af38e21546..2624c89b56 100644
--- a/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs
+++ b/src/Umbraco.Web/Trees/ContentTreeControllerBase.cs
@@ -69,7 +69,7 @@ namespace Umbraco.Web.Trees
{
var node = base.CreateRootNode(queryStrings);
- if (IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false)
+ if (IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false && IgnoreUserStartNodes(queryStrings) == false)
{
node.AdditionalData["noAccess"] = true;
}
@@ -91,7 +91,7 @@ namespace Umbraco.Web.Trees
{
bool hasPathAccess;
var entityIsAncestorOfStartNodes = Security.CurrentUser.IsInBranchOfStartNode(e, Services.EntityService, RecycleBinId, out hasPathAccess);
- if (entityIsAncestorOfStartNodes == false)
+ if (IgnoreUserStartNodes(queryStrings) == false && entityIsAncestorOfStartNodes == false)
return null;
var treeNode = GetSingleTreeNode(e, parentId, queryStrings);
@@ -101,7 +101,7 @@ namespace Umbraco.Web.Trees
//the node so we need to return null;
return null;
}
- if (hasPathAccess == false)
+ if (IgnoreUserStartNodes(queryStrings) == false && hasPathAccess == false)
{
treeNode.AdditionalData["noAccess"] = true;
}
@@ -141,7 +141,7 @@ namespace Umbraco.Web.Trees
// ensure that the user has access to that node, otherwise return the empty tree nodes collection
// TODO: in the future we could return a validation statement so we can have some UI to notify the user they don't have access
- if (HasPathAccess(id, queryStrings) == false)
+ if (IgnoreUserStartNodes(queryStrings) == false && HasPathAccess(id, queryStrings) == false)
{
LogHelper.Warn("User " + Security.CurrentUser.Username + " does not have access to node with id " + id);
return nodes;
@@ -158,7 +158,7 @@ namespace Umbraco.Web.Trees
// get child entities - if id is root, but user's start nodes do not contain the
// root node, this returns the start nodes instead of root's children
- var entities = GetChildEntities(id).ToList();
+ var entities = GetChildEntities(id, queryStrings).ToList();
nodes.AddRange(entities.Select(x => GetSingleTreeNodeWithAccessCheck(x, id, queryStrings)).Where(x => x != null));
// if the user does not have access to the root node, what we have is the start nodes,
@@ -190,7 +190,7 @@ namespace Umbraco.Web.Trees
protected abstract UmbracoObjectTypes UmbracoObjectType { get; }
- protected IEnumerable GetChildEntities(string id)
+ protected IEnumerable GetChildEntities(string id, FormDataCollection queryStrings)
{
// try to parse id as an integer else use GetEntityFromId
// which will grok Guids, Udis, etc and let use obtain the id
diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs
index 98eb2ec8bc..2ea41ff128 100644
--- a/src/Umbraco.Web/Trees/TreeControllerBase.cs
+++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs
@@ -349,6 +349,16 @@ namespace Umbraco.Web.Trees
return queryStrings.GetValue(TreeQueryStringParameters.IsDialog);
}
+ ///
+ /// If the request should allows a user to choose nodes that they normally don't have access to
+ ///
+ ///
+ ///
+ protected bool IgnoreUserStartNodes(FormDataCollection queryStrings)
+ {
+ return queryStrings.GetValue(TreeQueryStringParameters.IgnoreUserStartNodes);
+ }
+
///
/// An event that allows developers to modify the tree node collection that is being rendered
///
diff --git a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs
index 7f6fa28187..c79b9f8781 100644
--- a/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs
+++ b/src/Umbraco.Web/Trees/TreeQueryStringParameters.cs
@@ -8,7 +8,8 @@
public const string IsDialog = "isDialog";
public const string Application = "application";
public const string StartNodeId = "startNodeId";
+ public const string IgnoreUserStartNodes = "ignoreUserStartNodes";
//public const string OnNodeClick = "OnNodeClick";
//public const string RenderParent = "RenderParent";
}
-}
\ No newline at end of file
+}
diff --git a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs
index 18880b9f96..64fe9a4b65 100644
--- a/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs
+++ b/src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
@@ -52,7 +52,7 @@ namespace Umbraco.Web.WebApi.Filters
_paramName = paramName;
_permissionToCheck = ActionBrowse.Instance.Letter;
- }
+ }
public EnsureUserPermissionForContentAttribute(string paramName, char permissionToCheck)
: this(paramName)
@@ -72,6 +72,9 @@ namespace Umbraco.Web.WebApi.Filters
//not logged in
throw new HttpResponseException(System.Net.HttpStatusCode.Unauthorized);
}
+
+ var ignoreUserStartNodes = actionContext.ActionArguments.ContainsKey("ignoreUserStartNodes") &&
+ bool.Parse(actionContext.ActionArguments.GetValueAsString("ignoreUserStartNodes"));
int nodeId;
if (_nodeId.HasValue == false)
@@ -124,9 +127,11 @@ namespace Umbraco.Web.WebApi.Filters
actionContext.Request.Properties,
UmbracoContext.Current.Security.CurrentUser,
ApplicationContext.Current.Services.UserService,
- ApplicationContext.Current.Services.ContentService,
- ApplicationContext.Current.Services.EntityService,
- nodeId, _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null))
+ ApplicationContext.Current.Services.ContentService,
+ ApplicationContext.Current.Services.EntityService,
+ nodeId,
+ _permissionToCheck.HasValue ? new[]{_permissionToCheck.Value}: null,
+ ignoreUserStartNodes: ignoreUserStartNodes))
{
base.OnActionExecuting(actionContext);
}
diff --git a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs
index beb67f3395..23d4fb871c 100644
--- a/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs
+++ b/src/Umbraco.Web/WebApi/Filters/FilterAllowedOutgoingMediaAttribute.cs
@@ -3,11 +3,13 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
+using System.Web;
using System.Web.Http.Filters;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Web.Models.ContentEditing;
+using Umbraco.Web.Trees;
namespace Umbraco.Web.WebApi.Filters
{
@@ -77,7 +79,12 @@ namespace Umbraco.Web.WebApi.Filters
protected virtual void FilterItems(IUser user, IList items)
{
- FilterBasedOnStartNode(items, user);
+ bool.TryParse(HttpContext.Current.Request.QueryString.Get(TreeQueryStringParameters.IgnoreUserStartNodes), out var ignoreUserStartNodes);
+
+ if (ignoreUserStartNodes == false)
+ {
+ FilterBasedOnStartNode(items, user);
+ }
}
internal void FilterBasedOnStartNode(IList items, IUser user)